Android高級UI---XML及資源的加載流程和源碼分析

一、加載layout 布局文件的邏輯:

image-20210630133727716.png

1、初始化窗口及根布局:

1.1)從activity加載布局文件的過程分析,并來收集每一個view的屬性名和value,ActivityThread 類中的performLaunchActivity()方法來啟動activity

首先創(chuàng)建Activity

ContextImpl appContext = createBaseContextForActivity(r);
                        Activity activity = null;
                        java.lang.ClassLoader cl = appContext.getClassLoader();
                                    activity = mInstrumentation.newActivity(
                                                   cl, component.getClassName(), r.intent);

1.2) 調(diào)用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.referrer, r.voiceInteractor, window, r.configCallback,
                                              r.assistToken);

1.3) 進入Activity 類 找到attach()方法 他會創(chuàng)建一個phoneWindow的對象

mWindow = new PhoneWindow(this, window, activityConfigCallback);

14) 創(chuàng)建后phoneWindow后,我們來看activity的setContentView()方法:

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

1.5) 這里就是調(diào)用的上一步創(chuàng)建好的phoneWindow的setContentView()方法:

  public void setContentView(int layoutResID) {
       if (mContentParent == null) {
             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 {
              mLayoutInflater.inflate(layoutResID, mContentParent);
       }
}

1.6) 繼續(xù)進入到:installDecor(); 先生成一個 DecorView mDecor;而這個DecorView 其實就是一個FrameLayout

mDecor = generateDecor(-1);
//然后將這個mDecor作為根傳遞到generateLayout方法中,
ViewGroup mContentParent = generateLayout(mDecor);

1.7) 這里會生成一個布局進入:generateLayout(mDecor);然后找到系統(tǒng)資源文件中的布局id,并生成根視圖返回

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

截止到現(xiàn)在,我們已經(jīng)知道activity的窗口已經(jīng)已經(jīng)好了,而且在窗口給我們添加了一個默認的布局frameLayout.這些工作都是系統(tǒng)在幫我們做,加下來看看如何加載我們自己定義的布局。

2、加載自定義的布局文件setContentView()

找到了mContentParent的創(chuàng)建的地方后,我們繼續(xù)回到PhoneWindow類的setContentView()中

image-20210630134116133.png

2.1)mLayoutInflater.inflate(layoutResID, mContentParent);

說明:

1)這里把我們上一步創(chuàng)建好的mContentParent傳遞進去,

2)layoutResID 就是我們在自己的activity中的布局文件ID

3)一個參數(shù)attachToRoot的作用: true代碼動態(tài)加載 false 代表將我們自己的根布局的參數(shù)添加的DecorView中去

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
                // Temp is the root view that was found in the xml
                 final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                        // Create layout params that match root, if supplied
                      params = root.generateLayoutParams(attrs);
                      
                      if (!attachToRoot) {
                             // Set the layout params for temp if we are not
                             // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                      }
                }

2.2)繼續(xù)進createViewFromTag()方法中去

說明:

1) 此處就是LayoutInflater 幫助我們創(chuàng)建自定義View的核心代碼區(qū)域,兩種方式來創(chuàng)建:一種是Factory的方式 ;一種是infalter的createView()反射方式

 View view = tryCreateView(parent, name, context, attrs);
  if (view == null) {
        final Object lastContext = mConstructorArgs[0];
         mConstructorArgs[0] = context;
         try {
               if (-1 == name.indexOf('.')) {
                    view = onCreateView(context, parent, name, attrs);
               } else {
                    view = createView(context, name, null, attrs);
               }
          } finally {
               mConstructorArgs[0] = lastContext;
          }
 }

2.3)我們先進入tryCreateView()方法看看

特別強調(diào):

