首頁 > 軟體

Android Activity View載入與繪製流程深入刨析原始碼

2022-08-26 18:05:12

1.App的啟動流程,從startActivity到Activity被建立。

這個流程主要是ActivityThread和ActivityManagerService之間通過binder進行通訊來完成。

ActivityThread可以拿到AMS 的BinderProxy。AMS可以拿到ActivityThread的BinderProxy ApplicationThread。這樣雙方就可以互相通訊了。

當ApplicationThread 接收到AMS的Binder呼叫後,會通過handler機制來執行對應的操作。

可以這樣說handler和binder是android framework重要的兩塊基石。

而Activity的建立就是在ActivityThread中呼叫handleLaunchActivity方法實現。

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         //建立一個Activity,並呼叫生命週期onCreate方法
         Activity a = performLaunchActivity(r, customIntent);
          if (a != null) {
             //如果Activity成功建立,則會呼叫生命週期onResume方法。
             handleResumeActivity(r.token, false, r.isForward,
                      !r.activity.mFinished && !r.startsNotResumed);
          }
  }

2.接下來看performLaunchActivity。建立一個Activity。

如果Activity建立成功,先呼叫activity.attach(),在這個方法中,建立了Activity的PhoneWindow範例。

然後mInstrumentation.callActivityOnCreate,呼叫了Activity的onCreate生命週期方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
          Activity activity = null;
                try {
                    // Instrumentation mInstrumentation;
                    //拿到類載入器,最後會通過反射的方式建立Activity物件
                    //(Activity) cl.loadClass(className).newInstance();
                    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
                    activity = mInstrumentation.newActivity(
                            cl, component.getClassName(), r.intent);
                } catch (Exception e) {
                    if (!mInstrumentation.onException(activity, e)) {
                        throw new RuntimeException(
                            "Unable to instantiate activity " + component
                            + ": " + e.toString(), e);
                    }
                }
                if(activity!=null){
                //呼叫Activity.attach,
                 activity.attach(appContext, this, getInstrumentation(), r.token,
                                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                                        r.embeddedID, r.lastNonConfigurationInstances, config,
                                        r.voiceInteractor);
                }
                //通過這個方法呼叫了Activity的onCreate方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                    // activity.performCreate(icicle, persistentState);
                    // onCreate(icicle, persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                    //activity.performCreate(icicle);
                    // onCreate(icicle);
                }
          return activity;
}
 final void attach(...){
     //初始化了window的子類 初始化了PhoneWindow,PhoneWindow中有一個Decoview物件
     mWindow = new PhoneWindow(this, window, activityConfigCallback);
     mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
     //mWindowManager 是WindowManagerImpl的範例。
     mWindowManager = mWindow.getWindowManager();
 }

3.setContentView,佈局載入流程:

在Activity.onCreate方法中,我們通過setContentView(R.layout.activity_main);就能在介面顯示,那android是怎麼做到的?

1)呼叫installDecor(),初始化Decorview和mContentParent。

2)mLayoutInflater.inflate(),將佈局檔案,解析成android中對應的View。

//getWindow拿到的是PhoneWindow
   public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
   }
 @Override
 public void setContentView(int layoutResID) {
    if (mContentParent == null) {
          //初始化Decorview和mContentParent
          installDecor();
      }
    //載入並解析傳進來的佈局檔案,並add到mContentParent上。
    //這個操作比較耗時,因為要從xml檔案中解析,然後再通過反射的方式生成Java中的View物件。
    //所以在recyclerView和listView中要對這一步進行快取優化,
    mLayoutInflater.inflate(layoutResID, mContentParent);
 }

先看installDecor(),通過generateDecor() new了一個DecoView範例並賦值給了mDecor,

mDecor是PhoneView的一個變數。

//mDecorView是PhoneWindow的一個成員變數
private DecorView mDecor;
private void installDecor() {
     //建立mDecor
     if (mDecor == null) {
         mDecor = generateDecor(-1);
		  //new DecorView(context, featureId, this, getAttributes());
     }
    //建立mContentParent,將建立的DecorView作為引數傳遞
     if (mContentParent == null) {
         mContentParent = generateLayout(mDecor);
     }
}

通過generateLayout(decor) 載入了一個系統的layout檔案,在android.jar--res--layout目錄下。

在mDecor.onResourcesLoaded方法中載入了這個佈局,並新增到了mDecor中。

DecorView繼承自FrameLayout,是一個真正的view。

然後通過findViewbyid,找到了一個ViewGoup,可以看下面的佈局檔案。

ID_ANDROID_CONTENT = com.android.internal.R.id.content; ,並把這個返回出去了。

