1. 程式人生 > >Android Activity應用視窗的建立過程分析

Android Activity應用視窗的建立過程分析

前言

所謂的視窗(Window)就是一個顯示在手機螢幕上視覺化檢視的一片區域。在Android中視窗是一個抽象的概念,每一個Activity就對應著一個視窗,而所有的視窗都是由檢視(View)來呈現,而我們知道View構成的一個樹形結構的檢視就組成了一個Activity的介面了。在Android系統中視窗分為三個型別:

  1. 應用視窗:所謂應用視窗指的就是該視窗對應一個Activity,因此,要建立應用視窗就必須在Activity中完成了。
  2. 子視窗:所謂子視窗指的是必須依附在某個父視窗之上。
  3. 系統視窗:所謂系統視窗指的是由系統程序建立,不依賴於任何應用或者不依附在任何父視窗之上。

在WindowManager類的內部類LayoutParams中定義了以上三種視窗型別,我們暫且不管WindowManager類是幹嘛的,直接看其內部類LayoutParams的實現。內部類LayoutParams其實是一組用於描述視窗(Window)引數的資料類,其中包括視窗的大小,型別,特徵,軟鍵盤輸入法模式,相對位置以及動畫,背景影象格式等等。

1.視窗引數WinsowManager#LayoutParams

public interface WindowManager extends ViewManager {
    ...........
    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {

        //視窗的起點座標
        public int x;
        public int y;

        //以下定義都是描述視窗的型別
        public int type;
        //第一個應用視窗
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //所有程式視窗的base視窗,其他應用程式視窗都顯示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //所有Activity的視窗
        public static final int TYPE_APPLICATION        = 2;
        //目標應用視窗未啟動之前的那個視窗
        public static final int TYPE_APPLICATION_STARTING = 3;
        //最後一個應用視窗
        public static final int LAST_APPLICATION_WINDOW = 99;

        //第一個子視窗
        public static final int FIRST_SUB_WINDOW        = 1000;
        // 面板視窗,顯示於宿主視窗的上層
        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
        // 媒體視窗(例如視訊),顯示於宿主視窗下層
        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
        // 應用程式視窗的子面板,顯示於所有面板視窗的上層
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
        //對話方塊視窗
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //最後一個子視窗
        public static final int LAST_SUB_WINDOW         = 1999;

        //系統視窗,非應用程式建立
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //狀態列,只能有一個狀態列,位於螢幕頂端,其他視窗都位於它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //搜尋欄,只能有一個搜尋欄,位於螢幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //電話視窗,它用於電話互動(特別是呼入),置於所有應用程式之上,狀態列之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //系統警告提示視窗,出現在應用程式視窗之上
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        //鎖屏視窗
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //資訊視窗,用於顯示Toast
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        //系統頂層視窗,顯示在其他一切內容之上,此視窗不能獲得輸入焦點,否則影響鎖屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //電話優先,當鎖屏時顯示,此視窗不能獲得輸入焦點,否則影響鎖屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //系統對話方塊視窗
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //鎖屏時顯示的對話方塊
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //系統內部錯誤提示,顯示在任何視窗之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //內部輸入法視窗,顯示於普通UI之上,應用程式可重新佈局以免被此視窗覆蓋
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //內部輸入法對話方塊,顯示於當前輸入法視窗之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //牆紙視窗
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //狀態列的滑動面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //安全系統覆蓋視窗,這些窗戶必須不帶輸入焦點,否則會干擾鍵盤
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //最後一個系統視窗
        public static final int LAST_SYSTEM_WINDOW      = 2999;

        ........

        //視窗特徵標記
        public int flags;
        //當該window對使用者可見的時候,允許鎖屏
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        //視窗後面的所有內容都變暗
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        //Flag:視窗後面的所有內容都變模糊
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //視窗不能獲得焦點
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //視窗不接受觸控式螢幕事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //即使在該window在可獲得焦點情況下,允許該視窗之外的點選事件傳遞到當前視窗後面的的視窗去
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //當手機處於睡眠狀態時,如果螢幕被按下,那麼該window將第一個收到觸控事件
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //當該window對使用者可見時,螢幕出於常亮狀態
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //:讓window佔滿整個手機螢幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //允許視窗超出整個手機螢幕
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //window全屏顯示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //恢復window非全屏顯示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //開啟視窗抖動
        public static final int FLAG_DITHER             = 0x00001000;
        //安全內容視窗,該視窗顯示時不允許截圖
        public static final int FLAG_SECURE             = 0x00002000;


        //鎖屏時顯示該視窗
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //系統的牆紙顯示在該視窗之後
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //當window被顯示的時候,系統將把它當做一個使用者活動事件,以點亮手機螢幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //該視窗顯示,消失鍵盤
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //當該window在可以接受觸控式螢幕情況下,讓因在該window之外,而傳送到後面的window的觸控式螢幕可以支援split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //對該window進行硬體加速,該flag必須在Activity或Dialog的Content View之前進行設定
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //讓window佔滿整個手機螢幕,不留任何邊界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //透明狀態列
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //透明導航欄
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;


        ..........
        //軟輸入法模式
        public int softInputMode;

        //用於描述軟鍵盤顯示規則的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //沒有軟鍵盤顯示的約定規則
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //可見性狀態softInputMode,請不要改變軟輸入區域的狀態
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //使用者導航(navigate)到你的視窗時隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //總是隱藏軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //使用者導航(navigate)到你的視窗時顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        //總是顯示軟鍵盤
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        //顯示軟鍵盤時用於表示window調整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //不指定顯示軟體盤時,window的調整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //當顯示軟鍵盤時,調整window內的控制元件大小以便顯示軟鍵盤
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //當顯示軟鍵盤時,調整window的空白區域來顯示軟鍵盤,即使調整空白區域,軟鍵盤還是有可能遮擋一些有內容區域,這時使用者就只有退出軟鍵盤才能看到這些被遮擋區域並進行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //當顯示軟鍵盤時,不調整window的佈局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //使用者導航(navigate)到了你的window
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;


        //視窗的對齊方式
        public int gravity;

        //期望的點陣圖格式,預設為不透明,參考android.graphics.PixelFormat
        public int format;
        //視窗所使用的動畫設定,它必須是一個系統資源而不是應用程式資源,因為視窗管理器不能訪問應用程式
        public int windowAnimations;
        //整個視窗的半透明值,1.0表示不透明,0.0表示全透明
        public float alpha = 1.0f;
        //當FLAG_DIM_BEHIND設定後生效,該變數指示後面的視窗變暗的程度,1.0表示完全不透明,0.0表示沒有變暗
        public float dimAmount = 1.0f;

        public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
        public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
        public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
        public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
        //用來覆蓋使用者設定的螢幕亮度,表示應用使用者設定的螢幕亮度,從0到1調整亮度從暗到最亮發生變化
        public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;

        public static final int ROTATION_ANIMATION_ROTATE = 0;
        public static final int ROTATION_ANIMATION_CROSSFADE = 1;
        public static final int ROTATION_ANIMATION_JUMPCUT = 2;
        //螢幕旋轉動畫
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //視窗的標示符
        public IBinder token = null;
        //此視窗所在應用的包名
        public String packageName = null;
        //視窗螢幕方向
        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

        //控制status bar是否可見,兩種賦值  View#STATUS_BAR_VISIBLE;View#STATUS_BAR_HIDDEN
        public int systemUiVisibility;

        ......

    }
}