在中間View的創(chuàng)建是通過Factory工程模式來進行創(chuàng)建的,而Factory是給我們定義的一個接口,由此可見我們也可以來通過自定義這個Factory的方式來實現(xiàn)工廠的onCreatView()方法,方法中抄襲系統(tǒng)的createView()的實現(xiàn)來完成視圖View的創(chuàng)建。

 public final View tryCreateView(@Nullable View parent, @NonNull String name,
                         @NonNull Context context,
                         @NonNull AttributeSet attrs) {
                         if (name.equals(TAG_1995)) {
                             // Let's party like it's 1995!
                             return new BlinkLayout(context, attrs);
                         }
                 
                         View view;
                         if (mFactory2 != null) {
                             view = mFactory2.onCreateView(parent, name, context, attrs);
                         } else if (mFactory != null) {
                             view = mFactory.onCreateView(name, context, attrs);
                         } else {
                             view = null;
                         }
                 
                         if (view == null && mPrivateFactory != null) {
                             view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                         }
                 
                         return view;
                     }

2.4)View的創(chuàng)建都是通過調(diào)用LayoutInflater.java中的creatView()方法來完成的

 public final View createView(@NonNull Context viewContext, @NonNull String name,
                                  @Nullable String prefix, @Nullable AttributeSet attrs)
                                  throws ClassNotFoundException, InflateException {
                              Objects.requireNonNull(viewContext);
                              Objects.requireNonNull(name);
                              Constructor<? extends View> constructor = sConstructorMap.get(name);
                              if (constructor != null && !verifyClassLoader(constructor)) {
                                  constructor = null;
                                  sConstructorMap.remove(name);
                              }
                              Class<? extends View> clazz = null;
                      
                              try {
                                  Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
                      
                                  if (constructor == null) {
                                      // Class not found in the cache, see if it's real, and try to add it
                                      clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                              mContext.getClassLoader()).asSubclass(View.class);
                      
                                      if (mFilter != null && clazz != null) {
                                          boolean allowed = mFilter.onLoadClass(clazz);
                                          if (!allowed) {
                                              failNotAllowed(name, prefix, viewContext, attrs);
                                          }
                                      }
                                      constructor = clazz.getConstructor(mConstructorSignature);//此處可以說明所有的自定義都會調(diào)用兩個參數(shù)的構(gòu)造函數(shù)
                                      constructor.setAccessible(true);
                                      sConstructorMap.put(name, constructor);
                                  } else {
                                      // If we have a filter, apply it to cached constructor
                                      if (mFilter != null) {
                                          // Have we seen this name before?
                                          Boolean allowedState = mFilterMap.get(name);
                                          if (allowedState == null) {
                                              // New class -- remember whether it is allowed
                                              clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                                      mContext.getClassLoader()).asSubclass(View.class);
                      
                                              boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                                              mFilterMap.put(name, allowed);
                                              if (!allowed) {
                                                  failNotAllowed(name, prefix, viewContext, attrs);
                                              }
                                          } else if (allowedState.equals(Boolean.FALSE)) {
                                              failNotAllowed(name, prefix, viewContext, attrs);
                                          }
                                      }
                                  }
                      
                                  Object lastContext = mConstructorArgs[0];
                                  mConstructorArgs[0] = viewContext;
                                  Object[] args = mConstructorArgs;
                                  args[1] = attrs;
                      
                                  try {
                                        //此處就創(chuàng)建好了我們自己定義的layoutView
                                      final View view = constructor.newInstance(args);
                                      if (view instanceof ViewStub) {
                                          // Use the same context when inflating ViewStub later.
                                          final ViewStub viewStub = (ViewStub) view;
                                          viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                                      }
                                      return view;
                                  } finally {
                                      mConstructorArgs[0] = lastContext;
                                  }
                              } finally {
                                  Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                              }
                          }

通過上述createView我們可以發(fā)現(xiàn),自定義的view是通過反射來進行加載的,最后將通過反射創(chuàng)建出一個view的實例并返回回去

3、加載資源文件(ActivityThread.java 中的handleBindApplication(AppBindData data)方法中開始)

3.1) 創(chuàng)建一個儀表類

mInstrumentation = new Instrumentation();

3.2) 創(chuàng)建application

app = data.info.makeApplication(data.restrictedBackupMode, null);

