探究Activity的各回調方法

剛畢業那會兒,一心想朝着java web的方向進軍,卻豈料實習的時候陰差陽錯地踏入了Android的大門,自此人生跌宕起伏、坎坎坷坷,在一家外企參與了幾個須要越過GFW才能使用的有關於體育賽事的項目,以後跳了個槽跟另一個哥們作了好幾個沒見到陽光的充斥着濃濃果味兒的App。無數的努力付之東流會讓一我的厭倦某一件事情,因此我要開始寫寫能讓本身看懂的文章,藉此找點樂子固然也是爲了記錄記錄本身的學習歷程。java

探究activity的各回調方法以前,首先插入一張官方的生命週期圖,而後用適合本身的語言記錄下簡化的生命週期中各回調方法的涵義。android

 

onCreate()

通常人認爲的activity的入口(然而不是),當activity第一次created以後會回調這個方法。若是把activity比做房子的話,回調這個方法以前activity還只是一個毛坯房,咱們要在這個方法裏邊對它進行裝修,這樣它隨後顯示的效果就跟咱們所預想的同樣了。web

onstart() : 

這個時候咱們能夠看到activity了。編程

onResume() : 

這個時候咱們能夠跟activity進行交互了。api

onPause() : 

這個時候咱們仍是能看到activity,可是不能進行交互了。舉個栗子,假設咱們當前的activity爲A,有一個啓動activity B的意圖,這個時候A會回調onPause(),當A的onPause()回調完成以後,B開始onCreate()->onStart()->onResume()...完了以後B就處於可見可交互的狀態了。那麼A呢?若是此時A看不見了,A就會回調onStop()方法;另外一種狀況是此時A仍是部分可見的(好比Activity B的主題是@android:style/Theme.Dialog),A就不會回調onStop();  從上面的分析能夠知道,onPause()方法裏面不容許作耗時的操做,否則B等了半天都啓動不了。app

有一點須要注意的是,不是說A處於部分可見可是不可交互的狀態就必定會回調onPause()的,不信你show一個Dialog,show一個DialogFragment,或者show一個設置焦點爲true的PopupWindow試試看。框架

onStop():

這個時候activity已經一丁點兒都看不到了。ide

onDestroy():

activity的臨終遺言就在這裏面寫了,由於回調完它就被摧毀了。activity被摧毀有兩種狀況,一是someone調用了finish()方法,二是系統要節省內存空間而臨時幹掉它. 如何區分呢?官方文檔裏面說的是經過isFinishing()這個方法來判斷。 若是是someone調用了finish()方法,isFinishing()毫無疑問是return true的。若是是系統爲了節省空間,isFinishing()=false?佈局

onRestart():

activity準備從新出來見人了。典型的栗子是啓動一個能徹底遮擋住前一個activity的新的activity以後,再按back鍵返回到前一個actvity,這樣前一個activity就會onRestart()->onStart()->onResume();另外一個栗子是按下電源鍵,熄滅屏幕,再打開,點亮屏幕的時候;還有一個是按下home鍵,再從最近任務欄或者點擊應用圖標從新進去的時候....學習

 


 

到此爲止,簡化的activity生命週期就大體掌握了,可是將近七千行的activity源碼可不止這幾個回調方法。固然,咱們沒必要要去追究裏面每一個方法每一個變量的意義與做用,我以爲那樣是一種浪費時間的表現,還不如去多看幾個用得上的api或者研究下目前一些流行的開源框架怎麼使用(好吧,其實是老子看不懂那一堆fucking source code~~),話雖如此,一些有用或者有意思的回調方法咱們仍是須要了解了解的,要否則怎麼提高本身的編程逼格呢!!!!!!!!!!

onApplyThemeResource():

通常來講在AndroidManifest.xml的application標籤下會全局設置一個theme屬性,或者單獨爲每一個activity設置也能夠,這樣onApplyThemeResource()方法會先於onCreate()調用,固然你如果不在AndroidManifest.xml設置,硬是單單在onCreate()裏面調用setTheme()方法也是能夠的,這樣onApplyThemeResource()就會在onCreate()後調用了。

這個方法顧名思義就是activity應用主題資源的,固然並非說activity直接就調用onApplyThemeResource()了,咱們能夠稍微追蹤下它的調用路線。