分析:WindowManager.LayoutParams繼承自ViewGrop.LayoutParams用於描述Android視窗的引數。由上面程式碼定義我們知道關於視窗有以下幾個重要的引數:

  • width:描述視窗的寬度,該變數是父類ViewGroup.LayoutParams的成員變數。
  • height:描述視窗的高度,該變數同樣是父類ViewGroup.LayoutParams的成員變數。
  • x:描述視窗的起點X軸的座標。
  • y:描述視窗起點Y軸的座標。
  • type:視窗的型別,分為三個大型別:應用視窗,子視窗,系統視窗。
  • flag:視窗特徵標記,比如是否全屏,是否隱藏標題欄等。
  • gravity:視窗的對齊方式,居中還是置頂或者置底等等。

接下來從原始碼角度分析Android系統中的以上三種視窗建立過程。

2.應用視窗建立過程分析

2.1Activity,Window,WindowManager之間的關係

一個應用視窗一般對應一個Activity物件,因此要建立應用視窗就必須建立一個Activity。由Activity的啟動過程知道,所有Activity的啟動都是有ActivityManagerService(簡稱Ams)通過Bindler程序間通訊機制向客戶端程序ActivityThread傳送建立新的Activity物件通知,所有Activity的建立都在對應應用程式程序ActivityThread類中完成。Activity類的建立完成之後會呼叫Activity#attach方法,程式碼如下:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

    private Window mWindow;

    private WindowManager mWindowManager;

    ........
    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) {
        attachBaseContext(context);
        //----- 1 ------
        mWindow = new PhoneWindow(this);
        //----- 2 ------
        mWindow.setCallback(this);
        //----- 3 ------
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        //----- 4 ------   
        mWindow.setWindowManager(            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        //----- 5 ------
        mWindowManager = mWindow.getWindowManager();
        .........
    }
    ........
}

分析: 
1. 在attach放法中首先建立一個PhoneWindow物件賦值給Attivity的成員變數mWindow,而Window類是一個抽象類,用於描述螢幕頂層檢視和使用者行為操作的視窗,而PhoneWindow是實現了抽象類Window的子類。 
2. 給Activity視窗Window型別的成員變數mWindow設定事件回撥監聽,而Activity實現了Window#Callback介面類,如此一來Activity就可以分發處理觸控事件了,這就是為什麼按back鍵當前Activity會finish掉的原因。 
3. 給Activity視窗Window型別的成員變數mWindow設定視窗的消失回撥監聽,用於處理Activity的視窗消失時finish掉當前Activity。 
4. 獲得當前Activity的視窗管理物件WindowManager,然後給Window類設定視窗管理服務,也就是賦值給Window類的成員變數mWindowManager。 
5. 獲得Window類的視窗管理物件mWindowManager,然後賦值給Activity的成員變數mWindowManager,由此我們知道Activity的視窗管理和Window類的視窗管理指向的都是同一個WindowManager物件。

Window,WindowManager,Activity關係類圖如下:

這裡寫圖片描述

Activity的成員變數mWindow型別為Window,該類就是用於描述應用程式視窗類,因此一個Activity對應著一個Window也就是應用視窗。

Activity的成員變數mWindowManager型別為WindowManager,它用來管理當前Activity的視窗,因此一個Activity也對應著一個WindowManager視窗管理器。有前面分析得知Window類的成員變數mWindowManager和Activity的成員變數mWindowManager都是指向同一個WindowManager物件,而WindowManager物件是呼叫如下程式碼獲取:

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)
  • 1

而我們知道Activity是繼承Context類的,ContextImpl類中實現了WindowManager服務的註冊,程式碼如下:

class ContextImpl extends Context {
static {
    registerService(WINDOW_SERVICE, new ServiceFetcher() {
                Display mDefaultDisplay;
                public Object getService(ContextImpl ctx) {
                    Display display = ctx.mDisplay;
                    if (display == null) {
                        if (mDefaultDisplay == null) {
                            DisplayManager dm = (DisplayManager)ctx.getOuterContext().
                                    getSystemService(Context.DISPLAY_SERVICE);
                            mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
                        }
                        display = mDefaultDisplay;
                    }
                    return new WindowManagerImpl(display);
                }});
    .......
    }
}

有上面程式碼可知,在ContextImpl類中註冊服務是一個靜態程式碼塊,也就是說值執行main方法之前就已經完成了WindowManager服務的註冊。在整個應用第一次建立Context物件的時候就已經建立WindowManagerImpl物件了,然而WindowManager是個抽象類,具體實現指向WindowManagerImpl。整個應用程式的視窗管理服務都是指向同一個WindowManagerImpl物件,言外之意,不管一個應用程式中有多少個Activity,但只有一個WindowManagerImpl物件。

2.2 Activity視窗新增檢視View的過程分析

前面我們說了,一個Activity對應著一個視窗(Window),而視窗只是一個抽象的概念,所有的視窗都有檢視(View)來呈現,因此我們來分析下檢視View是怎麼新增到視窗Window上的?

每個Activity都會呼叫setContextView方法來載入佈局檢視,而這其實就是檢視View新增到Activity視窗上的一個過程。載入佈局檢視程式碼如下Activity#setContentView

 public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
  •  

由於getWindow方法獲得的是Activity的成員變數mWindow,而Window物件其實指向PhoneWindow類,因此這裡僅僅做了一個轉發,其實現在PhoneWindow#setContentView方法中。程式碼如下:

 public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //給視窗安裝裝飾檢視DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //載入xml佈局檢視內容到檢視容器mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
    }

