Android View的加載流程(Api 30)

我們都知道Android的界面是通過xml編寫的 然后通過Activity 加載xml的layout文件進(jìn)行顯示缀遍,那么怎么從xml加載到我們的Activity呢?
下面我們一步步了解view的加載流程 舷蒲,看看到底view是怎么創(chuàng)建出來的

一、Android的界面顯示層次

image.png

二、源碼分析-View的加載

1蛤吓、故事要從setContentView開始

在我們的Activity點進(jìn)setContentView方法 我們看到 里面實際上會調(diào)用window的setContentView 方法進(jìn)行l(wèi)ayout的解析

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

2抖锥、我們再看看window的setContentView干了什么

1.PhoneWindow,可以看到碎罚,當(dāng)mContentParent ==null時磅废,調(diào)用installDecor();

 @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        if (mDecor == null) {
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }

generateDecor幫我們new了一個DecorView

    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }

再往下 mDecor創(chuàng)建好了以后 通過 generateLayout 創(chuàng)建了一個mContentParent

 if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
}

generateLayout 中通過我們配置的各種參數(shù)進(jìn)行判斷加載哪個根布局,然后從ID_ANDROID_CONTENT 找到contentParent 并返回
而ID_ANDROID_CONTENT 就是android.R.id.content荆烈,也就是上圖中的最里面一層是一個FrameLayout

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

    protected ViewGroup generateLayout(DecorView decor) {
       // ......省略部分代碼
       // xxxx
       if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        
        }
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
     }

接著我們看我們傳入的layoutId在哪里使用拯勉,mContentParent在上面已經(jīng)創(chuàng)建完成,而我們的layout就添加到mContentParent上面憔购,最后調(diào)用LayoutInflater .inflate 添加我們的layout

    @Override
    public void setContentView(int layoutResID) {
      
        if (mContentParent == null) {
            installDecor();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          //xxx
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...

3.LayoutInflater 宫峦,接著來到LayoutInflater 中的inflate方法 我們發(fā)現(xiàn)最終都會來到這個方法:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 

并且解析屬性


            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

然后通過createViewFromTag 創(chuàng)建view

  if (TAG_MERGE.equals(name)) {
        rInflate(parser, root, inflaterContext, attrs, false);
   } else {                  
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        ViewGroup.LayoutParams params = null;       
   }

createViewFromTag 中經(jīng)過條件判斷 ,最終會調(diào)用createView() 反射創(chuàng)建最終我們得到的view


    public final View createView(@NonNull Context viewContext, @NonNull String name,
                                 @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;
        if (constructor == null) {
                  clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        }
        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        try {
            final View view = constructor.newInstance(args);
            return view;
        } finally {
            mConstructorArgs[0] = lastContext;
        }
    }

到這里我們的view就創(chuàng)建出來了

三玫鸟、源碼分析-資源的加載

1导绷、application的創(chuàng)建

 private void handleBindApplication(AppBindData data) {
        mInstrumentation = new Instrumentation();
    Application app = data.info.makeApplication(data.restrictedBackupMode, null);
}

先創(chuàng)建了個appContext 然后通過mInstrumentation 把application創(chuàng)建出來

 public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
                ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

            NetworkSecurityConfigProvider.handleNewApplication(appContext);
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
}

2、context的創(chuàng)建

進(jìn)入createAppContext(), 創(chuàng)建了個ContextImpl屎飘,給它設(shè)置recourse這個recourse就是要加載的資源

 static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
            String opPackageName) {
     ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,
                0, null, opPackageName);
        context.setResources(packageInfo.getResources());
     return context;
}

3妥曲、Resources的創(chuàng)建

進(jìn)入LoadedApk.getResources(), 經(jīng)過條件判斷,通過ResourcesManager.getResources()

 public Resources getResources() {
        if (mResources == null) {
            final String[] splitPaths;
               mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                    splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                    Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                    getClassLoader(), null);
        }
        return mResources;
    }

繼續(xù)進(jìn)入getRecourse钦购,通過findOrCreateResourcesImplForKeyLocked方法獲取到資源

private @Nullable Resources createResources(@Nullable IBinder activityToken,@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) synchronized (this) {
              ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);           
    }

然后判斷空 為空 調(diào)用createResourcesImpl 創(chuàng)建資源

 private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key) {
        ResourcesImpl impl = findResourcesImplForKeyLocked(key);
        if (impl == null) {
            impl = createResourcesImpl(key);
            if (impl != null) {
                mResourceImpls.put(key, new WeakReference<>(impl));
            }
        }
        return impl;
    }

進(jìn)入createResourcesImpl 創(chuàng)建了個AssetManager 資源即是通過AssetManager 獲取

  private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
        final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
        daj.setCompatibilityInfo(key.mCompatInfo);
        final AssetManager assets = createAssetManager(key);
        if (assets == null) {
            return null;
        }
        final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
        final Configuration config = generateConfig(key, dm);
        final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);

        return impl;
    }

=創(chuàng)建出AssetManager后調(diào)用addApkAssets 添加不同的資源

 protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        final AssetManager.Builder builder = new AssetManager.Builder();
        // already.
        if (key.mResDir != null) {
                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
        }


       if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                    builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/, false /*overlay*/));              
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {        
                        builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
                                false /*overlay*/));              
            }
        }
 return builder.build();

最后 調(diào)用 AssetManager.Builder.build把system資源檐盟、loader資源、user資源等整合在一起 通過AssetManager.nativeSetApkAssets() 加載資源

    public AssetManager build() {
        final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
        final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
        final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
        for (int i = mLoaders.size() - 1; i >= 0; i--) {
            final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
            for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
                final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
                if (uniqueLoaderApkAssets.add(apkAssets)) {
                    loaderApkAssets.add(0, apkAssets);
                }
            }
        }
        final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
                + loaderApkAssets.size();
        final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
        System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);

        for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
            apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
        }
        for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
            apkAssets[i + systemApkAssets.length  + mUserApkAssets.size()] =
                    loaderApkAssets.get(i);
        }
        final AssetManager assetManager = new AssetManager(false /*sentinel*/);
        assetManager.mApkAssets = apkAssets;
        AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,

        return assetManager;
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末押桃,一起剝皮案震驚了整個濱河市葵萎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唱凯,老刑警劉巖羡忘,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異磕昼,居然都是意外死亡壳坪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門掰烟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爽蝴,“玉大人,你說我怎么就攤上這事纫骑⌒牵” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵先馆,是天一觀的道長发框。 經(jīng)常有香客問我,道長煤墙,這世上最難降的妖魔是什么梅惯? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任宪拥,我火速辦了婚禮,結(jié)果婚禮上铣减,老公的妹妹穿的比我還像新娘她君。我一直安慰自己,他們只是感情好葫哗,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布缔刹。 她就那樣靜靜地躺著,像睡著了一般劣针。 火紅的嫁衣襯著肌膚如雪校镐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天捺典,我揣著相機(jī)與錄音鸟廓,去河邊找鬼。 笑死襟己,一個胖子當(dāng)著我的面吹牛肝箱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播稀蟋,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼煌张,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了退客?” 一聲冷哼從身側(cè)響起骏融,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萌狂,沒想到半個月后档玻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡茫藏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年误趴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片务傲。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡凉当,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出售葡,到底是詐尸還是另有隱情看杭,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布挟伙,位于F島的核電站楼雹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贮缅,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一榨咐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谴供,春花似錦块茁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胃夏。三九已至轴或,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仰禀,已是汗流浹背照雁。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留答恶,地道東北人饺蚊。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像悬嗓,于是被迫代替她去往敵國和親污呼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容