天天看點

Android源碼剖析之Framework層實戰版(Ams管理Activity啟動)Android基礎之Activity launchMode詳解Intent中的四個重要屬性——Action、Data、Category、Extras

  本文來自 http://blog.csdn.net/liuxian13183/

 ,引用必須注明出處!

講到實戰,就不得不拿兩個例子來說明,本篇想拿的是應用最廣泛的兩個:Ams和Wms,一個管理activity,一個管理視窗,而前面我們已經講了不少,本篇不再贅述。

關于Ams對activity的管理,無非這幾個方面:啟動哪個activity、實體按鍵對activity處理、記憶體驟減時activity的回收規則,以及暫停activity的一系列操作。

先說如何啟動activity?有哪些知識點。

Ams排程activity,運作某activity需要通過Ams決定,一般情況下僅限允許均可執行;記錄運作的頁面,儲存activity一些狀态,以便下次啟動更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做程序管理,清楚運作哪些程序,各自的狀态和資訊集合

當然Ams除了管理activity,還會管理其他兩個主要資料類,如Process和Task;Process同樣也有個ProcessRecord用來記錄程序運作資訊和狀态以及内部有哪些元件(指service、provider這樣的),多個apk可以運作在同一程序中,可以通過android:process來設定,一般情況下還是不要,因為手機預設64M記憶體(不同分辨率不同),記憶體一般也僅夠一個apk使用;而TaskRecord記錄activity所在的棧的id、intent和activity的數量,用于對棧進行管理。

AMS常見的預設系統常量:

1、MAX_ACTIVITIES=20: 通過硬體設定,目前頁面僅有一個activity存活,其他20個被緩存起來,超過則殺死優先低的

2、MAX_RECENT_TASKS=20:通過硬體設定,棧最多存儲20個,超過後最先舍棄最早的棧

3、PAUSE_TIMEOUT=500:啟動activity最長時間為500ms,否則關閉該頁面,一般表現是“螢幕黑一下”

4、LAUNCH_TIMEOUT=10*1000:啟動程序最長10s,否則舍棄

5、PROC_START_TIMEOUT=10*1000:啟動程序後10s内必須報告AMS啟動狀态,否則AMS不承認此程序的存在

以上是最基礎的變量,相信在多年開發中,大家對其引起的現象已經曆曆在目

其次在啟動和暫停activity過程中,需要一些變量來儲存狀态,主要因為AMS是通過Service機制動作,比如

1、mPendingActivityLaunched:即将啟動的activity清單

2、mStoppingActivities:A跳入B,B啟動後A即被加入此隊列,差別于mHistory的A跳入即加入

3、mHistory:所有背景activity,A跳入B即A被加入,或按Home鍵B被加入,回來按回退鍵則B被清除;finishing=true

4、mFinishingActivities:上面清單中執行完finish方法的activity,此時并未完全殺死等待被回收

5、mLRUActivities:最近使用的activity,包含mHistory裡的activity和已經清除的activity