咱們知道要啓動一個activity,會調用ActivityThread的performLaunchActivity()方法來建立這個activity,下面咱們點進去找到關於設置主題的幾行代碼。

private Activity performLaunchActivity(......) {
 ......
     if (activity != null) {
         Context appContext = createBaseContextForActivity(r, activity);
          ......
         activity.attach(appContext, this,......);
         ......
         int theme = r.activityInfo.getThemeResource();
         //若是在AndroidManifest.xml裏面設置了有效的theme屬性,則調用setTheme()
         if (theme != 0) {
             activity.setTheme(theme);
         }
     }
}   

而後去activity的setTheme()裏面看看,看以前先了解下activity的繼承關係,以下圖。activity是直接繼承自ContextThemeWrapper類的,因此才具備了變換主題的能力,實際上activity的setTheme()方法便是ContextThemeWrapper的setTheme()方法。

點進去ContextThemeWrapper的setTheme()方法:

public void setTheme(int resid) {
    if (mThemeResource != resid) {
        mThemeResource = resid;
        initializeTheme();
    }
}

而後initializeTheme():

private void initializeTheme() {
     final boolean first = mTheme == null;
     if (first) {
         mTheme = getResources().newTheme();
         Resources.Theme theme = getBaseContext().getTheme();
         if (theme != null) {
             mTheme.setTo(theme);
         }
     }
     //找到目標
     onApplyThemeResource(mTheme, mThemeResource, first);
}

以上用語言來表示就是,activity在建立的時候,若是有在AndroidManifest.xml裏面設置有效的主題資源id,就會在onCreate()以前調用setTheme()方法,而後調用initializeTheme()方法,進而回調onApplyThemeResource()方法;其實前面看ActivityThread的performLaunchActivity()方法的時候,除了關於設置主題的代碼外,上面還多了幾行代碼,爲何要把它提出來呢,由於initializeTheme()方法有個getBaseContext()的方法,那多出來的幾行代碼就是爲了此刻來講明下的。看看Contextwrapper類的源碼,發現它有一個名爲mBase的Context類型的成員變量,這個mBase變量委託Contextwrapper類來調用本身的全部方法,大概就是下面這樣:

public class ContextWrapper extends Context {
    Context mBase;
    ......
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    
    public Context getBaseContext() {
        return mBase;
    }

    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources()
    {
        return mBase.getResources();
    }
    ......
} 

因此initializeTheme()方法調用的getBaseContext()方法取的就是這個名爲mBase的Context類型的成員變量,可是實際上Context類是一個抽象類,裏面根本沒有任何實現,這個時候就要從新回到上面的performLaunchActivity(),看看那多出來的兩行代碼到底幹了什麼。

首先是createBaseContextForActivity()方法,只貼重要的代碼,其它的不想看:

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
     ......
     ContextImpl appContext = ContextImpl.createActivityContext(
             this, r.packageInfo, displayId, r.overrideConfig);
     appContext.setOuterContext(activity);
     Context baseContext = appContext;
     ......
     return baseContext;
} 

而後是Activity的attch()方法,就不貼代碼了,點進去發現調用了attachBaseContext()方法,對了,實則調用的就是上面ContextWrapper類的attachBaseContext(),由此,分析得知,activity在建立的時候經過createBaseContextForActivity()獲得一個Context的實現類ContextImpl類的實例,而後在attch()方法裏面經過調用attachBaseContext()方法讓ContextWrapper類的mBase成員變量指向這個實例.

綜上所述,前面initializeTheme()方法裏面的getBaseContext()就說得通了,它實際獲得的是一個ContextImpl類的實例,Activity第一次設置主題的時候,ContextImpl類的getTheme()方法會根據你的targetSdkVersion版本返回一個對應的默認的主題對象。之後有時間再去看看ContextImpl類,如今先跳過這個實際開發沒什麼用的onApplyThemeResource()方法。

onContentChanged():

當屏幕的內容視圖發生改變時會調用,官方文檔上的這句話反正我是看不大明白,只能看看源碼了。這裏以AppCompatActivity爲例,發現AppCompatActivity的setContentView()或者addContentView()方法都是經過AppCompatDelegate來委託調用的,因而咱們找到它的實現類AppCompatDelegateImplV7,而後把這兩個方法貼出來以下:

......
final Window.Callback mOriginalWindowCallback;
......
    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
......

因此,Activity在onCreate()裏邊調用setContentView()方法或者addContentView()方法的時候,都會緊接着回調onContentChanged()方法,上面代碼mOriginalWindowCallback變量指向的就是委託的Activity對象。根據上述分析,onContentChanged()方法何時會回調呢?就是當Activity的根視圖中id爲android.R.id.content的ViewGroup的子View發生改變時,這種改變指的是子View的替換。這樣來看的話,咱們在onCreate()裏邊就不用寫個initView()的方法來findViewById()了,直接把這些操做丟在onContentChanged()方法裏邊,感受吊吊的。遺憾的是,有了butterknife、AndroidAnnotations、Dagger亦或是其它的註解框架,誰還會用findViewById()?

固然,也能夠直接看看Activity裏邊的setContentView()方法,而後看看PhoneWindow類裏邊的setContentView()方法,原理是同樣的。那麼這個id爲android.R.id.content的ViewGroup究竟是什麼呢?之後有時間的話要單獨記錄下有關DecorView的知識😄。

onPostCreate():

照例把官方文檔翻譯一下,當activity已經徹底啓動的時候會回調這個方法,系統會在裏邊作一些最終的初始化,咱們的應用程序一般不用重寫這個方法。 嗯,就這樣,可是總感受少了點什麼,去ActivityThread類的performLaunchActivity()方法裏面看看:

......
/**
 *activity經過這個mCalled變量來判斷你activity的回調方法有木有調用父類對應的方法,
 *若是沒有會拋SuperNotCalledException異常,原理很是簡單,先把mCalled置爲false,
 *而後在父類的方法裏面再置爲true,由此判斷...
 */
activity.mCalled = false;
//調用onCreate()...
if (r.isPersistable()) {
    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
}else {
    mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
    throw new SuperNotCalledException(
          "Activity " + r.intent.getComponent().toShortString() +
              " did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
/**
  *記得前面說的isFinishing()方法嗎,實際上它返回的就是這個mFinished變量,someone  
  *調用finish()時候會把mfinished置爲true...so理論上,activity啓動的時候光速般按下返 
  *回鍵,這樣onStart()就不會調了,固然,這只是理論。onStart()調完以後,將stopped置
  *爲false,說明activity已經不在後臺了。
  */
if (!r.activity.mFinished) {
    activity.performStart();
    r.stopped = false;
}
//略過...
if (!r.activity.mFinished) {
    if (r.isPersistable()) {
        if (r.state != null || r.persistentState != null) {
            mInstrumentation.callActivityOnRestoreInstanceState(activity,r.state, r.persistentState);
        }
    }else if (r.state != null) {
        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
    }
}
//這裏就是說要去調onPostCreate()了...
if (!r.activity.mFinished) {
    activity.mCalled = false;
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
    } else {
        mInstrumentation.callActivityOnPostCreate(activity, r.state);
    }
    if (!activity.mCalled) {
        throw new SuperNotCalledException(
             "Activity " + r.intent.getComponent().toShortString() +
                    " did not call through to super.onPostCreate()");
    }
  }
}
r.paused = true;
......

看到這,我忽然發現無論是onPostCreate()仍是onPostResume(),Activity的默認實現操做都是跟標題欄有關,或者說跟ActionBar有關,我以爲這不是一個巧合,你看ActionBarDrawerToggle也是。因此,我以爲當設備狀態變化時,好比橫豎屏轉換的時候,若是咱們沒有在AndroidManifest.xml配置configChanges屬性,當Activity從新回調各生命週期方法的時候,咱們在這兩個方法裏面同步ActionBar的狀態是否會比較符合官方的思想。

onUserInteraction()、onUserLeaveHint():

兩個長的很像的方法,onUserInteraction()比較好理解,只要用戶與Activity有交互就會調用,好比說按了個鍵、觸了個屏、滾了個軌跡球...專業來說就是隻要有事件分發給Activity的時候就會首先調用onUserInteraction(),因此你去看Activity的源碼能夠發現,在dispatchXXXEvent()的方法體裏面,首先就是調onUserInteraction()。哦,除了dispatchPopulateAccessibilityEvent(),這個好像是android系統設置裏面有個什麼輔助功能相關的交互吧。