分析: 
1. 呼叫installDecor方法給視窗安裝檢視裝飾,所謂檢視裝飾指的就是介面上看到的標題欄,導航欄Actionbar,也就是視窗的標題欄;而檢視裝飾的內容就是layout.xml佈局,也就是視窗所載入的檢視View。

installDecor方法實現的程式碼如下:

 private void installDecor() {
        if (mDecor == null) {
            //
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
         }
        ........
}

該方法中主要做了兩件事:

  • 呼叫generateDecor方法獲得DecorView物件,該物件是ViewGroup型別,用於描述視窗Window的頂層檢視。
  • 呼叫generateLayout方法給視窗新增標題欄,同時獲得裝載視窗內容的容器mContentParent,該成員變數也是ViewGroup型別。

2.呼叫inflate方法將佈局檢視內容載入到剛剛獲得的內容容器mContentParent上。

到此,Activity視窗Window新增檢視View的流程就結束了。現總結一下他們之間的關係:一個Activity中有一個Window物件用於描述當前應用的視窗,而Window是抽象類,實際上指向PhoneWindow類。PhoneWindow類中有一個內部類DecorView用於描述視窗的頂層檢視,該類實際上是ViewGroup型別。當往DecorView上新增標題欄和視窗內容容器之後,最後將layout.xml佈局新增到視窗的內容容器中,即完成了視窗Window新增檢視View了。最後用之前部落格中的一張圖片來描述以上關係:

這裡寫圖片描述

【轉載請註明出處:http://blog.csdn.net/feiduclear_up CSDN 廢墟的樹】

2.3 Activity新增視窗Window的過程

有上一小節知道一個Activity對應著一個視窗Window,而視窗Window也完成了檢視View的新增,最後檢視View是怎麼繪製到手機螢幕上的呢?

在Activity建立完成之後,Ams利用Bindler程序間的通訊機制通知客戶端呼叫ActivityThread類中的handleResumeActivity方法來啟動Activity。程式碼如下:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        ........
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;

            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                //客戶端Activity的視窗Window物件
                r.window = r.activity.getWindow();
                //視窗的頂層檢視DecorView物件
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //客戶端Activity視窗的視窗管理器物件WindowManager
                ViewManager wm = a.getWindowManager();
                //視窗的引數
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                //視窗的型別為基礎應用視窗
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                //視窗的軟輸入法模式
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    //標記客戶端Activity新增視窗成功
                    a.mWindowAdded = true;
                    //新增視窗
                    wm.addView(decor, l);
                }
    ........
        }
    }

分析: 
首先獲得客戶端Activity的視窗物件Window,然後獲得該視窗的頂層檢視DecorView賦值給本地變數decor,再獲得客戶端Activity的視窗管理物件WindowManager賦值給本地變數wm,之後再來構造視窗Window的引數WindowManager.LayoutParams,將當前視窗的型別type賦值為TYPE_BASE_APPLICATION,由文章開頭我們知道該型別就是應用視窗型別。最後呼叫WindowManager類中的addView方法完成當前視窗Window的新增。

在這個過程中最主要的操作就是呼叫了WindowManager#addView方法,而WindowManager是個介面類,繼承自ViewManager。

ViewManager原始碼如下:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  •  

該介面類僅僅定義了三個介面方法:

  • addView:新增View檢視
  • updateViewLayout:更新檢視
  • removeView:移除檢視

所以該類是用來管理檢視View狀態的類,也就是管理檢視View的新增,更新,移除。從此處也可以看出,所謂的WindowManager視窗管理其實就是對視窗的檢視View進行管理。

WindowManager類原始碼如下:

public interface WindowManager extends ViewManager {
    ........
     public Display getDefaultDisplay();
     public void removeViewImmediate(View view);
     public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
    ........
    }
}
  • 1

該類繼承自ViewManager,也是一個介面類,裡面定義了兩個介面方法和一個內部類LayoutParams類用來描述視窗Window引數。關於WindowManager真正的實現其實在WindowManagerImpl類中。

WindowManagerImpl原始碼如下:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

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

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

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

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }
}

分析: 
該來中實現了介面類ViewManager中的三個方法。但是細心的你會發現,其實這三個方法內部將實際操作轉發到mClobal物件的三個對應的方法中去了,而mGlobal成員變數的型別是WindowManagerGlobal類。

WindowManagerGlobal類原始碼:

public final class WindowManagerGlobal {
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    ........
    //單例模式構造方法
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
     ........
     //視窗的新增過程
     public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {    
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };

            }
            //不能重複新增視窗
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }       
            }
            //判斷當前視窗是否為子視窗,如果是則獲得其父視窗並儲存在panelParentView變數中
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //每一個視窗對應著一個ViewRootImpl物件
            root = new ViewRootImpl(view.getContext(), display);
            //給當前視窗檢視設定引數
            view.setLayoutParams(wparams);
            //儲存三個陣列
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        try {
            //真正執行視窗的檢視View繪製工作的方法
            root.setView(view, wparams, panelParentView);
        } 
    }

 //主要更新當前視窗的引數LayoutParams
 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }
    //從三個數組裡面分別移除DecorView物件,ViewRootIpl物件,WindowManager.LayoutParams物件
    public void removeView(View view, boolean immediate) { 
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
        }
    }

分析: 
在WindowManagerGlobal類中維繫著三個陣列分別是:

  • mViews:儲存著當前應用所有視窗的頂層檢視DecorView物件
  • mRoots:儲存著當前應用所有視窗的檢視繪製類ViewRootImpl
  • mParams:儲存著當前應用所有視窗的引數 WindowManager.LayoutParams

在2.1小節的最後我們知道,一個應用中不管有多少個Activity,都共用一個WindowManagerImpl物件,而在WindowManagerImpl類中是單例模式獲得WindowManagerGlobal物件,因此:一個應用中也就只有一個WindowManagerGlobal物件了。而在該物件中通過維護以上三個陣列來維護一個應用中所有視窗的管理。

