Android高级UI---XML及资源的加载流程和源码分析

一、加载layout 布局文件的逻辑:

image-20210630133727716.png

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

1.1)从activity加载布局文件的过程分析,并来收集每一个view的属性名和value,ActivityThread 类中的performLaunchActivity()方法来启动activity

首先创建Activity

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

1.2) 调用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()方法 他会创建一个phoneWindow的对象

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

14) 创建后phoneWindow后,我们来看activity的setContentView()方法:

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

1.5) 这里就是调用的上一步创建好的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) 继续进入到:installDecor(); 先生成一个 DecorView mDecor;而这个DecorView 其实就是一个FrameLayout

mDecor = generateDecor(-1);
//然后将这个mDecor作为根传递到generateLayout方法中,
ViewGroup mContentParent = generateLayout(mDecor);

1.7) 这里会生成一个布局进入:generateLayout(mDecor);然后找到系统资源文件中的布局id,并生成根视图返回

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

截止到现在,我们已经知道activity的窗口已经已经好了,而且在窗口给我们添加了一个默认的布局frameLayout.这些工作都是系统在帮我们做,加下来看看如何加载我们自己定义的布局。

2、加载自定义的布局文件setContentView()

找到了mContentParent的创建的地方后,我们继续回到PhoneWindow类的setContentView()中

image-20210630134116133.png

2.1)mLayoutInflater.inflate(layoutResID, mContentParent);

说明:

1)这里把我们上一步创建好的mContentParent传递进去,

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

3)一个参数attachToRoot的作用: true代码动态加载 false 代表将我们自己的根布局的参数添加的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)继续进createViewFromTag()方法中去

说明:

1) 此处就是LayoutInflater 帮助我们创建自定义View的核心代码区域,两种方式来创建:一种是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()方法看看

特别强调:

在中间View的创建是通过Factory工程模式来进行创建的,而Factory是给我们定义的一个接口,由此可见我们也可以来通过自定义这个Factory的方式来实现工厂的onCreatView()方法,方法中抄袭系统的createView()的实现来完成视图View的创建。

 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的创建都是通过调用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);//此处可以说明所有的自定义都会调用两个参数的构造函数
                                      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 {
                                        //此处就创建好了我们自己定义的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我们可以发现,自定义的view是通过反射来进行加载的,最后将通过反射创建出一个view的实例并返回回去

3、加载资源文件(ActivityThread.java 中的handleBindApplication(AppBindData data)方法中开始)

3.1) 创建一个仪表类

mInstrumentation = new Instrumentation();

3.2) 创建application

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

3.2.1 )进入makeApplication()中,LoadedApk.java中的makeApplication方法中去,进行上下文创建:

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

3.2.2) 创建上下文的方法

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) 在创建上下文是需要配置context.setResources()设置资源信息,此时传入的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的实现:

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()方法,创建好了AssetManager的对象后返回该对象,用于生成ResourcesImpl 构建对象,这样就可以得到我们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();
    }

到目前为止,我们已经找到了ResoucesImpl的实现,完成了上面的工作后,就会回调到我们自己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);
                    }
                }

从上面的代码我们可以看到,所有资源的获取都会最终调用到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;
          }
      }   
      

完成上面的步骤后,接下来是回调回我们app的application的onCreate()方法中:

mInstrumentation.callApplicationOnCreate(app);

以上就是加载我们的布局和资源文件的全部过程已经相关方法的调用顺序。有了上面的基础,在实现换肤功能时,就有了初步思路了。(具体的换肤思路在后面的实战换肤文章中详说)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容