前言:
学习换肤功能的一种实现时,记录总结布局加载,资源加载相关内容
//加载布局
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实现换肤需要更改。