addView方法:

  • 首先檢查當前視窗是否已經新增過,不允許重複新增視窗。
  • 判斷如果新增的視窗為子視窗型別,則找到他的父視窗,然後儲存在變數panelParentView中作為後面setView方法的引數。
  • 在該方法中建立ViewRootImpl物件,每一個Window物件都持有一個ViewRootImpl物件,然後為當前視窗檢視設定視窗引數,在將當前視窗檢視View,ViewRootImpl物件,以及視窗引數分別儲存到三個數組裡。
  • 最後呼叫ViewRootImpl#setView方法來完成當前視窗檢視的繪製。ViewRootImpl類裡面會依次執行measure(測量),layout(佈局),draw(繪製)三個方法來完成檢視View繪製到手機屏上的一個過程,此處不詳解,具體參考部落格:從ViewRootImpl類分析View繪製的流程。此處才是真正將檢視View繪製到手機螢幕的地方。

updateViewLayout方法:

先找到指定的視窗檢視View,然後將舊的引數wparams從陣列mParams移除,往陣列mParams新增新的視窗引數,最後給視窗檢視重新設定一下引數即完成了視窗檢視的更新操作。

removeView方法:

依次將三個引數從陣列中移除,然後呼叫相應的方法銷燬檢視View的繪製。

總結

  1. 一個Activity就對應著一個應用視窗PhoneWindow物件,該物件用於描述視窗的。

  2. PhoneWindow類內部有一個DecorView類,該類是ViewGroup型別,用於描述視窗的頂層檢視的,該檢視才是真正裝載layout.xml佈局內容的。

  3. 一個應用不管有多少個Activity,都只有一個WindowManager視窗管理器,也就只有一個WindowManagerGlobal物件通過維護三個陣列 mViews,mRoots,mParams來管理所有視窗的新增,更新,刪除。

  4. 一個視窗對應著一個ViewRootImpl類,該類主要的作用就是將視窗的頂層檢視DecorView內容繪製到手機螢幕上。

最後給出以上所有類圖關係圖如下:

這裡寫圖片描述
點選看大圖

 

Android - Activity 啟動過程

概述

從點選桌面應用圖示到應用顯示的過程我們再熟悉不過了,下面我們來分析下這個過程都做了什麼。

本文主要對以下問題分析:

  • ActivityThread 是什麼,它是一個執行緒嗎,如何被啟動的?
  • ActivityClientRecord 與 ActivityRecord 是什麼?
  • Context 是什麼,ContextImpl,ContextWapper 是什麼?
  • Instrumentation 是什麼?
  • Application 是什麼,什麼時候建立的,每個應用程式有幾個 Application?
  • 點選 Launcher 啟動 Activity 和應用內部啟動 Activity 的區別?
  • Activity 啟動過程,onCreate(),onResume() 回撥時機及具體作用?

Launcher

如不瞭解 Android 是如何從開機到 Launcher 啟動的過程,請先閱讀Android - 系統啟動過程

這裡寫圖片描述

我們知道 Android 系統啟動後已經啟動了 Zygote,ServiceManager,SystemServer 等系統程序;ServiceManager 程序中完成了 Binder 初始化;SystemServer 程序中 ActivityManagerService,WindowManagerService,PackageManagerService 等系統服務在 ServiceManager 中已經註冊;最後啟動了 Launcher 桌面應用。

其實 Launcher 本身就是一個應用程式,執行在自己的程序中,我們看到的桌面就是 Launcher 中的一個 Activity。

應用安裝的時候,通過 PackageManagerService 解析 apk 的 AndroidManifest.xml 檔案,提取出這個 apk 的資訊寫入到 packages.xml 檔案中,這些資訊包括:許可權、應用包名、icon、apk 的安裝位置、版本、userID 等等。packages.xml 檔案位於系統目錄下/data/system/packages.xml。

同時桌面 Launcher 會為安裝過的應用生成不同的應用入口,對應桌面上的應用圖示,下面分析點選應用圖示的到應用啟動的過程。

點選 Launcher 中應用圖示

這裡寫圖片描述

點選 Launcher 中應用圖示將會執行以下方法

Launcher.startActivitySafely()
Launcher.startActivity()
//以上兩個方法主要是檢查將要開啟的 Activity 是否存在

Activity.startActivity()
//這段程式碼大家已經很熟悉,經常開啟 Activity 用的就是這個方法

Activity.startActivityForResult()
//預設 requestCode = -1,也可通過呼叫 startActivityForResult() 傳入 requestCode。 
//然後通過 MainThread 獲取到 ApplicationThread 傳入下面方法。

Instrumentation.execStartActivity()
//通過 ActivityManagerNative.getDefault() 獲取到 ActivityManagerService 的代理為程序通訊作準備。

ActivityManagerNative.getDefault().startActivity()
ActivityManagerProxy.startActivity()
//呼叫代理物件的 startActivity() 方法,傳送 START_ACTIVITY_TRANSACTION 命令。
  •  

在 system_server 程序中的服務端 ActivityManagerService 收到 START_ACTIVITY_TRANSACTION 命令後進行處理,呼叫 startActivity() 方法。

ActivityManagerService.startActivity() -> startActivityAsUser(intent, requestCode, userId)
//通過 UserHandle.getCallingUserId() 獲取到 userId 並呼叫 startActivityAsUser() 方法。

ActivityStackSupervisor.startActivityMayWait() -> resolveActivity()
//通過 intent 建立新的 intent 物件,即使之前 intent 被修改也不受影響。 然後呼叫 resolveActivity()。
//然後通過層層呼叫獲取到 ApplicationPackageManager 物件。

PackageManagerService.resolveIntent() -> queryIntentActivities()
//獲取 intent 所指向的 Activity 資訊,並儲存到 Intent 物件。

PackageManagerService.chooseBestActivity()
//當存在多個滿足條件的 Activity 則會彈框讓使用者來選擇。

ActivityStackSupervisor.startActivityLocked()
//獲取到呼叫者的程序資訊。 通過 Intent.FLAG_ACTIVITY_FORWARD_RESULT 判斷是否需要進行 startActivityForResult 處理。 
//檢查呼叫者是否有許可權來呼叫指定的 Activity。 
//建立 ActivityRecord 物件,並檢查是否執行 App 切換。

ActivityStackSupervisor.startActivityUncheckedLocked() -> startActivityLocked()
//進行對 launchMode 的處理[可參考 Activity 啟動模式],建立 Task 等操作。
//啟動 Activity 所在程序,已存在則直接 onResume(),不存在則建立 Activity 並處理是否觸發 onNewIntent()。

ActivityStack.resumeTopActivityInnerLocked()
//找到 resume 狀態的 Activity,執行 startPausingLocked() 暫停該 Activity,同時暫停所有處於後臺棧的 Activity,找不到 resume 狀態的 Activity 則回桌面。
//如果需要啟動的 Activity 程序已存在,直接設定 Activity 狀態為 resumed。 呼叫下面方法。