3.2.1 )進入makeApplication()中,LoadedApk.java中的makeApplication方法中去,進行上下文創(chuàng)建:

  ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);

3.2.2) 創(chuàng)建上下文的方法

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

3.2.3) 在創(chuàng)建上下文是需要配置context.setResources()設(shè)置資源信息,此時傳入的packageInfo.getResources()

 public Resources getResources() {
                         if (mResources == null) {
                             final String[] splitPaths;
                             try {
                                 splitPaths = getSplitPaths(null);
                             } catch (NameNotFoundException e) {
                                 // This should never fail.
                                 throw new AssertionError("null split not found");
                             }
                 
                             mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                                     splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                                     Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
                                     getClassLoader(), null);
                         }
                         return mResources;
                     }

3.2.4) 接著進入 ResourcesManager.getInstance()的getResources()方法中去

public @Nullable Resources getResources() {
                        try {
                            Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
                            final ResourcesKey key = new ResourcesKey(
                                    resDir,
                                    splitResDirs,
                                    overlayDirs,
                                    libDirs,
                                    displayId,
                                    overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
                                    compatInfo,
                                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
                            classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
                
                            if (activityToken != null) {
                                rebaseKeyForActivity(activityToken, key);
                            }
                
                            return createResources(activityToken, key, classLoader);
                        } finally {
                            Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
                        }
                    }

3.2.5) 接著進入createResources(),然后再進入findOrCreateResourcesImplForKeyLocked中去

private @Nullable Resources createResources(@Nullable IBinder activityToken,
                             @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
                         synchronized (this) {
                             if (DEBUG) {
                                 Throwable here = new Throwable();
                                 here.fillInStackTrace();
                                 Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
                             }
                 
                             ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
                             if (resourcesImpl == null) {
                                 return null;
                             }
                 
                             if (activityToken != null) {
                                 return createResourcesForActivityLocked(activityToken, classLoader,
                                         resourcesImpl, key.mCompatInfo);
                             } else {
                                 return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
                             }
                         }
                     }
                     
  
   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;
    }

3.2.6) 接著看看ResourcesImpl的實現(xiàn):

private @Nullable ResourcesImpl createResourcesImpl();
                   
                           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);
                   
                           if (DEBUG) {
                               Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
                           }
                           return impl;
                       }           

3.2.7) 進入createAssetManager()方法,創(chuàng)建好了AssetManager的對象后返回該對象,用于生成ResourcesImpl 構(gòu)建對象,這樣就可以得到我們apk包中的資源了

protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
        final AssetManager.Builder builder = new AssetManager.Builder();

        // resDir can be null if the 'android' package is creating a new Resources object.
        // This is fine, since each AssetManager automatically loads the 'android' package
        // already.
        if (key.mResDir != null) {
            try {
                builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
                        false /*overlay*/));
            } catch (IOException e) {
                Log.e(TAG, "failed to add asset path " + key.mResDir);
                return null;
            }
        }

        if (key.mSplitResDirs != null) {
            for (final String splitResDir : key.mSplitResDirs) {
                try {
                    builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
                            false /*overlay*/));
                } catch (IOException e) {
                    Log.e(TAG, "failed to add split asset path " + splitResDir);
                    return null;
                }
            }
        }

        if (key.mLibDirs != null) {
            for (final String libDir : key.mLibDirs) {
                if (libDir.endsWith(".apk")) {
                    // Avoid opening files we know do not have resources,
                    // like code-only .jar files.
                    try {
                        builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
                                false /*overlay*/));
                    } catch (IOException e) {
                        Log.w(TAG, "Asset path '" + libDir +
                                "' does not exist or contains no resources.");

                        // continue.
                    }
                }
            }
        }

        if (key.mOverlayDirs != null) {
            for (final String idmapPath : key.mOverlayDirs) {
                try {
                    builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
                            true /*overlay*/));
                } catch (IOException e) {
                    Log.w(TAG, "failed to add overlay path " + idmapPath);

                    // continue.
                }
            }
        }

        if (key.mLoaders != null) {
            for (final ResourcesLoader loader : key.mLoaders) {
                builder.addLoader(loader);
            }
        }

        return builder.build();
    }