此外還有HistroyRecord記錄mPausingActivity(record對象,記錄執行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。

上面講完基礎,接下來程序流程,講講如何啟動Activity?

一般調用方式有四種,點選手機圖示、執行startActivityForResult(startActivity同理)、注冊硬體監聽啟動、被action啟動

以下是啟動Activity的過程和startActivityForResult的啟動(startActivity傳一個預設的requestCode-1,最後仍然調用startActivityForResult方法,是以放在一起講)

啟動前的權限檢查和準備工作

啟動Activity的時候往往還需要進行權限檢查,以檢視其是否符合啟動條件。查詢不滿足條件則傳回,否則繼續;檢查Component是否存在,不滿足傳回,滿足繼續,啟動startActivityLocked(caller,intent)方法,完成以下幾件事

1、檢查如果啟動自己則直接傳回

2、會加入INTENT_FLAG_FORWARD_RESULT标志,用于從C直接傳回結果給A,使用方法A調用startActivityForResut->B調用startActivity->C調用setResult直接回到A

3、檢查call-ActivityRecord是否存在且有指定權限

4、如果Ams中有IActivityController對象,則通知Controller進行相應控制

5、建立臨時HistoryRecord對象,不一定加入mHistory清單,如不關閉則加入

6、檢查是否允許切換activity,否則加入mPendingActivityLaunched

7、判斷pendingActivity是否有要啟動的activity,有則先執行,無則執行該intent

如想了解launchMode,請移步: 

Android基礎之Activity launchMode詳解

如想了解Intent,請移步: 

http://blog.csdn.net/reboot123/article/details/9198101

Intent中的四個重要屬性——Action、Data、Category、Extras

以上是對intent的介紹,接下來會再介紹一下task,也就是如何啟動,以什麼樣的規則啟動和退出。以下均指launchFlag,标記均以FLAG_ACTIVITY_開頭,介紹時會忽略,請注意一下;啟動時會依次判斷如下辨別

1、NO_USER_ACTION:含義無使用者互動;基本不用,主要防止一段時間後執行onUserLeaving方法;接下來如果立即啟動,就把r.delay.Resume設為true

2、PREVIOUS_IS_TOP:含義上個intent是否位于棧頂,基本不用;然後為activity賦予權限加入緩存;此時差別于launchMode和launchFlag,前者指activity自己聲明的啟動方式,後者是明顯啟動者想讓activity如何啟動,能過intent設定,但兩者有相通性

3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用這三者均不建議使用startActivityForResut,而隻限于使用startActivity即r.resultTo=0,不需要回傳資料的情況下;第1個是會新起一個task,即兩個task之間最好不進行資料回傳;2和3的相同在于,如果已經存在這樣的task和component以及其他相同資料如intent,則均跳到相應棧中,不存在則聲明一個新的task;不同點在于:2的task裡可以包含多個activity,而3僅能包含一個;可能跟每個task記憶體大小有關,不同功用的activity可以申請不同的task使用,這一點也可以看上面“

Android基本之Activity LaunchMode詳解

4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,則清除該棧上面的其他activity;後者僅把自己放在最上面;舉例A1->A2->A3,前者啟動A2則變成A1->A2,後者啟動A2則變成A1->A3->A2

5、NO_HISTORY:不要儲存自己,設定A3,則A1->A2->A3->A4,執行完還是A1->A2,皮之不存,毛将焉附。

一般情況下,以上如果調整棧的順序,那麼是可以執行的;如A、B、C三個棧,分别有2個activity,如果從C切換到B,那麼是這樣的A1->A2->B1->B2->C1->C2,調整後A1->A2->C1->C2->B1->B2。

系統還提供另外一種辦法來設定activity的啟動方式,那就是

1、android:clearTaskOnLaunch=true/false:是否清除task裡的其他activity

2、android:finishOnTaskLaunch:是否關閉task中已有的此activity

3、android:allowTaskReparent:是否将自己帶入啟動的task中

4、android:alwaysRetainTaskState:是否由系統維護activity狀态;一般應用在根activity,每次可以打開最後打開的頁面

最近講的幾個東西之間的關系,畫了一張圖,如下所示

正式啟動工作(主要流程均為ActivityThread執行-在attach時初始化)

一、暫停目前activity

1、判斷該activity是否存在;然後執行onUserLeaving方法,避免此activity再與實體按鍵互動,如後退鍵

2、調用performPauseActivity(告知暫停而非逾時等情況,将prev指向自己),先onSaveInstanceState,再執行onPause

·3、報告AMS暫停完畢,通過IPC調用AMS的completePauseActivity方法

二、調用resumeTopActivity方法:

1、如果mHistory有記錄且直接啟動,否則執行startHomeActivityLocked方法啟動主界面程式;

2、系統處于睡眠狀态或目前activity未被暫停,則停止;

3、從mStoppingActivities和mWarningVisibleActivities裡移除目标對象;

4、将被停止的activity設定為不可見狀态,通知activty或task切換

5、判斷目錄程序是否存在并且activityThread存活,否則執行startSpecificActivityLocked方法;如果程序不存在,則調用

Process類啟動新程序,設定pid加入ProcessRecord,啟動完之後再通知Ams啟動目标Activity,至于啟動Process的過程跟調用

暫停或啟用activity的過程無異,僅僅參數發生變化而已,以及變量不同和意義不同。當然我們再講一點,啟動程序畢

竟是個重要流程,提取odex檔案,前面幾篇文章有過介紹,指已經優化過的dex檔案,通過Service、Provider和

Broadcast加入引用,建立完成。

6、最終調用performLaunchActivity,執行attach,執行setTheme,幾個on方法,拿到DecorView加入Wms,設

置可見。

接下來咱們講講停止工作:一般情況下是長時間不使用,或者應用記憶體緊張,或者啟動時設定no_history才會執行,執行過程

而應用與上面生命周期無異,也就是設定不可見,加個mStoppingActivities,異步通知Ams停止,最後調用onStop方法等等,

關閉activity也同樣如此;至于關閉的優先級前面似乎沒講,接下來會着重介紹一下。

Android系統如何管理自己記憶體的?

同樣可以預習一下,原理協同,再做補充。

一般情況下優化級分為-16到15級,而android預設僅使用0-15級,越小優先級越高,目前可見activity最高為0。

為什麼會産生這個優化級,主要android采用的是關閉而不退出,退出而不清理的原則,主要為了二次加載更加快速;

其次是上面停止activity的原因。而這個規則由Ams權重得出,有這樣幾個變量:是否展示在目前頁或是持久化

activity、相應的配合元件、剩餘記憶體量、HOME程序、活躍activity數量群組件等,就像JVM的記憶體管理規則一樣,

詳見:

Java進階之虛拟機垃圾回收機制

由于Ams無法預知記憶體的變動(OOM除外),因而采用這套機制:最先是空程序(無activity程序),其次是

activity,再次是前台的配合元件,如Service、Receiver、Provider,最後才是前台activity;而這些操作由OOM Killer

程序直接管理,關于activity回收需要滿足以下幾種情況:

1、finish操作或crash或anr,通過updateOomAdjLocked方法讓其指定優先級,動态調整

2、如果運作的activity超過20個,必須是已經stop的、不可見的、非常駐程序

因而不合理的手機架構也會造成系統的崩潰。PS:持久化對象的在ProcessRecord的persistent變量是否為true

LocalActivityManager存在于Activity内部,用來裝載和管理activity以及各種狀态,維持一個最低的記憶體消耗;核

心在于它使用UI線程的activityThread來裝載指定的 activity;有同學可能會問task越少或者histroyRecord越小,記憶體

會占用越小嗎?這個問題跟手機裡裝一個app和裝n個app重量是否增加是一樣的道理,但是越過限制以後(task允許)

記憶體占用确實會變大,直到出現OOM。

以下是按鍵方面的内容(鎖屏下,基本均不操作)

1、後退鍵:Activity裡監聽到onBackPressed方法;一般執行的是finish動作,執行performDestroyActivity方法

2、Home鍵:Acitivty的onKey方法無法截取它,屬于設計原因;Wms中使用PhoneWindowManager類中的

interceptKeyTi截取消息,發現是它,再執行launchHomeFromHotKey;2.0之後添加另一個Home鍵執行

startDockOrHome方法來監聽硬體。

與普通的啟動差別在于,能啟動特殊的intent,方法在ContextImpl裡。而長按震動、關閉所有視窗、會彈出LRT-

lasted recent task,可以調用Ams的getRecentTask來取出,通過彈出的窗體的點選事件,進入相應的應用。

總結一下:

程序啟動:1、核心建立程序資料結構,指出其位址總線

2、裝載函數,讀取代碼,拿到資料總線

3、将程式指針指向目标位址入口

虛拟機啟動:首次是從Zygote程序fork出來一個子程序,用來加載資源,然後啟動SystemSever,用來監

控手機按鍵和觸摸事件,以及管理Ams、Pms、Wms等,最終根據配置檔案的HomeActivity,啟動它。

Activity啟動,然後觸發init.rc檔案執行main函數,啟動一個ActivityThread

這就是所謂的UI線程,由Looper聲明一個MessageQueue,接着建立Activity對象,初始化ViewRoot和token,用來分

發消息和接收轉換為本地消息,為後面執行Activity的生命周期,建立PhoneWindow,執行attach方法,初始化内部組

件,根據Wms傳回的消息,适時的執行生命周期,執行setContentView建立DecorView(View内部也有ViewRoot來設定View的各種屬性),由Wms加入到視窗中,設定Visiable,加載結束。

注:每個應用僅有一個ActivityThread來異步處理内部事務,如果出錯則App Crash;具體應用啟動後,會建立ApplicationThread和ActivityThread,分别用來處理應用事務和Activity事務,并且建立MessageQueue,不停的輪循;AMS通過判斷加載某個Activity和資源的加載情況,将消息從手機端通過Binder發給目前應用的ViewRoot對象,通過handler把消息放入消息隊列,輪循出來的消息處理,即推動Activity的生命周期執行。

問題1:在應用運作過程中Window有多個嗎?是的,每個Activity都會建立Window(見1)。

問題2:在應用運作過程中WindowManager有多個嗎?Activity的WindowManager通過系統的WindowManager建立(見2、3、4)

Activity類

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
      
1、   mWindow = new PhoneWindow(this, window);      

mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } }

    2、mWindow.setWindowManager(      

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); }

    3、mWindowManager = mWindow.getWindowManager();      

mCurrentConfig = config; }Window類:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
      
    4、   mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);      

}WindowManagerImpl類:

* Provides low-level communication with the system window manager for
 * operations that are bound to a particular context, display or parent window.
 * Instances of this object are sensitive to the compatibility info associated
 * with the running application.      

提供一個跟系統WindowManager低等級的溝通方式,用于特别的Context、Display或者父Window。

WindowManagerGlobal類的解釋同上。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();      
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        // Only use the default token if we don't have a parent window.
        if (mDefaultToken != null && mParentWindow == null) {
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }

            // Only use the default token if we don't already have a token.
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }      

繼續閱讀