ActivityStackSupervisor.startSpecificActivityLocked()
//程序存在呼叫 realStartActivityLocked() 啟動 Activity,程序不存在則呼叫下面方法。

fork 新程序

從 Launcher 點選圖示,如果應用沒有啟動過,則會 fork 一個新程序。建立新程序的時候,ActivityManagerService 會儲存一個 ProcessRecord 資訊,Activity 應用程式中的AndroidManifest.xml 配置檔案中,我們沒有指定 Application 標籤的 process 屬性,系統就會預設使用 package 的名稱。每一個應用程式都有自己的 uid,因此,這裡 uid + process 的組合就可以為每一個應用程式建立一個 ProcessRecord。每次在新建新程序前的時候會先判斷這個 ProcessRecord 是否已存在,如果已經存在就不會新建程序了,這就屬於應用內開啟 Activity 的過程了。

ActivityManagerService.startProcessLocked()
//程序不存在請求 Zygote 建立新程序。 建立成功後切換到新程序。
  •  

程序建立成功切換至 App 程序,進入 app 程序後將 ActivityThread 類載入到新程序,並呼叫 ActivityThread.main() 方法

ActivityThread.main()
//建立主執行緒的 Looper 物件,建立 ActivityThread 物件,ActivityThread.attach() 建立 Binder 通道,開啟 Looper.loop() 訊息迴圈。

ActivityThread.attach()
//開啟虛擬機器各項功能,建立 ActivityManagerProxy 物件,呼叫基於 IActivityManager 介面的 Binder 通道 ActivityManagerProxy.attachApplication()。

ActivityManagerProxy.attachApplication()
//傳送 ATTACH_APPLICATION_TRANSACTION 命令
  •  

此時只建立了應用程式的 ActivityThread 和 ApplicationThread,和開啟了 Handler 訊息迴圈機制,其他的都還未建立, ActivityThread.attach(false) 又會最終到 ActivityMangerService 的 attachApplication,這個工程其實是將本地的 ApplicationThread 傳遞到 ActivityMangerService。然後 ActivityMangerService 就可以通過 ApplicationThread 的代理 ApplicationThreadProxy 來呼叫應用程式 ApplicationThread.bindApplication,通知應用程式的 ApplicationThread 已和 ActivityMangerService 繫結,可以不借助其他程序幫助直接通訊了。此時 Launcher 的任務也算是完成了。

在 system_server 程序中的服務端 ActivityManagerService 收到 ATTACH_APPLICATION_TRANSACTION 命令後進行處理,呼叫 attachApplication()。

ActivityMangerService.attachApplication() -> attachApplicationLocked()
//首先會獲取到程序資訊 ProcessRecord。 繫結死亡通知,移除程序啟動超時訊息。 獲取到應用 ApplicationInfo 並繫結應用 IApplicationThread.bindApplication(appInfo)。
//然後檢查 App 所需元件。
  •  
  • Activity: 檢查最頂層可見的 Activity 是否等待在該程序中執行,呼叫 ActivityStackSupervisor.attachApplicationLocked()。
  • Service:尋找所有需要在該程序中執行的服務,呼叫 ActiveServices.attachApplicationLocked()。
  • Broadcast:檢查是否在這個程序中有下一個廣播接收者,呼叫 sendPendingBroadcastsLocked()。

此處討論 Activity 的啟動過程,只討論 ActivityStackSupervisor.attachApplicationLocked() 方法。

ActivityStackSupervisor.attachApplicationLocked() -> realStartActivityLocked()
//將該程序設定為前臺程序 PROCESS_STATE_TOP,呼叫 ApplicationThreadProxy.scheduleLaunchActivity()。

ApplicationThreadProxy.scheduleLaunchActivity()
//傳送 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令

傳送送完 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令,還會發送 BIND_APPLICATION_TRANSACTION 命令來建立 Application。

ApplicationThreadProxy.bindApplication()
//傳送 BIND_APPLICATION_TRANSACTION 命令
  • 1
  • 2

App 程序初始化

在 app 程序中,收到 BIND_APPLICATION_TRANSACTION 命令後呼叫 ActivityThread.bindApplication()。

ActivityThread.bindApplication()
//快取 Service,初始化 AppBindData,傳送訊息 H.BIND_APPLICATION。
  • 1
  • 2

ApplicationThreadProxy.bindApplication(…) 會傳來這個應用的一些資訊,如ApplicationInfo,Configuration 等,在 ApplicationThread.bindApplication 裡會待資訊封裝成A ppBindData,通過

sendMessage(H.BIND_APPLICATION, data)
  • 1

將資訊放到應用裡的訊息佇列裡,通過 Handler 訊息機制,在 ActivityThread.handleMeaasge 裡處理 H.BIND_APPLICATION 的資訊,呼叫 AplicationThread.handleBindApplication。

handleBindApplication(AppBindData data) {
    Process.setArgV0(data.processName);//設定程序名
    ...
    //初始化 mInstrumentation
    if(data.mInstrumentation!=null) {
        mInstrumentation = (Instrumentation) cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } else {
        mInstrumentation = new Instrumentation();
    }
    //建立Application,data.info 是個 LoadedApk 物件。
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
    mInitialApplication = app;
    //呼叫 Application 的 onCreate()方法。
    mInstrumentation.callApplicationOnCreate(app);
}

public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {

    if (mApplication != null) {   
       return mApplication;
    }

    String appClass = mApplicationInfo.className;
    java.lang.ClassLoader cl = getClassLoader();

    //此時新建一個 Application 的 ContextImpl 物件,
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

    //通過在 handleBindApplication 建立的 mInstrumentation 物件新建一個 Application 物件,同時進行 attach。
    app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
    appContext.setOuterContext(app);
}

//設定程序名,獲取 LoadedApk 物件,建立 ContextImpl 上下文
//LoadedApk.makeApplication() 建立 Application 物件,呼叫 Application.onCreate() 方法。

Instrumentation:

public Application newApplication(ClassLoader cl, String className, Context context) {    
    return newApplication(cl.loadClass(className), context);
}
Instrumentation類:
static public Application newApplication(Class<?> clazz, Context context)  {
    //例項化 Application
    Application app = (Application)clazz.newInstance();     

    // Application 和 context繫結
    app.attach(context);    
    return app;
}
//attach 就是將新建的 ContextImpl 賦值到 mBase,這個 ContextImpl 物件就是所有Application 內 Context 的具體實現,同時賦值一些其他的資訊如 mLoadedApk。
final void attach(Context context) {    
    mBase = base;  
    mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

這時 Application 就建立好了,這點很重要,很多資料裡說 Application 是在performLaunchActivity() 裡建立的,因為 performLaunchActivity() 也有mInstrumentation.newApplication 這個呼叫,newApplication() 函式中可看出會先判斷是否以及建立了 Application,如果之前已經建立,就返回已建立的 Application 物件。

Activity 啟動

上面 fork 程序時會發送 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令,在 app 程序中,收到 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION 命令後呼叫 ApplicationThread.scheduleLaunchActivity()。

ApplicationThread.scheduleLaunchActivity()
//傳送訊息 H.LAUNCH_ACTIVITY。

sendMessage(H.LAUNCH_ACTIVITY, r);

ActivityThread.handleLaunchActivity()
//最終回撥目標 Activity 的 onConfigurationChanged(),初始化 WindowManagerService。
//呼叫 ActivityThread.performLaunchActivity()

ActivityThread.performLaunchActivity() {
    //類似 Application 的建立過程,通過 classLoader 載入到 activity.
    activity = mInstrumentation.newActivity(classLoader, 
               component.getClassName(), r.intent);
    //因為 Activity 有介面,所以其 Context 是 ContextThemeWrapper 型別,但實現類仍是ContextImpl.
    Context appContext = createBaseContextForActivity(r, activity);
    activity.attach(context,mInstrumentation,application,...);
    //與 Window 進行關聯

    //attach 後呼叫 activity 的 onCreate()方法。
    mInstrumentation.callActivityOnCreate(activity,...)

}
//在ActivityThread.handleLaunchActivity裡,接著呼叫

Activity.performCreate() -> onCreate()
//最終回撥目標 Activity 的 onCreate()。

Activity.setContentView()
//設定 layout 佈局

ActivityThread.performResumeActivity()
//最終回撥目標 Activity 的 onResume()。
  •  

與 Window 進行關聯,具體過程詳見:Activity,Window,View 之間的關係

總結

Activity 的整體啟動流程如圖所示:

這裡寫圖片描述

  • ActivityThread 是什麼,它是一個執行緒嗎,如何被啟動的?

它不是一個執行緒,它是執行在 App 程序中的主執行緒中的一個方法中。當 App 程序建立時會執行 ActivityThread.main(),ActivityThread.main() 首先會建立 Looper 執行 Looper.prepareMainLooper();然後建立 ActivityThread 並呼叫 ActivityThread.attach() 方法告訴 ActivityManagerService 我們建立了一個應用 並將 ApplicationThread 傳給 ActivityManagerService;最後呼叫 Looper.loop()。

  • ActivityClientRecord 與 ActivityRecord 是什麼?

記錄 Activity 相關資訊,比如:Window,configuration,ActivityInfo 等。 
ActivityClientRecord 是客戶端的,ActivityRecord 是 ActivityManagerService 服務端的。

  • Context 是什麼,ContextImpl,ContextWapper 是什麼?

Context 定義了 App 程序的相關環境,Context 是一個介面,ContextImpl 是子類,ContextWapper 是具體實現。

應用資源是在 Application 初始化的時候,也就是建立 Application,ContextImpl 的時候,ContextImpl 就包含這個路徑,主要就是對就是 ResourcesManager 這個單例的引用。

可以看出每次建立 Application 和 Acitvity 以及 Service 時就會有一個 ContextImpl 例項,ContentProvider 和B roadcastReceiver 的 Context 是其他地方傳入的。

所以 Context 數量 = Application 數量 + Activity 數量 + Service 數量,單程序情況下 Application 數量就是 1。

  • Instrumentation 是什麼?

管理著元件Application,Activity,Service等的建立,生命週期呼叫。

  • Application 是什麼,什麼時候建立的,每個應用程式有幾個 Application?

Application 是在 ActivityThread.handleBindApplication() 中建立的,一個程序只會建立一個 Application,但是一個應用如果有多個程序就會建立多個 Application 物件。

  • 點選 Launcher 啟動 Activity 和應用內部啟動 Activity 的區別?

點選 Launcher 時會建立一個新程序來開啟 Activity,而應用內開啟 Activity,如果 Activity 不指定新程序,將在原來程序開啟,是否開啟新程序實在 ActivityManagerService 進行控制的,上面分析得到,每次開啟新程序時會儲存程序資訊,預設為 應用包名 + 應用UID,開啟 Activity 時會檢查請求方的資訊來判斷是否需要新開程序。Launcher 開啟 Activity 預設 ACTIVITY_NEW_TASK,新開一個 Activity 棧來儲存 Activity 的資訊。

  • Activity 啟動過程,onCreate(),onResume() 回撥時機及具體作用?

Activity.onCreate() 完成了 App 程序,Application,Activity 的建立,呼叫 setContentView() 給 Activity 設定了 layout 佈局。

Activity.onResume() 完成了 Activity 中 Window 與 WindowManager 的關聯,並對所有子 View 進行渲染並顯示。

參考資料

Android - 系統啟動過程

計算機是如何啟動的?

首先熟悉一些概念,計算機的硬體包括:CPU,記憶體,硬碟,顯示卡,顯示器,鍵盤滑鼠等其他輸入輸出裝置。 所有的軟體(比如:作業系統)都是存放在硬碟上,程式執行時需要將程式從硬碟上讀取到記憶體中然後載入到 CPU 中來執行。 當我們按下開機鍵時,此時記憶體中什麼都沒有,因此需要藉助某種方式,將作業系統載入到記憶體中,而完成這項任務的就是 BIOS。

  • 引導階段

BIOS: Basic Input/Output System(基本輸入輸出系統),在 IBM PC 相容系統上,是一種業界標準的韌體介面(來自維基百科)。 BIOS 一般是主機板晶片上的一個程式,計算機通電後,第一件事就是讀取它。

BIOS 程式首先檢查計算機硬體能否滿足執行的基本條件,這叫做"硬體自檢"(Power-On Self-Test),縮寫為 POST。 如果硬體出現問題,主機板會發出不同含義的蜂鳴,啟動中止。 如果沒有問題,螢幕就會顯示出 CPU,記憶體,硬碟等資訊。

硬體自檢完成後,BIOS 把控制權轉交給下一階段的啟動程式。 這時 BIOS 需要知道,下一階段的啟動程式到底存放在哪一個裝置當中。 也就是說 BIOS 需要有一個外部儲存裝置的排序,排在前面的裝置就是優先轉交控制權的裝置。 這種排序叫做啟動排序,也就是我們平時進入 BIOS 介面時能看到的 Boot Sequence。

如果我們沒有進行特殊操作的話,那麼 BIOS 就會按照這個啟動順序將控制權交給下一個儲存裝置。 我們在使用 U 盤光碟之類的裝系統時就是在這裡將啟動順序改變了,將本來要移交給硬碟的控制權交給了 U 盤或者光碟。

第一儲存裝置被啟用後,計算機讀取該裝置的第一個扇區,也就是讀取最前面的 512 個位元組。 如果這 512 個位元組的最後兩個位元組是 0x55 和 0xAA ,表明這個裝置可以用於啟動;如果不是,表明裝置不能用於啟動,控制權於是被轉交給“啟動順序”中的下一個裝置。

這最前面的 512 個位元組,就叫做"主引導記錄"(Master boot record,縮寫為 MBR)。 主引導記錄 MBR 是位於磁碟最前邊的一段引導程式碼。它負責磁碟作業系統對磁碟進行讀寫時分割槽合法性的判別、分割槽引導資訊的定位,它由磁碟作業系統在對硬碟進行初始化時產生的。 硬碟的主引導記錄 MBR 是不屬於任何一個作業系統的,它先於所有的作業系統而被調入記憶體,併發揮作用,然後才將控制權交給主分割槽內的作業系統,並用主分割槽資訊表來管理硬碟。

MBR 只有512個位元組,放不了太多東西。 它的主要作用是,告訴計算機到硬碟的哪一個位置去找作業系統。 我們找到可用的 MBR 後,計算機從 MBR 中讀取前面 446 位元組的機器碼之後,不再把控制權轉交給某一個分割槽,而是執行事先安裝的"啟動管理器"(boot loader),由使用者選擇啟動哪一個作業系統。

  • 載入核心階段

選擇完作業系統後,控制權轉交給作業系統,作業系統的核心首先被載入記憶體。

以 Linux 系統為例,先載入 /boot 目錄下面的 kernel。 核心載入成功後,第一個執行的程式是 /sbin/init。 它根據配置檔案(Debian 系統是 /etc/initab )產生 init 程序。 這是 Linux 啟動後的第一個程序,pid 程序編號為 1,其他程序都是它的後代。

然後,init 執行緒載入系統的各個模組,比如:視窗程式和網路程式,直至執行 /bin/login 程式,跳出登入介面,等待使用者輸入使用者名稱和密碼。

至此,全部啟動過程完成。

Android 手機的啟動過程

Android 系統雖然也是基於 Linux 系統的,但是由於 Android 屬於嵌入式裝置,並沒有像 PC 那樣的 BIOS 程式。 取而代之的是 Bootloader —— 系統啟動載入器。 它類似於 BIOS,在系統載入前,用以初始化硬體裝置,建立記憶體空間的映像圖,為最終呼叫系統核心準備好環境。 在 Android 裡沒有硬碟,而是 ROM,它類似於硬碟存放作業系統,使用者程式等。 ROM 跟硬碟一樣也會劃分為不同的區域,用於放置不同的程式,在 Android 中主要劃分為一下幾個分割槽:

  • /boot:存放載入程式,包括核心和記憶體操作程式
  • /system:相當於電腦c盤,存放Android系統及系統應用
  • /recovery:恢復分割槽,可以進入該分割槽進行系統恢復
  • /data:使用者資料區,包含了使用者的資料:聯絡人、簡訊、設定、使用者安裝的程式
  • /cache:安卓系統快取區,儲存系統最常訪問的資料和應用程式
  • /misc:包含一些雜項內容,如系統設定和系統功能啟用禁用設定
  • /sdcard:使用者自己的儲存區,可以存放照片,音樂,視訊等檔案

那麼 Bootloader 是如何被載入的呢?跟 PC 啟動過程類似,當開機通電時首先會載入 Bootloader,Bootloader 會讀取 ROM 找到作業系統並將 Linux 核心載入到 RAM 中。

當 Linux 核心啟動後會初始化各種軟硬體環境,載入驅動程式,掛載根檔案系統,Linux 核心載入的最後階段會啟動執行第一個使用者空間程序 init 程序。

init 程序

init 是 Linux 系統中使用者空間的第一個程序(pid=1),Kernel 啟動後會呼叫 /system/core/init/Init.cpp 的 main() 方法。

  • Init.main()

首先初始化 Kernel log,建立一塊共享的記憶體空間,載入 /default.prop 檔案,解析 init.rc 檔案。

init.rc 檔案

init.rc 檔案是 Android 系統的重要配置檔案,位於 /system/core/rootdir/ 目錄中。 主要功能是定義了系統啟動時需要執行的一系列 action 及執行特定動作、設定環境變數和屬性和執行特定的 service。

init.rc 指令碼檔案配置了一些重要的服務,init 程序通過建立子程序啟動這些服務,這裡建立的 service 都屬於 native 服務,執行在 Linux 空間,通過 socket 向上層提供特定的服務,並以守護程序的方式執行在後臺。

通過 init.rc 腳本系統啟動了以下幾個重要的服務:

  • service_manager:啟動 binder IPC,管理所有的 Android 系統服務
  • mountd:裝置安裝 Daemon,負責裝置安裝及狀態通知
  • debuggerd:啟動 debug system,處理除錯程序的請求
  • rild:啟動 radio interface layer daemon 服務,處理電話相關的事件和請求
  • media_server:啟動 AudioFlinger,MediaPlayerService 和 CameraService,負責多媒體播放相關的功能,包括音視訊解碼
  • surface_flinger:啟動 SurfaceFlinger 負責顯示輸出
  • zygote:程序孵化器,啟動 Android Java VMRuntime 和啟動 systemserver,負責 Android 應用程序的孵化工作

在這個階段你可以在裝置的螢幕上看到 “Android” logo 了。

以上工作執行完,init 程序就會進入 loop 狀態。

service_manager 程序

ServiceManager 是 Binder IPC 通訊過程中的守護程序,本身也是一個 Binder 服務。ServiceManager 程序主要是啟動 Binder,提供服務的查詢和註冊。

具體過程詳見 Binder:

相關推薦

Android Activity應用視窗建立過程分析

前言 所謂的視窗(Window)就是一個顯示在手機螢幕上視覺化檢視的一片區域。在Android中視窗是一個抽象的概念,每一個Activity就對應著一個視窗,而所有的視窗都是由檢視(View)來呈現,而我們知道View構成的一個樹形結構的檢視就組成了一個Activity的介面了。在Android

Android應用程式視窗Activity)的執行上下文環境(Context)的建立過程分析

                        在前文中,我們簡要介紹了Android應用程式視窗的框架。Android應用程式視窗在執行的過程中,需要訪問一些特定的資源或者類。這些特定的資源或者類構成了Android應用程式的執行上下文環境,Android應用程式視窗可以通過一個Context介面來訪問它,

