說起生命周期摩窃,基本上小白們都還是知道點,不過時間長了就忘了萄喳?為什么就容易忘了?事實上我們還是沒能真正的理解蹋半,沒能比較深入的理解他巨,I think.
基本上圖啥的都可以擺出來看看:
理解的第一步是,我們親自打印一遍日志看看:
搞兩個Activity减江,F(xiàn)irstActivity和SecondActivity染突, 我們在FirstActivity里面打印下相關(guān)日志信息, 這里我還列出了另外三個生命周期onWindowFocusChanged, onNewIntent, onRestoreInstanceState辈灼, 后面兩個和啟動模式有些關(guān)系喲份企。至于onWindowFocusChanged,小白再三年前首次做android項目的時候用到過(當時是給U3D做Android插件的時候巡莹,想要利用Android進行版本檢查的彈窗等操作司志,發(fā)現(xiàn)在onCreate或者onResume中都不行甜紫,
)。 這個時候真正的焦點可用就是onWindowFocusChanged中hasFocus為true的時候骂远。So, 某些時候不了解深點還不行囚霸。
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class FirstActivity extends AppCompatActivity {
private static final String Tag = FirstActivity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e(Tag, "onRestoreInstanceState");
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e(Tag, "onNewIntent");
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e(Tag, "onWindowFocusChanged");
}
@Override
protected void onStart() {
super.onStart();
Log.e(Tag, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.e(Tag, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.e(Tag, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.e(Tag, "onStop");
}
@Override
protected void onRestart() {
super.onRestart();
Log.e(Tag, "onRestart");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e(Tag, "onDestroy");
}
public void JumpToSecond(View view) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
}
我們分別進行 0.首次啟動 1. 跳轉(zhuǎn)到第二個頁面/回到桌面、2. 彈一個窗體激才、3. 退出拓型、4.從第二個頁面或者桌面返回、5.打開透明主題新頁面瘸恼。
Let's do it .
***第0種情況: ***首次啟動
E/FirstActivity: onCreate
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onWindowFocusChanged hasFocus=true
onCreate里面可以做一些布局初始化劣挫,界面初始化,但是不要做太多的事情东帅。
onResume一般不會做什么事情压固,但是如果某些頁面要求刷新頻率比較高(比如每次回到該頁面都需要刷新一次,可以在這個頁面進行刷新操作(比如進行網(wǎng)絡(luò)請求刷新數(shù)據(jù)來刷新)冰啃,當然很多時候我們通過發(fā)送廣播或者Eventbus發(fā)送廣播來通知該頁面刷新邓夕。
關(guān)于onResume,小白目前的項目中阎毅,個人中心頁面涉及到算力焚刚,涉及到積分,涉及到獎勵信息等扇调,所以每次都是在onResume中進行了刷新矿咕,也沒搞什么定時器啥的,當然或許有些需要搞定時器或者別的做實時刷新狼钮, 比如直播的聊天室等碳柱。
onWindowFocusChanged了,小白目前沒有用到熬芜。不過早期的U3D的Android插件的項目中用到了(當時的那種項目交叉的情況下如果在onCreate中版本檢查莲镣,會出現(xiàn)is activity ruanning的錯誤),當時是用來做版本檢查和更新涎拉, 大概邏輯是:
private boolean bHasChecked = false;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e(Tag, "onWindowFocusChanged hasFocus=" + hasFocus);
if (hasFocus && !bHasChecked){
bHasChecked = true;
///< chekcVersion操作
}
}
首先是頁面處于首次啟動且可見的情況下進行版本檢查瑞侮?因為如果你從其他頁面回到該頁面,也是會獲取焦點鼓拧,所以我們不能每次回到該頁面都進行版本檢查半火。你懂的!關(guān)于onWindowFocusChanged有相關(guān)官網(wǎng)可以看 Activity | Android Developers
大概就是說焦點的失去和獲取時調(diào)用季俩,當焦點失去后不要進行什么交互操作钮糖。如果在該頁面彈窗,該頁面將會失去焦點酌住。而如果是系統(tǒng)級的頂層彈窗店归,比如狀態(tài)欄通知或者系統(tǒng)警告提示等都將會暫時接管輸入焦點阎抒,但是不會暫停前臺頁面。一會可以驗證下彈窗的情況娱节。
另外以前小白首次使用Android在onCreate中想獲取控件的寬高(直接調(diào)用getWidth()發(fā)現(xiàn)為0挠蛉,然后又換了別的獲取控件寬高的方法,像View.post中獲纫蘼)谴古。其實還有一個地方就是這個里面肯定也是可以的,當焦點一旦獲取就可以拿到, 下面就驗證下獲取寬高的情況(onResume算是較晚的周期了吧..那就在onResume中試試):
activity_first.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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.example.hl.FirstActivity">
<Button
android:id="@+id/af_jupmBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳轉(zhuǎn)到第二個Activity"
android:onClick="JumpToSecond"/>
</android.support.constraint.ConstraintLayout>
看結(jié)果賽:
E/FirstActivity: onCreate
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onResume width=0
E/FirstActivity: onResume height=0
E/FirstActivity: onWindowFocusChanged hasFocus=true
E/FirstActivity: onWindowFocusChanged width=443
E/FirstActivity: onWindowFocusChanged height=126
最后來看**onStart. **這是一個基本被忽視的生命周期回調(diào),官方基本好像也沒說啥
簡單說了下在onCreate之后稠歉,或者onRestart之后掰担。onSart之后都會跟隨onResume.多數(shù)時候我們可以這樣認為,onStart對應(yīng)onStop, onResume對應(yīng)onPause,然后再聯(lián)想下周期圖怒炸,拔過來的:
Android可能就是這樣設(shè)計的吧带饱,雖然onStart很少涉及,但是還是要有所了解的吧....
***第1種情況: ***跳轉(zhuǎn)到第二個頁面/回到桌面
E/FirstActivity: onPause
E/FirstActivity: onWindowFocusChanged hasFocus=false
E/FirstActivity: onStop
可以發(fā)現(xiàn)onPause(暫停后就無法響應(yīng)交互了...也就是失去了焦點了)阅羹、onStop按順序調(diào)用勺疼。而onWindowFocusChanged是onPause之后調(diào)用,可以參考網(wǎng)友簡單分析 Activity之onWindowFocusChanged - Matrix_Ran - 博客園
你可以在這種情況你可以在onPause中做一些暫停操作捏鱼,或者onStop中做执庐。這個得看具體需求。比如你彈窗如果為了不影響播放导梆,那就是onStop中轨淌,因為彈窗可能也會導致頁面走onPause方法。
***第2種情況: ***彈一個窗體
AlertDialog.Builder
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("test");
builder.setMessage("test");
builder.create().show();
}
彈出和消失看尼,只走了焦點回調(diào)...并沒有onPause...
什么情況下走onPause呢傀履?
只有包含dialog或者dialog樣式的activity彈窗時才會走onPause方法当犯。
而如果是PopuWindow呢狂鞋?
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("test");
// builder.setMessage("test");
// builder.create().show();
View contentView = LayoutInflater.from(this).inflate(R.layout.activity_second, null);
PopupWindow popWnd = new PopupWindow (this);
popWnd.setContentView(contentView);
popWnd.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
popWnd.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
popWnd.showAsDropDown(view);
}
什么回調(diào)也不會走菇存,焦點回調(diào)也不走?
然后小白發(fā)現(xiàn)是因為沒有設(shè)置popuwindow的focuse狰域。
public void JumpToSecond(View view) {
// Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
// startActivity(intent);
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("test");
// builder.setMessage("test");
// builder.create().show();
View contentView = LayoutInflater.from(this).inflate(R.layout.activity_second, null);
PopupWindow popWnd = new PopupWindow (this);
popWnd.setContentView(contentView);
popWnd.setBackgroundDrawable(new ColorDrawable(0xffeeeeff));
popWnd.setOutsideTouchable(true);
popWnd.setFocusable(true);
popWnd.setWidth(200);
popWnd.setHeight(200);
popWnd.showAsDropDown(view);
}
這下就可以了窜觉。。北专。原來如此!哇咔咔...
彈alertdialog樣式的activity自然就是正常的周期了啦.....這個自己也可以試試.
***第3種情況: ***退出
***第4種情況: ***退出.從第二個頁面或者桌面返回
E/FirstActivity: onRestart
E/FirstActivity: onStart
E/FirstActivity: onResume
E/FirstActivity: onWindowFocusChanged hasFocus=true
***第5種情況: ***打開透明主題新頁面
設(shè)置透明主題先:
<!--繼承Theme.AppCompat.NoActionBar旬陡,不顯示標題欄-->
<style name="TransparentTheme" parent="Theme.AppCompat.NoActionBar">
<!--不設(shè)置activity進入和退出動畫樣式-->
<item name="android:windowAnimationStyle">@null</item>
<!--設(shè)置窗口的背景為透明拓颓,設(shè)置透明背景必須要設(shè)置此項-->
<item name="android:windowBackground">@android:color/transparent</item>
<!--設(shè)置窗口的背景是否為半透明,設(shè)置透明背景必須要設(shè)置此項-->
<item name="android:windowIsTranslucent">true</item>
<!--設(shè)置狀態(tài)欄的背景為半透明-->
<item name="android:windowTranslucentStatus" tools:ignore="NewApi">true</item>
</style>
使用一下:
<activity
android:name="com.example.hl.SecondActivity"
android:theme="@style/TransparentTheme"></activity>
還是這個時候onStop就不會走了描孟,我們暫停就行驶睦,因為下層頁面實際上是可見的砰左,也就符合了生命周期的特征。哇哦!
第o種情況:
當前頁面顯示時场航,息屏缠导,亮屏
*打開透明頁面后息屏,亮屏 *- 打開透明主題只是走了onPause, 息屏后再走onStop, 恢復(fù)走下(onRestart, onStart溉痢,這個地方并沒有走onResume. 因為當前頁面還是處在被其他頁面覆蓋的情況僻造,只不過該頁面透明罷了) - 這個需要留意下。
小結(jié)一下: Android開發(fā)藝術(shù)探索孩饼,里面有關(guān)于onStart髓削、onStop , onResume、onPause的闡述:
從上面我們打印的一系列日志也可以看出镀娶。被覆蓋立膛,再回來,就是onResume, onPause的配合梯码, 是否在前臺宝泵!可見性就是看不見了,被遮擋了也好轩娶,返回桌面了也好儿奶。 正常頁面的跳轉(zhuǎn)頁面,返回的操作基本這四個配合使用:
而我們從生命周期圖中可以看到中間有個部分是:
這種就是其中提到的跳轉(zhuǎn)新的透明主題的頁面的情況(那種情況下onStop是不會觸發(fā)的)罢坝。 這樣簡單過一遍下來廓握,我們就大概知道了每個流程的情況,周期回調(diào)嘁酿。
還有兩個onNewIntent隙券, onRestoreInstanceState,我們也簡單了解下闹司,因為有可能你在做某些啟動方式娱仔,或者崩潰恢復(fù)某些數(shù)據(jù)等的時候會用到。
**onNewIntent - **當任務(wù)棧頂已經(jīng)存在該頁面游桩,當再次啟動該頁面時牲迫,不會再走onCreate方法,而是走onNewIntent方法(此時你可以在這個地方進行這個view視圖的更新操作)
前提是啟動模式設(shè)置為singleTask
此時可以設(shè)置跳轉(zhuǎn)參數(shù)
public void JumpToSecond(View view) {
Intent intent = new Intent(FirstActivity.this, FirstActivity.class);
intent.putExtra("name", "皮皮蝦");
startActivity(intent);
}
然后走onNewIntent的時候獲取 - 進而實現(xiàn)界面復(fù)用借卧,視圖更新的目的
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.e(Tag, "onNewIntent" + intent.getStringExtra("name"));
}
這種有什用途呢盹憎? 我們?yōu)榱瞬蛔岉撁婷看沃匦聞?chuàng)建,實現(xiàn)頁面復(fù)用铐刘,視圖動態(tài)更新的效果陪每。小白想到了自己做的項目的一個案例(默認的standard啟動模式),就是新聞資訊列表,點擊列表中某個新聞后會進入詳情頁面A檩禾,詳情頁面底部有推薦文章和熱門文章挂签,此時再點擊推薦文件或熱門文章,再次啟動資訊詳情頁面A盼产,如果一直點下去饵婆,可能會產(chǎn)生10來個,甚至更多的詳情頁面戏售。此時侨核,如果想要返回主頁面,難道我們一個個的退出蜈项?
這個時候就可以設(shè)置singletask啟動模式芹关,復(fù)用界面,將數(shù)據(jù)更新放到onNewInstance中請求一次紧卒,當不停瀏覽詳情退出后就直接退出該頁面了侥衬,棒棒噠!有沒有跑芳≈嶙埽或許產(chǎn)品就是這樣的需求...
而關(guān)于onSaveInstanceState(之前的生命周期忘了打印了,不過沒有關(guān)系)....和onRestoreInstanceState....
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
Log.e(Tag, "onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.e(Tag, "onRestoreInstanceState");
}
當某個activity變得“容易”被系統(tǒng)銷毀時博个,該activity的onSaveInstanceState就會被執(zhí)行怀樟,而當再次恢復(fù)時就會走onRestoreInstanceState。此時可以分別進行數(shù)據(jù)的存儲和恢復(fù)操作盆佣。比如備忘錄往堡,記事本,當然這些應(yīng)用可能是實時存儲共耍,所以不用擔心數(shù)據(jù)丟失虑灰,至少本地是不會丟失的。
正常情況不會走痹兜,當出現(xiàn)異常穆咐,崩潰什么的就會觸發(fā)。模擬器還可以進行這個設(shè)置 測試 onSaveInstanceState(Bundle)方法 干得漂亮字旭。
but对湃,小白測試自己的模擬器,沒有調(diào)用遗淳,呵呵
這個后面看源碼的時候再看吧拍柒。。屈暗。
另外補充一下拆讯,小白實際項目中的fragment的存儲和恢復(fù)記錄一下:HomeActivity中多個fragment的tag的保存剧包,以及崩潰重啟后進行恢復(fù)的操作。
FragmentTransaction的add方法有一個參數(shù)用于存儲碎片標記....
然后就可以進行恢復(fù)
/**
* 獲取崩潰保存的碎片
* @param savedInstanceState
*/
private void getSavedInstanceState(Bundle savedInstanceState){
if (null != savedInstanceState){
newsFragment = getSupportFragmentManager().findFragmentByTag("news");
flashFragment = getSupportFragmentManager().findFragmentByTag("flash");
mineFragment = getSupportFragmentManager().findFragmentByTag("mine");
tgHeziFragment = getSupportFragmentManager().findFragmentByTag("tghezi");
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
///< 崩潰重啟往果,隱藏fragment,重新運行之前的主頁面的顯示邏輯一铅;否則會發(fā)現(xiàn)碎片重疊...
if (null != newsFragment &&
newsFragment.isAdded() &&
!newsFragment.isHidden()){
transaction.hide(newsFragment);
transaction.commit();
}
if (null != mineFragment &&
mineFragment.isAdded() &&
!mineFragment.isHidden()){
transaction.hide(mineFragment);
transaction.commit();
}
if (null != flashFragment &&
flashFragment.isAdded() &&
!flashFragment.isHidden()){
transaction.hide(flashFragment);
transaction.commit();
}
if (null != tgHeziFragment &&
tgHeziFragment.isAdded() &&
!tgHeziFragment.isHidden()){
transaction.hide(tgHeziFragment);
transaction.commit();
}
Log.e("home", "infofrag=" + newsFragment);
Log.e("home", "infofrag=" + flashFragment);
Log.e("home", "mineFragment=" + mineFragment);
Log.e("home", "tgHeziFragment=" + tgHeziFragment);
}
}
以上可以解決崩潰重啟后花屏的問題陕贮?因為如果你只是單純的崩潰重啟,而不處理隨便隱藏邏輯潘飘,你會發(fā)現(xiàn)重啟后界面會重疊花屏肮之,因為碎片恢復(fù)后產(chǎn)生錯了亂,你并沒有恢復(fù)之前的顯示狀態(tài)卜录。
所以處理是關(guān)鍵:
關(guān)于捕獲異常重啟戈擒,另外一篇簡單記錄下吧。網(wǎng)上也很多類似的艰毒。也可以看下官方文檔....對了筐高,我是在onCreate中調(diào)用的getSavedInstanceState方法,實際上了解了上面的知識丑瞧,可以放到onRestoreInstanceState中柑土,那也就可以不用判斷savedInstanceState是否為空了~!啊哈哈...
Last, 這個周期這個地方簡單先記錄到這里绊汹。小白時覺得稽屏,要理解一個東西,除了場景的實踐西乖,可能還需要一些項目實戰(zhàn)狐榔,那樣印象和理解更深些。我也是再想获雕,如何能更好的理解并記憶這些周期薄腻,這些方法,目前這一流程走一下典鸡,感覺好多了被廓。后面準備開源碼的專欄的了,要開始源碼級的學習了萝玷。嫁乘。。球碉。
還是那句蜓斧,懶人多做事,開心快樂睁冬,早點休息挎春,早點起床看疙,一家人開開心心比什么都重要! - 黃磊