onUserLeaveHint(),由於用戶的選擇從而讓當前的Activity進入後臺的時候就會回調這個方法,必定要注意,"用戶的選擇"和"進入後臺"。好比,在當前Acitivity按下home鍵會回調onUserLeaveHint()方法;啓動一個新的Activity(包括Dialog或者透明風格的Activity),前一個Activity會回調onUserLeaveHint()方法,其實這個狀況這種說法不徹底正確啊 ,若是你前一個Activity在startActivity()以前先調用了finish()方法,onUserLeaveHint()是不會調用的,由於此時前一個Activity就不只僅是進入後臺了,而是要被摧毀了...固然若是你的finish()方法寫在startActivity()以後的話,仍是會調用onUserLeaveHint()的;第三種狀況,由於系統的調用而讓你的Activity進入後臺是不會走onUserLeaveHint()的,好比忽然一個電話打進來的時候。以上三種狀況的話特別要注意第二種,最後記錄一句,倘若onUserLeaveHint()要回調的話是在onPause()以前的。

分析了這麼多,那onUserLeaveHint()能作什麼呢,捕獲home點擊事件?那啓動一個新的Activity也會被調用啊,這個時候咱們就要問了,系統是怎麼判斷當前的Activity進入後臺是否是用戶的選擇呢,實際上,Intent有個FLAG叫作FLAG_ACTIVITY_NO_USER_ACTION。

因此啊,前一個Activity調用startActivity(Intent intent)啓動一個新的Activity的時候,若是intent設置了這個FLAG,前一個Activity的onUserLeaveHint()方法就會被阻止調用了,由於這表明不是USER的ACTION. 到此,咱們就知道了,想要大體捕獲home點擊事件,應用內的Activity跳轉的時候加上這個FLAG就OK了。 那咱們能在這個方法裏面作什麼呢,典型的,發送一個Notification告訴用戶,你的app如今跑在後臺...

dispatchXXXEvent():

事件分發進Activity時被調用,這個方法裏邊通常首先調用onUserInteraction();

"Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法經過 CallBack 調用了 Activity 的 dispatchTouchEvent 方法,在 Activity 這裏,咱們能夠重寫 Activity 的dispatchTouchEvent 方法阻斷 touch事件的傳播。接着在Activity裏的dispatchTouchEvent 方法裏,事件又再次傳遞到DecorView,DecorView經過調用父類(ViewGroup)的dispatchTouchEvent 將事件傳給父類處理,也就是咱們下面要分析的方法,這才進入網上大部分文章講解的touch事件傳遞流程"

以上這段話出自 Android 事件分發機制詳解

onSaveInstanceState()、onRestoreInstanceState():

首先,當內存不足時,爲了保證前臺進程的正常運行,Android系統會kill掉一些後臺進程來釋放內存。當某一個Activity存在一種被系統kill掉的可能性時,或者當一個Activty被系統摧毀可是又立刻從新建立時,onSaveInstanceState()就會回調,由於Activity在這種狀況下被kill掉並非咱們的本意,因此係統給出這麼一個地方讓咱們保存本身的數據,等下次再進入這個Acticity的時候能夠復原。Activity的onSaveInstanceState()方法裏面,默認給咱們保存了視圖層次與Fragmets的相關狀態,因此咱們override這個方法的時候,先super調一下,而後再保存一些其它的臨時數據就是了(經過咱們熟悉的Bundle對象來保存)。列舉一下onSaveInstanceState()會被回調的狀況以下:

  1. 用戶按下home鍵 
  2. 在最近任務切換到其它應用 (通常是長按home鍵)  
  3. 啓動一個新的Actvity
  4. 按電源鍵熄滅屏幕
  5. 設備的配置信息發生改變時,好比橫豎屏切換、系統語言切換、調整設置裏面的字體大小等

前四種狀況都是因爲Activity進入後臺了,當內存不足時存在被系統kill掉的可能性,因此會回調onSaveInstanceState()。最後一種狀況比較特殊,若是沒有在AndroidManifest.xml配置相應的configChanges屬性,系統會摧毀這個Activity並立馬從新建立。須要注意的,是存在被摧毀的可能性纔會回調,因此像點擊back鍵,顯示地調用finish(),這種百分之一百會被摧毀的時候是不會給你保存數據的,由於這是你本身想要它去死的。