這個view 就installDecor()方法中的mContentParent()

 protected ViewGroup generateLayout(DecorView decor) {
        int layoutResource;
        //這個佈局檔案就在android.jar--res--layout目錄下。
        layoutResource = R.layout.screen_simple;
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
       // int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
       //這個R.id.content就是定義在screen_simple中的一個FrameLayout
       //  android:id="@android:id/content"
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ....
        return contentParent;
  }
   //將佈局R.layout.screen_simple 載入成view,並新增到DecorView中
  void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ......
         final View root = inflater.inflate(layoutResource, null);
        ......
        // Put it below the color views.
         addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
  }
//R.layout.screen_simple
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:fitsSystemWindows="true"
     android:orientation="vertical">
     <ViewStub android:id="@+id/action_mode_bar_stub"
               android:inflatedId="@+id/action_mode_bar"
               android:layout="@layout/action_mode_bar"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:theme="?attr/actionBarTheme" />
     <FrameLayout
          android:id="@android:id/content"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:foregroundInsidePadding="false"
          android:foregroundGravity="fill_horizontal|top"
          android:foreground="?android:attr/windowContentOverlay" />
 </LinearLayout>

總結:至此 installDecor();已經完成。主要是建立了mDecorView,並載入了一個系統的佈局,R.layout.screen_simple,

將載入得到的View新增到了mDecorView中,並findViewById(R.id.content)的到的View賦值給了mParentContent。

回到setContentView中看第二行程式碼:

layoutResID 就是傳入的佈局檔案id,mContentParent就是載入的系統的佈局檔案中id為“content”的view

mLayoutInflater.inflate(layoutResID, mContentParent);

  //載入並解析傳進來的佈局檔案,並add到mContentParent上。
    mLayoutInflater.inflate(layoutResID, mContentParent);
  public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
         return inflate(resource, root, root != null);
   }
   public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
       final Resources res = getContext().getResources();
        //通過 Resource 得到了一個XML解析器。
       final XmlResourceParser parser = res.getLayout(resource);
       try {
           //解析我們自定義的layout,並新增到mParentContent上。
           //將xml中定義的ViewGroup和View解析成Java物件。
           //這塊程式碼會單獨寫文章講解
           return inflate(parser, root, attachToRoot);
       } finally {
           parser.close();
       }
   }

至此上面的關係可以總結為:

Activity-->PhoneWindow-->mDecorView-->addView(R.layout.screen.simple)-->

R.id.content-->mParentContent-->addView(R.layout.activity.main)

我們自己寫的layout已經新增到了系統的DecorView中。

4.我們知道View有三個重要的方法onMeasure,onLayout,onDraw,

那這些方法是在哪裡呼叫的?我們建立的View是如何新增到螢幕上的呢?

回到handleLaunchActivity方法中,還有一個handleResumeActivity,通過performResumeActivity 會執行Activity的onResume生命週期方法。

   private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
           //建立一個Activity,並呼叫生命週期onCreate方法
           Activity a = performLaunchActivity(r, customIntent);
            if (a != null) {
               //如果Activity成功建立,則會呼叫生命週期onResume方法。
               handleResumeActivity(r.token, false, r.isForward,
                        !r.activity.mFinished && !r.startsNotResumed);
            }
   }
  final void handleResumeActivity(IBinder token,
               boolean clearHide, boolean isForward, boolean reallyResume) {
    //執行Activity onResume生命週期方法
   ActivityClientRecord r = performResumeActivity(token, clearHide);
    if(r!=null){
                final Activity a = r.activity;
                //通過上面程式碼我們知道 window 是PhoneWindow
                r.window = r.activity.getWindow();
                //拿到DecorView
                View decor = r.window.getDecorView();
                //wm 是 WindowManagerImpl
                ViewManager wm = a.getWindowManager();
                   if (a.mVisibleFromClient) {
                         a.mWindowAdded = true;
                         //關鍵程式碼,將decorView 新增到wm中
                         wm.addView(decor, l);
                   }
           }
 }
 public final ActivityClientRecord performResumeActivity(IBinder token,
             boolean clearHide) {
        //執行Activity onStart onResume方法
        ActivityClientRecord r = mActivities.get(token);
        r.activity.performResume();
     return r;
}
//Activity中 onStart onResume 生命週期方法
 final void performResume(boolean followedByPause, String reason) {
         performRestart(true /* start */, reason);
         mInstrumentation.callActivityOnResume(this);
 }
  final void performRestart(boolean start, String reason) {
     if (start) {
         performStart(reason);
      }
  }
public void callActivityOnResume(Activity activity) {
     activity.mResumed = true;
     activity.onResume();
}

執行完onResume方法後:

wm.addView(decor, l);將Activity的DecorView新增到了wm中。

ViewManager wm = a.getWindowManager();