到目前為止,我們已經(jīng)找到了ResoucesImpl的實現(xiàn),完成了上面的工作后,就會回調(diào)到我們自己app的application的OnCreate()方法中了。

mInstrumentation.callApplicationOnCreate(app);

二、使用加載好的資源

image-20210630142700047.png

2.1) resources.getDrawable()

public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
                    final TypedValue value = obtainTempTypedValue();
                    try {
                        final ResourcesImpl impl = mResourcesImpl;
                        impl.getValueForDensity(id, density, value, true);
                        return loadDrawable(value, id, density, theme);
                    } finally {
                        releaseTempTypedValue(value);
                    }
                }

2.2) resources.getString()/resources.getText()

  @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
                        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
                        if (res != null) {
                            return res;
                        }
                        throw new NotFoundException("String resource ID #0x"
                                + Integer.toHexString(id));
                    }

2.3) resources.getColor()

 public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
                    final TypedValue value = obtainTempTypedValue();
                    try {
                        final ResourcesImpl impl = mResourcesImpl;
                        impl.getValue(id, value, true);
                        if (value.type >= TypedValue.TYPE_FIRST_INT
                                && value.type <= TypedValue.TYPE_LAST_INT) {
                            return value.data;
                        } else if (value.type != TypedValue.TYPE_STRING) {
                            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
                        }
            
                        final ColorStateList csl = impl.loadColorStateList(this, value, id, theme);
                        return csl.getDefaultColor();
                    } finally {
                        releaseTempTypedValue(value);
                    }
                }

2.4)resources.getAnimation()

XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
                        throws NotFoundException {
                    final TypedValue value = obtainTempTypedValue();
                    try {
                        final ResourcesImpl impl = mResourcesImpl;
                        impl.getValue(id, value, true);
                        if (value.type == TypedValue.TYPE_STRING) {
                            return loadXmlResourceParser(value.string.toString(), id,
                                    value.assetCookie, type);
                        }
                        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                                + " type #0x" + Integer.toHexString(value.type) + " is not valid");
                    } finally {
                        releaseTempTypedValue(value);
                    }
                }

2.5)resources.getDimension()

public float getDimension(@DimenRes int id) throws NotFoundException {
                    final TypedValue value = obtainTempTypedValue();
                    try {
                        final ResourcesImpl impl = mResourcesImpl;
                        impl.getValue(id, value, true);
                        if (value.type == TypedValue.TYPE_DIMENSION) {
                            return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics());
                        }
                        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                                + " type #0x" + Integer.toHexString(value.type) + " is not valid");
                    } finally {
                        releaseTempTypedValue(value);
                    }
                }

從上面的代碼我們可以看到,所有資源的獲取都會最終調(diào)用到AssetManager的getResourceValue()方法中去

boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
              boolean resolveRefs) {
          Objects.requireNonNull(outValue, "outValue");
          synchronized (this) {
              ensureValidLocked();
              final int cookie = nativeGetResourceValue(
                      mObject, resId, (short) densityDpi, outValue, resolveRefs);
              if (cookie <= 0) {
                  return false;
              }
 
              // Convert the changing configurations flags populated by native code.
              outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
                      outValue.changingConfigurations);
  
              if (outValue.type == TypedValue.TYPE_STRING) {
                  outValue.string = getPooledStringForCookie(cookie, outValue.data);
              }
              return true;
          }
      }   
      

完成上面的步驟后,接下來是回調(diào)回我們app的application的onCreate()方法中:

mInstrumentation.callApplicationOnCreate(app);

以上就是加載我們的布局和資源文件的全部過程已經(jīng)相關(guān)方法的調(diào)用順序。有了上面的基礎(chǔ),在實現(xiàn)換膚功能時,就有了初步思路了。(具體的換膚思路在后面的實戰(zhàn)換膚文章中詳說)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容