續第二課( 下)
寫app必須掌握活動的生命週期。
[活動的生命週期]
[返回棧]
android每次啓動的活動會覆蓋在原活動之上,然後點擊Back鍵會銷燬最上層的活動。是使用Task來管理活動,一個任務就是一組存放在棧裏面的活動的集合,這個棧被稱爲返回棧。每當我們按下這個Back或者調用finish()方法去銷燬一個活動,處於棧頂的活動就會出棧。
[活動狀態]
- 運行狀態
一個活動位於返回棧棧頂的時候,就是運行狀態,也是系統最不願意回收的狀態的活動。
- 暫停狀態
當一個活動不再處於棧頂,但還是可見時,就進入了暫停狀態。如對話框。只有在內存極低的情況下,系統纔會回收這個活動
- 停止狀態
當一個活動不處於棧頂且不可見時就進入了停止狀態。系統有可能會回收。
- 銷燬狀態
當一個活動從返回棧中移除後就變成了銷燬狀態。系統回收這種狀態的活動,保證內存充足。
[活動的生存期]
Activity類中定義了7個回調方法,覆蓋了活動生命週期的每一個環節。
方法 | 簡介 |
---|---|
onCreate() | 每個活動,我們都重寫了這個方法,他會在活動第一次被創建的時候調用。可以用來完成活動的初始化操作。如加載佈局,綁定事件 |
onStart() | 在活動由不可見變爲可見時調用 |
onResume() | 在活動準備好與用戶進行交互的時候調用。此時的活動一定位於返回棧的棧頂,處於運行狀態。 |
onPause() | 這個方法在活動準備去啓動或者恢復另一個活動的時候調用。通常在這個方法中釋放一些消耗CPU的資源,以及保存一些關鍵數據,但這個方法的執行速度一定要快,否則會影響新棧頂活動的使用 |
onStop() | 這個方法在活動不可見的時候調用。它和onPause()區別:啓動的活動是一個對話框式的活動,onStop()不會執行。 |
onDestory() | 這個方法在活動被銷燬之前調用 |
onRestart() | 這個方法在活動由停止狀態變爲運行狀態之前調用 |
除了onRestart()方法外,都爲兩量對應關係
三種生存期:
生存期 | 簡介 |
---|---|
完整生存期 | 在onCreate()方法和onDestory()之間所經歷的就是完整生存期 |
可見生存期 | onStart()和onStop()方法之間所經歷的。 |
前臺生存期 | 在onResume()和onPause()之間經歷的 |
都是從加載資源到釋放資源,從而合理的管理資源。
[體驗活動的生命週期]
既然是體驗,我們還是重新new project吧,這次創建子活動勾選Launcher Activity,創建NormalActivity,DialogActivity.
編輯activity_normal.xml:
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="This is normal activity"
/>
</LinearLayout>
編輯activity_dialog.xml:
<LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text = "This is a dialog activity"/>
</LinearLayout>
從名字上可以看出,一個是normal活動,一個是dialog活動(對話框式),但是上面的代碼基本一樣啊。。。。我們需要去AndroidManifest.xml中修改:
<activity android:name=".DialogActivity"
android:theme="@android:style/Theme.Dialog">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
我們給它增加了android:theme的屬性。
現在去activity_main.xml中去增加button:
<Button
android:id="@+id/start_normal_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start normalActivity"
/>
<Button
android:id="@+id/start_dialog_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start dialogActivity"
/>
最後修改MainActivity中的代碼:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startNormalActivity = (Button) findViewById(R.id.start_normal_activity);
Button startDialogActivity = (Button) findViewById(R.id.start_dialog_activity);
startNormalActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, NormalActivity.class);
startActivity(intent);
}
});
startDialogActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, DialogActivity.class);
startActivity(intent);
}
});
}
@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, "onDestory");
}
@Override
protected void onRestart(){
super.onRestart();
Log.d(TAG, "onRestart");
}
}
跑路。順帶觀察一下logcat:
好了,你現在已經體驗了一遍完整的生命週期。
我碰到了一個問題,start_dialog_activity這個button運行時除了錯,原因是給dialog這個activity的theme裏面的屬性和DialogActivity繼承的一個AppCompatActivity類不兼容,所以只要把繼承的AppCompatActivity改爲Activity就可以了。
[活動被回收怎麼辦?]
當一個活動進入到了停止狀態,有可能被系統回收。這樣的話,返回之前的處於停止狀態的活動是可以的,只不過不會執行onRestart() 方法而是執行的onCreate()方法,也就是說這種情況下,返回之前 被回收的活動是會被重新創建的。
不過問題來了,這樣臨時的數據會丟失,不過問題不大。Activity中提供了onSaveInstanceState()回調方法,這個方法可以保證在活動被回收之前一定被調用,因此我們可以通過這個方法來保存臨時數據。
onSaveInstanceState()方法攜帶一個Bundle類型的參數,Bundle類提供了一些列方法保存數據,putString)(),putInt()等等,每個保存方法需要傳入兩個參數,第一個參數是鍵,用於取值,第二個是真正要保存的數據。和Intent的差不多。
MainActivity:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String tempData = "Something i just typed";
outState.putString("data_key",tempData);
}
保存完數據,但是該如何取出數據呢?
onCreate()方法中有一個Bundle類型的參數,就靠它
MainActivity:
Log.d(TAG, "onCreate");
if(savedInstanceState != null){
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG,tempData);
}
而且我們還可以將Bundle對象存放在Intent中,到了目標活動中再取出Bundle,再從Bundle中一一取出數據。
[活動的啓動模式]
啓動模式分爲四種:
- standard
- singleTop
- singleTask
- singleInstance
可以在androidManifest.xml中通過給標籤中指定android:launchMode屬性來選擇。
1.standard
活動默認的啓動模式。因此,我們之前所使用過的都是standard模式。每當啓動一個新的活動,它就會在返回棧中入棧,並處於棧頂的位置。對於standard模式下的活動,系統不會在乎這個活動是否已在返回棧中存在,每次啓動都會創建該活動的一個新的實例。
打開之前的ActivityTest項目。
修改FirstActivity:
public class FirstActivity extends AppCompatActivity {
private static final String Tag = "FirstActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.first_layout);
Log.d(Tag, this.toString());
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(FirstActivity.this , FirstActivity.class);
startActivity(intent);
}
});
Button button2 = (Button) findViewById(R.id.button_2);
button2.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
String data = "Hello huchi";
Intent intentString = new Intent(FirstActivity.this, SecondActivity.class);
intentString.putExtra("extra_data",data);
//startActivity(intentString);
startActivityForResult(intentString, 1);
}
});
Button button4 = (Button) findViewById(R.id.button_4);
button4.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intentSecond = new Intent("com.example.wrjjrw.activitytest.ACTION_START");
intentSecond.addCategory("com.example.wrjjrw.activitytest.MY_CATEGORY");
intentSecond.putExtra("extra_data","error");
startActivity(intentSecond);
}
});
Button button5 = (Button) findViewById(R.id.button_5);
button5.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intentToBaidu = new Intent(Intent.ACTION_VIEW);
intentToBaidu.setData(Uri.parse("http://blog.csdn.net/jaywrzz/article/details/65937639"));
startActivity(intentToBaidu);
}
});
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this, "You clicked Add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "You clicked Remove" ,Toast.LENGTH_SHORT).show();
break;
case R.id.huchi_item:
Toast.makeText(this, "I love huchi,too", Toast.LENGTH_LONG).show();
break;
default:
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if(resultCode == RESULT_OK){
String returnedData = data.getStringExtra("data_return");
Log.d("FirstActivity",returnedData);
}
break;
default:
}
}
}
我們看到他可以啓動他自己,跑路。
03-30 12:38:54.424 7273-7273/com.example.wrjjrw.activitytest D/FirstActivity: com.example.wrjjrw.activitytest.FirstActivity@42900c50
03-30 12:38:59.485 7273-7273/com.example.wrjjrw.activitytest D/FirstActivity: com.example.wrjjrw.activitytest.FirstActivity@4293a0c8
發現如果打開它自己三下,也就需要
2.singleTop
似乎standard不是很合理。singleTop在啓動活動時如果發現返回棧的棧頂已是該活動,則認爲可以直接使用它,不會再創建新的活動實例。
修改AndroidManifest.xml
<activity
android:name=".FirstActivity"
android:launchMode="singleTop"
android:label="This is huchi's FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
發現無論點多少次logcat都沒有打印信息了。
不過在FirstActivity並未處於棧頂時,這時再啓動FirstActivity,還是會創建新的實例的。
我們發現在FirstActivity和SecondActivity兩個活動中跳來跳去可以在logcat看到打印的消息。
3.singleTask
singleTask模式可以解決上述問題。每次啓動活動時,系統會在返回棧中檢查是否存在該活動的實例,如果發現已經存在則直接使用該實例,並把在這個活動上面的所有活動統統出棧,如果沒有發現就會創建一個新的活動實例。
修改FirstActivity:
@Override
protected void onRestart() {
super.onRestart();
Log.d(Tag, "onRestart");
}
修改SecondActivity,添加onDestroy():
public class SecondActivity extends AppCompatActivity {
private static final String Tag = "SecondActivity";
// @Override
// public void onBackPressed() {
// Intent intent = new Intent();
// intent.putExtra("data_return", "back_to_firstActivity");
// setResult(RESULT_OK, intent);
// finish();
// }
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intentFirst = new Intent(SecondActivity.this, FirstActivity.class);
startActivity(intentFirst);
}
});
Button buttonFinishSecond = (Button) findViewById(R.id.button_finish);
buttonFinishSecond.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intent = new Intent();
intent.putExtra("data_return","byebye huchi");
setResult(RESULT_OK, intent);
finish();
}
});
Intent intent = getIntent();
String data = intent.getStringExtra("extra_data");
Log.d("SecondActivity",data);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(Tag, "onDestroy");
}
}
跑路。觀察logcat
4.singleInstance
最特殊最複雜的模式。
會啓動一個新的返回棧來管理這個活動(如果singleTask模式下指定了不同的taskAffinity).singleInstance 可以實現其他程序和我們的程序共享一個活動的實例。在這種模式下會有一個單獨的返回棧來管理這個活動,不管是哪個應用程序來訪這個活動,都公用同一個返回棧。
實踐出真知:
修改AndroidManifest.xml:
<activity android:name=".SecondActivity"
android:launchMode="singleInstance">
<intent-filter>
<action android:name="com.example.wrjjrw.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.example.wrjjrw.activitytest.MY_CATEGORY" />
</intent-filter>
</activity>
FirstActivity:
Log.d(Tag, "Task id is " + getTaskId());
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(FirstActivity.this , SecondActivity.class);
startActivity(intent);
}
});
修改SecondActivity:
Log.d(Tag, "Task id is "+ getTaskId());
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intentFirst = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intentFirst);
}
});
activity_third:
<Button
android:id="@+id/button_to_first"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="return first"
></Button>
修改ThirdActivity
Log.d(Tag, "Task id is "+ getTaskId());
Button button3 = (Button) findViewById(R.id.button_3);
button3.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intentFirst = new Intent(SecondActivity.this, ThirdActivity.class);
startActivity(intentFirst);
}
});
我們發現從FirstActivity跳轉到SecondActivity,再跳轉到ThirdActivity後,此時按back鍵返回回到的就是FirstActivity。
[實踐出真知]
[知曉當前是哪一個活動?]
在集體編程時,可能會找不到界面對應的活動。
新建一個BaseActivity:
public class BaseActivity extends AppCompatActivity {
private static final String Tag = "BaseActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Tag, getClass().getSimpleName());
}
}
因爲BaseActivity繼承的是AppCompatActivity,我們可以讓其他三個活動繼承BaseActivity。也就是說其他活動onCreate的時候,就會執行父類的onCreate,也就都會執行Log.d(Tag, getClass().getSimpleName());
這句話,然後我們就知道哪個界面對應的是哪個活動了。
03-30 14:09:16.085 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: FirstActivity
03-30 14:09:19.440 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: SecondActivity
03-30 14:09:21.617 9459-9459/com.example.wrjjrw.activitytest D/BaseActivity: ThirdActivity
[隨時隨地退出程序]
只需要一個專門的集合類對所有的活動進行管理。
BaseActivity:
public class ActivityCollector {
public static List<Activity> activities = new ArrayList<>();
public static void addActivity(Activity activity){
activities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for(Activity activity: activities){
if(!activity.isFinishing())
activity.finish();
}
}
}
BaseActivity:
public class BaseActivity extends AppCompatActivity {
private static final String Tag = "BaseActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(Tag, getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
這樣就可以把存在的活動放在一個數組裏。
以後不管想在什麼時候地方退出程序,只要調用ActivityCollector.finishAll().
public static void finishAll(){
for(Activity activity: activities){
if(!activity.isFinishing())
activity.finish();
}
android.os.Process.killProcess(android.os.Process.myPid());
}
我進行了一番嘗試:
Button button10 = (Button) findViewById(R.id.button_to_finish);
button10.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
ActivityCollector.finishAll();
}
});
美好的。
[啓動活動的最佳寫法]
雖然我們寫的intent不管是語法上還是規範上都符合。但在對接的時候總是會有疑問???不清楚這個活動需要哪些數據。
修改SecondActivity:
public static void actionStart(Context context, String data1, String data2){
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("param1",data1);
intent.putExtra("param2",data2);
context.startActivity(intent);
}
修改FirstActivity:
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Toast.makeText(FirstActivity.this, "You click me", Toast.LENGTH_SHORT).show();
SecondActivity.actionStart(FirstActivity.this, "data1", "data2");
}
});
[問題]
問:爲啥我自動生成的都不是Linearlayout?
答:Click me,下一次學習筆記中將具體學習。
問:啥是taskAffinity?
答:Click me.