Android應用程式視窗Activity)的檢視物件(View)的建立過程分析

       從前文可知道,每一個Activity元件都有一個關聯的Window物件,用來描述一個應用程式視窗。每一個應用程式視窗內部又包含有一個View物件,用來描述應用程式視窗的檢視。應用程式視窗檢視是真正用來實現UI內容和佈局的,也就是說,每一個Activity元件的U

Android應用程式視窗Activity)的視窗物件(Window)的建立過程分析

       在前文中,我們分析了Android應用程式視窗的執行上下文環境的建立過程。由此可知,每一個Activity元件都有一個關聯的ContextImpl物件,同時,它還關聯有一個Window物件,用來描述一個具體的應用程式視窗。由此又可知,Activity只不過是一個

Android AMS(四) Activity視窗物件(Window)的建立過程分析

在Android AMS(二) App啟動過程之onCreate中講到,在activity到onCreate狀態前,會呼叫Activity.java-->attach()方法 final void attach(Context context, ActivityThread aTh

Android原始碼解析之應用程式資源管理器(Asset Manager)的建立過程分析

轉載自:https://blog.csdn.net/luoshengyang/article/details/8791064 我們分析了Android應用程式資源的編譯和打包過程,最終得到的應用程式資源就與應用程式程式碼一起打包在一個APK檔案中。Android應用程式在執行的過程中,是通過一個

轉自 老羅 Android應用程式資源管理器(Asset Manager)的建立過程分析

原文地址在 http://blog.csdn.net/luoshengyang/article/details/8791064 原創老羅,轉載請說明     在前面一篇文章中,我們分析了Android應用程式資源的編譯和打包過程,最終得到的應用程式資源就與

Android AMS(五) Activity的檢視物件(View)的建立過程分析

從Android AMS(四) Activity的視窗物件(Window)的建立過程分析知道,每一個Activity元件都有一個關聯的Window物件,用來描述一個應用程式視窗。每一個應用程式視窗內部又包含有一個View物件,用來描述應用程式視窗的檢視。應用程式視窗檢視是真正用來實現UI內容和佈局的

Android原始碼解析之應用程式在新的程序中啟動新的Activity的方法和過程分析

轉載自:http://blog.csdn.net/luoshengyang/article/details/6720261        前面我們在分析Activity啟動過程的時候,看到同一個應用程式的Activity一般都是在同一個程序中啟動,事實上,A

Android應用冷啟動過程分析和優化方案你瞭解多少

你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚至更久的白屏(也可能是黑屏)時間才能進入主介面呢?這其實是安卓應用常見的冷熱啟動問題。本文就和大家一起聊聊冷熱啟動方式和啟動頁的體驗優化方案。 啟動方式 安卓應用的啟動方式分為三種:冷啟動

Android 你應該知道的的應用冷啟動過程分析和優化方案

你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚至更久的白屏(也可能是黑屏)時間才能進入主介面呢?這其實是安卓應用常見的冷熱啟動問題。本文就和大家一起聊聊冷熱啟動方式和啟動頁的體驗優化方案。 啟動方式

Android入門之從輸入裝置中獲取訊息——視窗建立過程

        上文講到訊息獲取過程,本文來詳細看程式碼。         我們說視窗建立時會建立本地的ViewRoot,然後呼叫WmS的addWindow方法,所以我們首先來看看WmS的程式碼。         你會看到frameworks/base/services/j

Android原始碼解析Window系列第(一)篇---Window的基本認識和Activity的Window建立過程

您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關係,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象

android應用冷啟動過程分析與優化過程

http://yifeng.studio/2016/11/15/android-optimize-for-cold-start/?utm_source=tuicool&utm_medium=referral 你有沒有發現,點選安卓手機桌面上的App圖示時,有時候應用馬上進入主介面,有時候要經歷好幾秒甚

[Android] bindService的binder通訊過程分析

關於bindService方法 public class ContextWrapper extendsContext {    Context mBase;      public ContextWrapper(Conte

程序建立過程分析NtCreateProcess-NtCreateProcessEx-PspCreateProcess

轉載自:http://www.blogfshare.com/createprocess-analyse.html 在核心中,windows建立一個程序的過程是從NtCreateProcess函式開始的。找到這個函式,發現它只是簡單地對引數稍作處理,然後把建立程序的任務交給NtCreateProc

OkHttp3的連線池及連線建立過程分析

如我們前面在 OkHttp3 HTTP請求執行流程分析 中的分析,OkHttp3通過Interceptor鏈來執行HTTP請求,整體的執行過程大體如下: OkHttp Flow 這些Interceptor中每一個的職責,這裡不再贅述。 在OkHttp3中,Str

webrtc 點對點會話建立過程分析

關於 webrtc 建立點對點連線的文章很多,其中都提到了如何利用 stun 伺服器獲取本機的公網地址,本文側重區域網(兩臺裝置之間可以直接 ping 通)下webrtc 點對點連線建立問題分析。 1.區域網內連線建立過程 瞭解過 webrtc 的都知道,要在公

Window視窗建立過程

Win32專案—用C語言寫的。。。 #include<windows.h> #include "stdio.h" /* 1.定義入口函式winMain() 2.建立一個視窗 A.設計視窗類WNDCLASS(給成員變數賦值) B.註冊視窗類 C.建

Chromium硬體加速渲染的OpenGL上下文繪圖表面建立過程分析

       GPU命令需要在OpenGL上下文中執行。每一個OpenGL上下文都關聯有一個繪圖表面,GPU命令就是作用在繪圖表面上的。不同用途的OpenGL上下文關聯的繪圖表面不一樣,例如用於離屏渲染的OpenGL上下文關聯的繪圖表面可以用Pbuffer描述,而用於螢幕渲染