布局加载、资源加载学习总结

前言:

学习换肤功能的一种实现时,记录总结布局加载,资源加载相关内容

//加载布局

ActivityThread.performLaunchActivity(ActivityClientRecord r, Intent customIntent){
    ActivityInfo aInfo = r.activityInfo; //获取activity的信息

    ContextImpl appContext = createBaseContextForActivity(r);//生成Activity上下文

    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); //生成activity,mInstrumentation是一个hook,可用于自动化测试

    window = r.mPendingRemoveWindow; //给生成的activity设置window, 只是去获取了activity的成员变量mWindow

    activity的成员变量mWindow是在其attach函数中赋值的
} -> Activity.attach(){
    mWindow = new PhoneWindow(this, window, activityConfigCallback); //设置mWindow
} -> PhoneWindow.PhoneWindow(this, window, activityConfigCallback){
    mDecor = (DecorView) preservedWindow.getDecorView();

    当我们启动一个activity时在onCreate中会通过setContentView()方法去加载布局文件,该方法的实现是在PhoneWindow中
} -> PhoneWindow.setContentView(int layoutResID){

    installDecor(); //安装decor
        ->{
            mDecor = generateDecor(-1); // new DecorView()

            mContentParent = generateLayout(mDecor);//去加载系统预设的一些布局,返回的是一个viewGroup
                ->{
                    // Apply data from current theme.

                    // Inflate the window decor.
                    layoutResource = R.layout.screen_title;//获取布局xml,系统预设的

                    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
                         -> {
                             final View root = inflater.inflate(layoutResource, null);//解析布局
                            addView();//将解析的view添加到mDecor的content中
                        }

                    ViewGroup contentParent = (ViewGroup)findViewById(com.android.internal.R.id.content);
                    return contentParent;
                }
        }

    mLayoutInflater.inflate(layoutResID, mContentParent); //解析布局
} -> LayoutInflater.inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot){
    //三个参数:parser xml解析器,root(这里是mContentParent), attachToRoot:

    final View temp = createViewFromTag(root, name, inflaterContext, attrs); //通过xml去创建view

    params = root.generateLayoutParams(attrs);
    //如果第三个参数为true,那么布局文件的根布局(经常是lineLayout标签等)view的参数不会添被设置,需要使用代码动态添加,一般我们使用false
    if (!attachToRoot) {
        // Set the layout params for temp if we are not
        // attaching. (If we are, we use addView, below)
        temp.setLayoutParams(params);
    }
} -> LayoutInflater.createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr){
    //name 就是我们view的tag,如TextView,LineLayout,Button等

    //两种方式去创建view
    //第一种,通过工厂创建,系统预设了三个工厂,分别是Factory、Factory2、PrivateFactory;它们都是接口,源码中默认没有工厂的实现。
    View view = tryCreateView(parent, name, context, attrs); -> view = mFactory2.onCreateView(parent, name, context, attrs);

    //如果没有通过工厂创建便会使用下述方式创建
    if (-1 == name.indexOf('.')) {
        view = onCreateView(context, parent, name, attrs);
    } else {
        view = createView(context, name, null, attrs);
    }
} -> LayoutInflater.createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs){

    //用一个HashMap保存了view的名字和构造方法
    private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<String, Constructor<? extends View>>();
    //先从缓存中获取
    Constructor<? extends View> constructor = sConstructorMap.get(name);

    //缓存中没有,则通过反射获取
    //获取字节码文件
    clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class);
    //获取 两个参数 的构造方法, static final Class<?>[] mConstructorSignature = new Class[] {Context.class(上下文), AttributeSet.class(属性集)};
    constructor = clazz.getConstructor(mConstructorSignature);
    //设置可写
    constructor.setAccessible(true);
    //放入缓存map
    sConstructorMap.put(name, constructor);

    final View view = constructor.newInstance(args);//通过反射调用构造方法生成view
    return view;
}

//资源获取

ActivityThread.handleBindApplication(AppBindData data){
    app = data.info.makeApplication(data.restrictedBackupMode, null);//调用loadedApk类中的makeApplication方法生成Application
        ->LoadedApk.makeApplication{

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

            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); //通过ContextImpl创建Application上下文
                -> ContextImpl.createAppContext(ActivityThread mainThread, LoadedApk packageInfo, String opPackageName){

                    //new 一个ContextImpl
                    ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null, 0, null, opPackageName);

                    context.setResources(packageInfo.getResources());//通过packageinfo获取资源,这个packageInfo就是传进来的参数,是一个LoadedApk

                    return context;
                }

            app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext); //生成Application

            return app;// 返回生成的Application
        }

    mInstrumentation.callApplicationOnCreate(app);//调用Application的onCreate()方法
}
上述中ContextImpl在创建上下文的时候会去获取资源,接下来分析具体怎么获取
-> LoadedApk.getResources(){

    mResources = ResourcesManager.getInstance().getResources() ->ResourcesManager.createResources
        ->{
            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key); -> findOrCreateResourcesImplForKeyLocked (key)
                -> createResourcesImpl() -> final AssetManager assets = createAssetManager(key);
                -> {
                    builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,false /*overlay*/));
                }
        }
}
这里有一个层级关系: Resources -> resourcesImpl -> AssetManager(assets)
应用的资源在打包时会生成一个resources.arsc文件,该二进制文件保存了各种资源的 名称、id、路径。
AssetManager类就负责从该文件中加载信息

总结:

1、通过实现Factory让加载布局时创建view走到自定义Factory,在Factory中记录统计apk创建的view等(统计需要换肤的view)

2、 资源的获取都是通过Resources。比如我们拿一个颜色,可以直接使用getResources.getColor()。而Resources是通过AssetManager去读取resources.arsc文件获取的颜色。

3、API30中 AssetManager mAssets 的生成采用了Builder模式,addAssetPath也打上了废除的tag,因此使用addAssetPath实现换肤需要更改。

上一篇:linux下udev详解


下一篇:Python爬虫:设置Cookie解决网站拦截并爬取蚂蚁短租