一、什么是Activity
Activity是Android組件中最基本也是最為常見用的四大組件之一。Activity是一個應(yīng)用程序組件秸弛,提供一個屏幕四康,用戶可以用來交互為了完成某項任務(wù)搪搏。
??Activity中所有操作都與用戶密切相關(guān),是一個負(fù)責(zé)與用戶交互的組件闪金,可以通過setContentView(View)來顯示指定控件疯溺。
??在一個android應(yīng)用中,一個Activity通常就是一個單獨的屏幕哎垦,它上面可以顯示一些控件也可以監(jiān)聽并處理用戶的事件做出響應(yīng)囱嫩。Activity之間通過Intent進(jìn)行通信。
二漏设、Activity的基本用法
1. 新建Activity
繼承自AppCompatActivity墨闲,任何Activity都應(yīng)該實現(xiàn)onCreate()方法,由于Android Studio會為我們自動創(chuàng)建布局,所以在OnCreate()方法中調(diào)用setContentView()來設(shè)置對應(yīng)的布局文件愿题。
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
2. 創(chuàng)建布局
我們創(chuàng)建Activity時损俭,Android Studio會為我們創(chuàng)建相應(yīng)的布局文件蛙奖,我們修改為LinearLayout,并加入一個Button杆兵。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.kingdee.zhao.myapplication.TestActivity">
<Button
android:id="@+id/btn1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button1" />
</LinearLayout>
3. 在AndroidManifest文件中注冊Activity
只要是四大組件雁仲,要使用都必須在AndroidManifest文件中注冊,好在Android Studio會為我們自動注冊,我們可以直接修改一些屬性琐脏。
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".TestActivity">
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
4. 嘗試通過點擊Button彈出Toast
在Activity的OnCreate()方法中實現(xiàn)Button的SetOnClickListener接口攒砖。
Button btn1 = findViewById(R.id.btn1);
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(TestActivity.this, "Click this button", Toast.LENGTH_SHORT).show();
}
});
2017 Google I/O大會之后使用Android Studio 3.0進(jìn)行開發(fā)findViewById可以不用強制類型轉(zhuǎn)換了。
5. 在Activity中使用Menu
在res中新建文件夾menu日裙,新建文件menu_main吹艇,設(shè)置兩個item。
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="add"/>
<item
android:id="@+id/remove_item"
android:title="remove"/>
</menu>
回到TestActivity中重寫onCreateOptionsMenu()方法昂拂。getMenuInflater()得到MenuInflater對象受神,再調(diào)用inflate()來創(chuàng)建菜單。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
重寫OnOptionsItemSelected()方法來處理菜單的Item的點擊邏輯格侯。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(TestActivity.this, "Click add item", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(TestActivity.this, "Click remove item", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
6. 銷毀一個Activity
修改btn1的1監(jiān)聽邏輯代碼鼻听,調(diào)用finish()方法即可銷毀當(dāng)前Activity。
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
三联四、使用Intent跳轉(zhuǎn)Activity
1. 顯式使用Intent
創(chuàng)建SecondActivity撑碴,修改TestActivity中btn1的代碼邏輯為:
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(TestActivity.this, SecondActivity.class);
startActivity(intent);
}
});
2. **隱式使用Intent **
修改AndroidManifest文件中SecondActivity的屬性。
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="com.kingdee.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
修改TestActivity中btn1的代碼邏輯為:
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent("com.kingdee.activitytest.ACTION_START");
startActivity(intent);
}
});
3. 向下一個Activity傳遞數(shù)據(jù)
Intent提供了一系列putExtra()方法的重載朝墩,事例中第一個參數(shù)為鍵醉拓,第二個參數(shù)為值,在TestActivity的跳轉(zhuǎn)邏輯中增加代碼:
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String data = "Hello SecondActivity";
Intent intent = new Intent(TestActivity.this, SecondActivity.class);
intent.putExtra("tag", data);
startActivity(intent);
}
});
在SecondActivity中通過getIntent()方法來獲取到啟動SecondActivity的Intent收苏,并通過getStringExtra()來獲取傳遞過來的數(shù)據(jù)(根據(jù)不同的參數(shù)類型)亿卤。在SecondActivity的OnCreate()方法中添加以下代碼:
Intent intent = getIntent();
String data = intent.getStringExtra("tag");
Toast.makeText(SecondActivity.this, data, Toast.LENGTH_LONG).show();
4. 返回數(shù)據(jù)給上一個Activity
Activity中有一個叫startActivityForResult()的方法,用于在該活動被銷毀的時候返回數(shù)據(jù)給上一個Activity鹿霸。修改TestActivity中btn1的邏輯,使用startActivityForResult()來啟動SecondActivity:
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// String data = "Hello SecondActivity"
Intent intent = new Intent(TestActivity.this, SecondActivity.class);
startActivityForResult(intent, 1);
// intent.putExtra("tag", data);
// startActivity(intent);
}
});
給SecondActivity注冊監(jiān)聽事件怠噪,并構(gòu)建一個Intent用于傳遞數(shù)據(jù)。調(diào)用setResult()方法來返回數(shù)據(jù)杜跷。
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.putExtra("return_tag", "Hero is coming back");
setResult(RESULT_OK, intent);
}
});
使用startActivityForResult()啟動SecondActivity被銷毀之后會調(diào)用上一個活動的onActivityResult()方法傍念,所以我們需要在TestActivity中重寫這個方法來的到返回的數(shù)據(jù)。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String returnData = data.getStringExtra("return_tag");
Toast.makeText(TestActivity.this, returnData, Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
onActivityResult()中第一個參數(shù)requestCode是啟動Activity時傳入的請求碼葛闷,第二個resultCode是返回數(shù)據(jù)時傳入的處理結(jié)果憋槐;第三個參數(shù)是攜帶數(shù)據(jù)的Intent。
??同理淑趾,我們也可以在SecondActivity中重寫onBackPressed()方法,通過點擊Back鍵來銷毀Activity并返回數(shù)據(jù)給上一個Activity阳仔。
@Override
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("return_tag", "Hero is coming back");
setResult(RESULT_OK, intent);
finish();
}
四、Activity的生命周期
1. 返回棧
Task其實就是是一個具有棧結(jié)構(gòu)的容器,可以放置多個Activity實例近范。啟動一個應(yīng)用嘶摊,系統(tǒng)就會為之創(chuàng)建一個task,來放置根Activity评矩;默認(rèn)情況下叶堆,一個Activity啟動另一個Activity時,(例如美團(tuán)外賣里面有個催單按鈕斥杜,點擊之后會調(diào)用并打開系統(tǒng)的撥號界面)虱颗,兩個Activity是放置在同一個task中的,后者被壓入前者所在的task棧蔗喂,當(dāng)用戶按下后退鍵忘渔,后者從task被彈出,前者又顯示在幕前缰儿,特別是啟動其他應(yīng)用中的Activity時畦粮,兩個Activity對用戶來說就好像是屬于同一個應(yīng)用;系統(tǒng)task和task之間是互相獨立的乖阵,當(dāng)我們運行一個應(yīng)用時锈玉,按下Home鍵回到主屏,啟動另一個應(yīng)用义起,這個過程中,之前的task被轉(zhuǎn)移到后臺师崎,新的task被轉(zhuǎn)移到前臺默终,其根Activity也會顯示到幕前,過了一會之后犁罩,在此按下Home鍵回到主屏齐蔽,再選擇之前的應(yīng)用,之前的task會被轉(zhuǎn)移到前臺床估,系統(tǒng)仍然保留著task內(nèi)的所有Activity實例含滴,而那個新的task會被轉(zhuǎn)移到后臺,如果這時用戶再做后退等動作丐巫,就是針對該task內(nèi)部進(jìn)行操作了谈况。
2. Activity的狀態(tài)
- 運行狀態(tài)
??當(dāng)一個Activity位于返回棧的棧頂時就處于運行狀態(tài)。系統(tǒng)為了用戶體驗不會去回收這樣的Activity递胧。 - 暫停狀態(tài)
??當(dāng)一個活動不再處于棧頂位置(不可交互)碑韵,但仍然可見時,這時活動就進(jìn)入了暫停狀態(tài)缎脾。系統(tǒng)也不愿意去回收這種活動(因為它還是可見的祝闻,回收可見的東西都會在用戶體驗方面有不好的影響),只有在內(nèi)存極低的情況下遗菠,系統(tǒng)才會去考慮回收這種活動联喘。 - 停止?fàn)顟B(tài)
??當(dāng)一個活動不再處于棧頂位置华蜒,并且完全不可見的時候,就進(jìn)入了停止?fàn)顟B(tài)豁遭。當(dāng)其他地方需要內(nèi)存時叭喜,處于停止?fàn)顟B(tài)的活動有可能會被系統(tǒng)回收。 - 銷毀狀態(tài)
??當(dāng)一個活動從返回棧中移除后就變成了銷毀狀態(tài)堤框。系統(tǒng)會會回收處于這種狀態(tài)的活動域滥,從而保證手機的內(nèi)存充足。
3. Activity的生命周期
4. Activity生命周期的實踐
新建一個NormalActivity和一個DialogActivity蜈抓,分別在布局文件中加入一個TextView启绰。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/normal_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="This is a normal activity" />
</LinearLayout>
activity_dialog.xml同理,秩序更改TextView的id和text沟使。然后在AndroidManifest中更改DialogActivity的標(biāo)簽委可。
<activity android:name=".DialogActivity"
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
在TestActivity的布局文件中增加兩個Button分別啟動這兩個Activity。
<Button
android:id="@+id/btn_normal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start NormalActivity" />
<Button
android:id="@+id/btn_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start DialogActivity" />
編寫TestActivity中兩個Button的邏輯代碼腊嗡,并重寫Activity的七個生命周期的方法着倾,使用Log.d在Logcat中顯示。
public class TestActivity extends AppCompatActivity {
public static final String TAG = "TestActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
... ...
Button normalBtn = findViewById(R.id.btn_normal);
normalBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(TestActivity.this, NormalActivity.class);
startActivity(intent);
}
});
Button dialogBtn = findViewById(R.id.btn_dialog);
dialogBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(TestActivity.this, DialogActivity.class);
startActivity(intent);
}
});
Log.d(TAG, "onCreate");
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
... ...
}
由Logcat的顯示可知燕少,當(dāng)TestActivity啟動時卡者,調(diào)用的是onCreate(),onStart()和onReasume()。
??點擊normalBtn后調(diào)用onPause()和onStop()客们。返回TestActivity調(diào)用的是onRestart(),onStart()和onResume()崇决。
??有一點需要關(guān)注的是,實際上TestActivity先執(zhí)行了onPause()方法底挫,然后NormalActivity再依次執(zhí)行onCreate()恒傻,onStart()和onReasume()方法,這樣是為了避免第二個的Activity開始工作時建邓,第一個Activity還處于工作狀態(tài)盈厘,造成混淆。而TestAcitivity的onStop()方法被最后執(zhí)行是因為擔(dān)心SecondActivity的啟動過程中途出錯而造成“閃退”現(xiàn)象官边,這時屏幕顯示還可以回到TestActivity的狀態(tài)沸手,防止兩個Activity被同時停止的尷尬。
??若點擊的是dialogBtn注簿,則TestActivity仍處于可見的狀態(tài)罐氨,所以只會調(diào)用onPause()不會調(diào)用onStop(),返回時也只會調(diào)用onResume()而不會調(diào)用onRestart()和onStart()滩援。
5. 解決Activity被回收時數(shù)據(jù)沒被保存的問題
Activity處于onStop()狀態(tài)時是有可能被系統(tǒng)回收栅隐,這時可以調(diào)用用onSaveInstanceState()攜帶的Bundle鍵值對來保存數(shù)據(jù)。在TestActivity中重寫這個方法。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "lose data";
outState.putString("data_key", tempData);
}
在onCreate()方法中帶有一個參數(shù)Bundle就是用于這個地方的租悄。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
... ...
}
這個方法只有在Activity被意外回收時才會調(diào)用(例如未處理過的Activity橫豎平切換)谨究,主動殺死Activity不會調(diào)用。
五泣棋、Activity的啟動模式
Activity的啟動模式有四種胶哲,在AndroidManifest文件中通過給<activity>標(biāo)簽指定android:launchMode屬性來選擇啟動模式。
standard
??默認(rèn)啟動模式潭辈,每次激活A(yù)ctivity時都會創(chuàng)建Activity鸯屿,并放入任務(wù)棧中,永遠(yuǎn)不會調(diào)用onNewIntent()把敢。singleTop
??如果在任務(wù)的棧頂正好存在該Activity的實例寄摆, 就重用該實例,并調(diào)用其onNewIntent()修赞,否者就會創(chuàng)建新的實例并放入棧頂(即使棧中已經(jīng)存在該Activity實例婶恼,只要不在棧頂,都會創(chuàng)建實例柏副,而不會調(diào)用onNewIntent()勾邦,此時就跟standard模式一樣)。singleTask
??如果在棧中已經(jīng)有該Activity的實例割择,就重用該實例(會調(diào)用實例的onNewIntent())眷篇。重用時,會讓該實例回到棧頂荔泳,因此在它上面的實例將會被移除棧蕉饼。如果棧中不存在該實例,將會創(chuàng)建新的實例放入棧中(此時不會調(diào)用onNewIntent())换可。singleInstance
??在一個新棧中創(chuàng)建該Activity實例,并讓多個應(yīng)用共享該棧中的該Activity實例厦幅。一旦該模式的Activity的實例存在于某個棧中沾鳄,任何應(yīng)用再激活改Activity時都會重用該棧中的實例,其效果相當(dāng)于多個應(yīng)用程序共享一個應(yīng)用确憨,不管誰激活該Activity都會進(jìn)入同一個應(yīng)用中译荞。
六、Activity實踐用法
1. BaseActivity
BaseActivity不需要再AndroidManifest文件中注冊休弃,我們可以利用基類BaseActivity來簡化代碼吞歼,減少重復(fù)或者實現(xiàn)一些功能,例如獲得所在類的名稱塔猾。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
}
讓BaseActivity繼承自AppcompatActivity篙骡,所有的Activity又繼承自BaseActivity,這樣每個Activity重寫的onCreate方法都會調(diào)用super.onCreate(),所有都會Log出自己的類名糯俗。
2. 殺死所有Activity尿褪,退出程序
新建一個ActivityCollector作為Activity的管理類。
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
// 將Activity添加到List中
public static void addActivity(Activity activity) {
activities.add(activity);
}
// 將Activity從List中移除
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
// 將List中的Activity全部銷毀
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
}
}
修改BaseActivity的代碼得湘,使得每次創(chuàng)建Activity時都能把activity添加到List中杖玲。
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
修改SecondActivity中btn2的邏輯,調(diào)用ActivityCollector.finishAll()可以達(dá)到直接退出程序的效果淘正。
Button btn2 = findViewById(R.id.btn2);
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCollector.finishAll();
}
});
這種做法有發(fā)生內(nèi)存泄露的隱患摆马,現(xiàn)在也比較少APP會有這種一鍵退出的功能。