onRestoreInstanceState()與onSaveInstanceState()並非成對出現的,只有當上述的可能性變成現實的時候纔會回調,由於沒有摧毀就沒有復原。因而可知,對上述的第五種狀況來講,onSaveInstanceState()與onRestoreInstanceState()是必定會成雙成對得出現的。

onSaveInstanceState()若是被回調的話,在onStop()以前,有可能在onPause()以前也有可能在onPause()以後;onRestoreInstanceState()若是被回調的話,在onStart()與onPostCreate()之間。

onConfigurationChanged():

這個方法是跟設備的配置信息改變有關的,若是在AndroidManifest.xml給Activity配置了相應的configChanges屬性,這個時候Activity就不會先被摧毀而後立馬從新建立了,而會只是回調這個方法。那麼設備都有哪些配置呢?在此摘抄下《Android開發藝術探索》的有關於Configuration的說明。

mcc : SIM卡惟一標識IMSI(國際移動用戶識別碼)中的國家代碼,由三位數字組成,中國爲460。此項標識mcc代碼發生了改變

mnc:SIM卡惟一標識IMSI(國際移動用戶識別碼)中的運營商代碼,由兩位數字組成,中國移動TD系統爲00,中國聯通爲01,中國電信爲03。此項標識mnc發生了改變

locale:設備的本地位置發生了改變,通常指切換了系統語言

touchscreen:觸摸屏發生了改變,這個很費解,正常狀況下沒法發生,能夠忽略它

keyboard:鍵盤類型發生了改變,好比用戶使用了外插鍵盤

keyboardHide:鍵盤的可訪問性發生了改變,好比用戶調出了鍵盤

navigation:系統導航方式發生了改變,好比採用了軌跡球導航,這個有點費解,很難發生,能夠忽略它

screenLayout:屏幕布局發生了改變,極可能是用戶激活了另一個顯示設備

fontScale:系統字體縮放比例發生了改變,好比用戶選擇了一個新字號

uiMode:用戶界面模式發生了改變,好比是否開啓了夜間模式(API 8添加)

orientation:屏幕方向發生了改變,這個是最經常使用的,好比旋轉了手機屏幕

screenSize:當屏幕的尺寸信息發生了改變,當旋轉設備屏幕時,屏幕尺寸會發生變化,這個選項比較特殊,它和編譯選項有關,當編譯選項中的minSdkVersion和TargetVersion均低於13時,此選項不會致使Activity重啓,不然會致使Activity重啓(API 13新添加)

smallestScreenSize:設備的物理屏幕尺寸發生改變,這個項目和屏幕的方向沒有關係,僅僅表示在實際的物理屏幕的尺寸改變的時候發生,好比用戶切換到了外部的顯示設備,這個選項和screenSize同樣,當編譯選項中的minSdkVersion和TargetVersion均低於13時,此選項不會致使Activity重啓,不然會致使Activity重啓(API 13新添加)

layoutDirection:當佈局方向發生改變,這個屬性用的比較少,正常狀況下無須修改佈局的layoutDirection屬性(API 17新添加)

爲何要必定要抄一遍,由於能夠加深印象。

onAttachedToWindow(),onDetachedFromWindow():

官方要咱們去看View的這兩個對應的方法。。。那就再說吧。。。

onWindowFocusChanged():

當Activity的窗口獲取或失去焦點的時候就會回調這個方法。官方文檔上說,這個方法是判斷Activity是否對用戶可見的最好的標誌了,我的理解的意思就是,當走到這個方法的時候,Activity裏面的視圖都已經測量過了,咱們的肉眼也確實能看到這個Activity了,可是可能尚未繪製沒有渲染,整個界面是黑的仍是灰的,我也不知道,我只知道在這個方法裏面能夠獲取到View的寬高。

列舉幾種Activity焦點變化的狀況。

  1. 彈出/消失一個Dialog
  2. 上/下拉狀態欄
  3. 啓動一個新的Activity & 返回到前一個Activity
  4. 略......

最後,附一張所謂的Activity的完整的生命週期圖:

 

activity完整生命週期圖

相關文章
相關標籤/搜索