ViewManager 是一個抽象類,範例是WindowManagerImpl。

在WindowManagerImpl中通過單例模式獲取了一個WindowManagerGlobal物件。

既然是單例模式獲取的物件,也就在一個程序,ActivityThread 主程序中 只有一個範例。

//WindowManagerImpl.java addView 方法
//WindowManagerGlobal 是一個單例模式,在ActivityThread 主程序中 只有一個範例。
 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
 }
WindowManagerGlobal.java
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 void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
         ViewRootImpl root;
            ........
              root = new ViewRootImpl(view.getContext(), display);
            .........
           view.setLayoutParams(wparams);
         //將decorView ViewRootImpl 存到集合中
         mViews.add(view);
         mRoots.add(root);
         mParams.add(wparams);
         root.setView(view, wparams, panelParentView);
}

在WindowManagerGlobal中建立了一個ViewRootImpl物件。這是很重要的一個物件。

將傳進來的DecorView設定在了root中,root.setView(view, wparams, panelParentView);

ViewRootImpl.java
  public ViewRootImpl(Context context, Display display) {
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mThread = Thread.currentThread();
  }
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
      // Schedule the first layout -before- adding to the windowmanager,
        //to make sure we do the relayout before receiving
        // any other events from the system.
       //在新增到windowmanager之前進行佈局,確保在收到系統的event之前進行relayout
       // 出發佈局的繪製流程,measure,layout,view 的繪製流程,就是從這來的。
       //這個方法保證了,在新增的螢幕前已經完成了測量、繪製。
        requestLayout();
        try {
            //通過binder和WindowManagerService進行通訊,將view新增到螢幕上。
            // mWindow = new W(this);
            // static class W extends IWindow.Stub {}
            //新增到螢幕的邏輯,稍後寫文章詳細分析。
             res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                        getHostVisibility(), mDisplay.getDisplayId(),
                                        mAttachInfo.mContentInsets, mInputChannel);
        }catch (RemoteException e) {
            throw new RuntimeException("Adding window failed", e);
        }
 }

在setView()方法中有兩句很重要的程式碼。

requestLayout();

res = mWindowSession.addToDisplay()

1) requestLayout()請求佈局,呼叫者行程式碼會執行view的measue,layou,draw方法。

 public void requestLayout() {
     if (!mHandlingLayoutInLayoutRequest) {
         checkThread();
         mLayoutRequested = true;
         scheduleTraversals();
     }
 }

checkThread這有一個很重要的知識點,為啥子執行緒不能修改主執行緒建立的view?

//mThread是在ViewRootImp初始初始化是所在的執行緒。
//在requestLayout時,會獲取當前請求佈局的執行緒。
//如果兩個執行緒不一致就會拋異常,只有原始建立的執行緒,可以修改views
  void checkThread() {
         if (mThread != Thread.currentThread()) {
             throw new CalledFromWrongThreadException(
                     "Only the original thread that created a view hierarchy can touch its views.");
         }
     }

在scheduleTraversals方法中。通過mChoreographer編舞者物件,最後執行了mTraversalRunnable中的方法。這塊程式碼在訊息屏障文章中,詳細分解。

 //開始遍歷
 void scheduleTraversals() {
   if (!mTraversalScheduled) {
     mTraversalScheduled = true;
        //傳送一個同步訊息,在handler機制分析中提過一下,同步屏障。關於這個知識點還會做詳細的分析。
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            //mChoreographer 編舞者類。
            //通過編舞者,執行 runnable中 doTraversal 方法
            mChoreographer.postCallback(
                 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
   }
 }
final class TraversalRunnable implements Runnable {
           @Override
           public void run() {
               doTraversal();
           }
    }
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
            performTraversals();
        }
  }

在TraversalRunnable中執行了doTraversal()方法。在這裡呼叫了三個重要的方法

performMeasure(),performLayout(),performDraw()。

這也就是為什麼View的繪製流程是先呼叫onMeasure,onLayout,後呼叫onDraw的原因。

並且這些寫方法都是在onResume執行才呼叫的。所以,這就是我們想拿到View的寬高,在onResume之前拿不到的原因。

 private void performTraversals() {
       //mView DecorView
       final View host = mView;
      // Ask host how big it wants to be
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        if (didLayout) {
          performLayout(lp, desiredWindowWidth, desiredWindowHeight);
       }
      performDraw();
}
 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
         mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  }
   private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
              int desiredWindowHeight) {
        final View host = mView;
         host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  }
   private void performDraw() {
       draw(fullRedrawNeeded);
       //
  }

到此這篇關於Android Activity View載入與繪製流程深入刨析原始碼的文章就介紹到這了,更多相關Android Activity View內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com