從活動一跳轉(zhuǎn)到活動二:
在跳轉(zhuǎn)中執(zhí)行完onPause之后馬上就執(zhí)行活動二的onCreate方法撞秋,等活動二的onResume執(zhí)行完后才執(zhí)行活動一的onStop方法,所以不能再onPause方法里執(zhí)行耗時操作
當(dāng)Activity異常終止的時候onSaveInstanceState和onRestoreInstanceState就會被調(diào)用嚣鄙,用來儲存和恢復(fù)數(shù)據(jù)吻贿,其他情況下不會觸發(fā)這個過程
- 在活動異常終止不可見時馬上調(diào)用儲存數(shù)據(jù)的方法,當(dāng)活動要運(yùn)行到前臺可見之前調(diào)用恢復(fù)數(shù)據(jù)的方法
- 每個View都有這兩個方法哑子,了解他們的具體實(shí)現(xiàn)后就知道系統(tǒng)能夠自動為每個view恢復(fù)哪些數(shù)據(jù)
- 注:onSaveInstanceState一定是在onStop方法前調(diào)用舅列,但和onPause方法沒有既定的時序關(guān)系,有可能在這個方法之前調(diào)用卧蜓,也有可能在這個方法之后調(diào)用
- 什么情況為異常終止:
- 資源相關(guān)的系統(tǒng)配置發(fā)生改變導(dǎo)致Activity被殺死并重新創(chuàng)建
例如從橫屏狀態(tài)突然帶豎屏狀態(tài) - 資源內(nèi)存不足導(dǎo)致優(yōu)先級低的Activity被殺死
測試數(shù)據(jù)儲存和恢復(fù)
測試代碼如下:
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);
//onCreate如果是正常啟動參數(shù)為null
if (savedInstanceState!=null){
String test=savedInstanceState.getString("extra_text");
Log.d(TAG, "onCreate恢復(fù)數(shù)據(jù):"+test);
}
Button button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent=new Intent(MainActivity.this,Main2Activity.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 onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, "onSaveInstanceState: ");
outState.putString("extra_text","text");
}
//onRestoreInstanceState一旦被調(diào)用其參數(shù)一定是有值的
@Override
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String test=savedInstanceState.getString("extra_text");
Log.d(TAG, "onRestoreInstanceState恢復(fù)數(shù)據(jù):"+test);
}
}
結(jié)果:
可以用Bundle傳輸數(shù)據(jù)
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 button=findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//用Bundle傳輸數(shù)據(jù)
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
Bundle bundle=new Bundle();
bundle.putString("name","Tom");
bundle.putInt("age",20);
intent.putExtras(bundle);
//用intent傳輸數(shù)據(jù)
intent.putExtra("nickname","lkl");
startActivity(intent);
}
});
public class Main2Activity extends AppCompatActivity {
private static final String TAG = "Main2Activity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//獲取數(shù)據(jù)
Intent intent1=getIntent();
//接收intent的傳輸
String nickName=intent1.getStringExtra("nickname");
Log.d(TAG, "onCreate別名[intent]):"+nickName);
//接收Bundle的傳輸
Bundle bundle=intent1.getExtras();
if (bundle!=null){
String name=bundle.getString("name");
int age=bundle.getInt("age");
Log.d(TAG, "onCreate名字[bundle]: "+name);
Log.d(TAG, "onCreate年齡[bundle]:"+age);
}
}
}
控制屏幕翻轉(zhuǎn)后活動不重新創(chuàng)建
1.在AndroidManifest.xml文件里配置
android:configChanges="orientation|screenSize"
如下:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
2.在活動里從寫onConfigurationChanged方法
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "onConfigurationChanged: 最新的位置"+newConfig.orientation);
}
這樣就不會再調(diào)用onSaveInstanceState和onRestoreInstanceState方法了
從圖中可以看出這個方法被調(diào)用了兩次帐要!Why?
1.當(dāng)推出鍵盤的時候,會觸發(fā)硬件的改變,使手機(jī)豎屏變成了橫屏
2.當(dāng)推進(jìn)鍵盤的時候,合上手機(jī)的一刻,觸發(fā)的是同一樣的固件,由于,固件是無法辨別那個是推進(jìn),和推出,發(fā)出的是一樣的信號,然后系統(tǒng)就會認(rèn)為這是一個橫屏改變,等到合上手機(jī)的時候系統(tǒng)再接受到一個信號,然后切換成豎屏.這樣系統(tǒng),認(rèn)為橫屏切換豎屏改變了兩次,這樣就導(dǎo)致調(diào)用了兩次onConfigurationChanged();
再加一個Button控件測試不同的啟動模式
在對應(yīng)的活動中添加:
android:launchMode="standard"
在活動中的代碼:
Button button2=findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent2=new Intent(MainActivity.this,MainActivity.class);
startActivity(intent2);
}
});
-
每點(diǎn)擊一次按鈕就會重新創(chuàng)建一個相同的活動
修改
android:launchMode="singleTop"
點(diǎn)擊了兩次,back了一次的執(zhí)行情況
補(bǔ)充說明任務(wù)棧
重要參數(shù):TaskAffinity(任務(wù)相關(guān)性)
- 每個 Activity 運(yùn)行時都有一個其歸屬的 task棧弥奸,我們可以用 activity.getTaskId() 的方法得到當(dāng)前 activity 的taskId榨惠。如果兩個 activity 的 taskId 不同,則他們肯定不會屬于同一個 task盛霎。
- Android 手機(jī)的任務(wù)列表就是根據(jù)不同 task 彈出的赠橙,我們可以根據(jù)任務(wù)管理器有幾個 item 圖標(biāo),來知道我們開啟了幾個 task愤炸。
- 指定活動2的棧測試使用方法:
<activity android:name=".Main2Activity"
android:taskAffinity="com.example.task1">
</activity>
- 如上圖所示简烤,taskaffinity 可以單獨(dú)對一個 activity 使用,代表該 activity 所想歸屬的 task摇幻;
也能對application 使用横侦,代表該 application 內(nèi)聲明的所有 activity 都?xì)w屬于這個task。這個屬性值必須和包名不同绰姻,否則就相當(dāng)于沒有指定 - 如果 activity 組件沒有聲明 taskAffinity 的話枉侧,該 activity 的 taskAffinity 屬性也是有默認(rèn)值的。如果 application 指定了 taskAffinity 值狂芋,默認(rèn)值就是 application 指定的 taskAffinity 值榨馁;如果 application 未指定的話,默認(rèn)值就是 manifest 中聲明的包名(package 對應(yīng)的字符串)
- 是不是我指定了一個 Activity 的 taskAffinity 值(跟包名不同)帜矾,運(yùn)行該 Activity 時翼虫,是否就會新開這個 task棧呢屑柔?答案是否定的,一個 Activity 運(yùn)行時所歸屬的task珍剑,默認(rèn)是啟動它 的那個Activity 所在的 task
在活動1和2分別打印棧的id
Log.d(TAG, "活動1的棧: "+this.getTaskId());
Log.d(TAG, "活動2的棧: "+this.getTaskId());
結(jié)果:
結(jié)論:
- taskAffinity 單獨(dú)使用并不會生效掸宛。
要想其生效,需要配合其他屬性使用招拙,或者配合 Intent.FLAG_ACTIVITY_NEW_TASK唧瘾,或者配合allowTaskReparenting 。使用時用其中的一個就行
測試:
在跳轉(zhuǎn)的intent中加入:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
總體主要代碼:
Intent intent=new Intent(MainActivity.this,Main2Activity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
結(jié)果:
完整測試及l(fā)og日志:
我們假定都是 Activity A 跳轉(zhuǎn)到 Activity B 中别凤,A沒有指定 taskAffinity 屬性,B 的launchMode 為standard饰序。
case1: A、B 屬同一App, intent 未指定 FLAG_ACTIVITY_NEW_TASK规哪,B 未指定 taskAffinity 屬性
D/MyApplication: onActivityResumed+MainActivity####taskid = 61
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 61
- 可以驗(yàn)證:一個Activity 歸屬的task 是由 啟動它的 Activity 所決定的求豫。
case2: A、B 屬同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK诉稍,B 指定 taskAffinity 屬性蝠嘉,但與包名相同
D/MyApplication: onActivityResumed+MainActivity####taskid = 62
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 62
- 可以驗(yàn)證,一個 Activity 的默認(rèn) task 值就是 manifest 定義的包名均唉。
case3: A、B 屬同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK肚菠,B 指定 taskAffinity 屬性舔箭,但與包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 63
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 63
- 可以驗(yàn)證:不指定 FLAG_ACTIVITY_NEW_TASK的話, 即使 taskAffinity 不同蚊逢,一個Activity 歸屬的task 仍然是由 啟動它的 Activity 所決定的层扶。
case4: A、B 屬同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK烙荷,B 未指定 taskAffinity 屬性
D/MyApplication: onActivityResumed+MainActivity####taskid = 64
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 64
- 可以驗(yàn)證:即使 使用了 FLAG_ACTIVITY_NEW_TASK镜会,但由于兩者的 taskAffinity 相同,所以仍然不會開啟一個新的task终抽。
case5: A戳表、B 屬同一App,intent 指定 FLAG_ACTIVITY_NEW_TASK,B 指定 taskAffinity 屬性昼伴,且和包名不同
D/MyApplication: onActivityResumed+MainActivity####taskid = 65
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 66
- 可以驗(yàn)證:開啟一個新task 的條件是 FLAG_ACTIVITY_NEW_TASK 和 taskAffinity 不同 缺一不可匾旭。
case6: A、B 屬同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK圃郊,B 未指定 taskAffinity 屬性价涝,B啟動模式為 singletask or singletop
D/MyApplication: onActivityResumed+MainActivity####taskid = 67
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 67
- 可以得出:未指定 FLAG_ACTIVITY_NEW_TASK 和 新的 taskAffinity 時,這兩種啟動模式對task 沒有影響
case7: A持舆、B 屬同一App,intent 未指定 FLAG_ACTIVITY_NEW_TASK色瘩,B 未指定 taskAffinity 屬性伪窖,B啟動模式為 singleinstance
D/MyApplication: onActivityResumed+MainActivity####taskid = 70
D/MyApplication: onActivityResumed+KeyboardActivity####taskid = 71
- 可以得出:
singleinstance 啟動模式本身就是會開啟一個新的task 裝載 這個Activity,且task 中只有這一個 Activity居兆。 - 經(jīng)判斷得知覆山,AMS 是先對 launchMode 做判斷 再處理 FLAG_ACTIVITY_NEW_TASK 的,如果是 singleinstance 史辙,則會直接開啟一個task汹买。
onNewIntent方法與啟動模式
前提:ActivityA已經(jīng)啟動過,處于當(dāng)前應(yīng)用的Activity任務(wù)棧中;
當(dāng)ActivityA的LaunchMode為Standard時:
由于每次啟動ActivityA都是啟動新的實(shí)例,和原來啟動的沒關(guān)系聊倔,所以不會調(diào)用原來ActivityA的onNewIntent方法
當(dāng)ActivityA的LaunchMode為SingleTop時:
如果ActivityA在棧頂,且現(xiàn)在要再啟動ActivityA晦毙,這時會調(diào)用onNewIntent()方法 ,生命周期順序?yàn)椋?/p>
onCreate--->onStart--->onResume---onPause>onNewIntent--->onResume
當(dāng)ActivityA的LaunchMode為SingleInstance,SingleTask:
如果ActivityA已經(jīng)在堆棧中耙蔑,那么此時會調(diào)用onNewIntent()方法见妒,生命周期調(diào)用順序?yàn)椋?/p>
onCreate--->onStart--->onResume---按下Home鍵>onPause--->onstop--->onNewIntent--->onRestart--->onstart--->onResume
測試代碼:
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent2=new Intent(MainActivity.this,MainActivity.class);
intent2.putExtra("music","hey kong");
startActivity(intent2);
}
});
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
String musicIs=getIntent().getStringExtra("music");
Log.d(TAG, "onNewIntent:1 "+musicIs);
}
注意:
有時候,我們在多次啟動同一個棧唯一模式下的activity時甸陌,在onNewIntent()里面的getIntent()得到的intent感覺都是第一次的那個數(shù)據(jù)须揣。對,這里就是這個陷阱钱豁。因?yàn)樗褪菚祷氐谝粋€intent的數(shù)據(jù)耻卡。就是這么坑。
原因就是我們沒有在onNewIntent()里面設(shè)置setIntent()牲尺,將最新的intent設(shè)置給這個activity實(shí)例卵酪。
加了setIntent():
沒加:
修改:
intent2.putExtra("music","hey kong2");
結(jié)果:
還是上一個intent結(jié)果,沒有改變
加了setIntent():