流程分析
1.创建skip.apk,里面只有要修改的资源文件,没有代码
2.将skip.apk放在data/data目录下
3.通过原apk中的资源id获取到资源的name和type,然后通过mSkinResources.getIdentifier获取到皮肤包中的资源(mSkinResources是skip.apk资源包中获取的Resources,下面会讲怎么获取skip.apk中的资源)
/**
* 1.通过原始app中的resId(R.color.XX)获取到自己的 名字
* 2.根据名字和类型获取皮肤包中的ID
*/
public int getIdentifier(int resId){
if(isDefaultSkin){
return resId;
}
String resName=mAppResources.getResourceEntryName(resId);
String resType=mAppResources.getResourceTypeName(resId);
int skinId=mSkinResources.getIdentifier(resName,resType,mSkinPkgName);
return skinId;
}
4.对view更换新的资源
1.Resource是怎么加载进内存的
当我们的app进程创建出来的时候,回执行我们app的入口函数ActivityThread.main()方法
//创建ActivityThread
ActivityThread thread = new ActivityThread();
thread.attach(true, 0);
//查看attach
final IActivityManager mgr = ActivityManager.getService();
//通过AMS的代理对象,会执行AMS的attachApplication()
mgr.attachApplication(mAppThread, startSeq);
//来到AMS中,通过ApplicationThread的代理类,执行thread.bindApplication
//查看ApplicationThread的bindApplication()方法,通过handler执行handleBindApplication()
//handleBindApplication()中会makeApplication(data.restrictedBackupMode, null);
//makeApplication 中ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null);
//获取Resources
context.setResources(packageInfo.getResources());
return context;
}
//LoadApk
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());
}
return mResources;
}
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
...
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
重点重点,创建我们的Resource
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);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
到这里,我们将新的资源加载进去了,demo代码
//宿主app的 resources;
Resources appResource = mContext.getResources();
//
//反射创建AssetManager 与 Resource
AssetManager assetManager = AssetManager.class.newInstance();
//资源路径设置 目录或压缩包
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath",
String.class);
addAssetPath.invoke(assetManager, skinPath);
//根据当前的设备显示器信息 与 配置(横竖屏、语言等) 创建Resources
Resources skinResource = new Resources(assetManager, appResource.getDisplayMetrics
(), appResource.getConfiguration());
1.从setContentView()流程查看View是怎么创建出来的
activity中setContentView(layoutResID); 调用到getWindow().setContentView(layoutResID);Window的实现类是PhoneWindow
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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 {
//重点看inflate工程
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
一直跟进去,到LayoutInflater中
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
// Temp is the root view that was found in the xml
//创建view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
...
}
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view;
//重点来了
//默认mFactory2为空,这个是Google默认给我们留的一个入口,我们重写mFactory2后就可以修改view, 换肤我们就从这儿开始,LayoutInflaterCompat.setFactory2(layoutInflater, skinLayoutInflaterFactory);
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
//默认是用的mFactory
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//上面创建失败,创建view的工程,我们会在mFactory2中抄袭这块儿代码
//普通view
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//a.b.c.View一般是自定义view
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
}
2.分析如何重写Factory2中创建view的方法
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
2.1创建SkinLayoutInflaterFactory.java实现Factory2接口,重写onCreateView()创建我们的View
抄写LayoutInflater中创建view的方式
public class SkinLayoutInflaterFactory implements LayoutInflater.Factory2 {
private static final String[] mClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app.",
"android.view."
};
//记录对应VIEW的构造函数
private static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
private static final HashMap<String, Constructor<? extends View>> mConstructorMap =
new HashMap<String, Constructor<? extends View>>();
private Activity activity;
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//换肤就是在需要时候替换 View的属性(src、background等)
//所以这里创建 View,从而修改View属性
View view = createSDKView(name, context, attrs);
if (null == view) {
view = createView(name, context, attrs);
}
//拿到view,就可以对其更换资源
return view;
}
private View createSDKView(String name, Context context, AttributeSet
attrs) {
//如果包含 . 则不是SDK中的view 可能是自定义view包括support库中的View
if (-1 != name.indexOf('.')) {
return null;
}
//不包含就要在解析的 节点 name前,拼上: android.widget. 等尝试去反射
for (int i = 0; i < mClassPrefixList.length; i++) {
View view = createView(mClassPrefixList[i] + name, context, attrs);
if(view!=null){
return view;
}
}
return null;
}
private View createView(String name, Context context, AttributeSet
attrs) {
Constructor<? extends View> constructor = findConstructor(context, name);
try {
return constructor.newInstance(context, attrs);
} catch (Exception e) {
}
return null;
}
private Constructor<? extends View> findConstructor(Context context, String name) {
Constructor<? extends View> constructor = mConstructorMap.get(name);
if (constructor == null) {
try {
Class<? extends View> clazz = context.getClassLoader().loadClass
(name).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
mConstructorMap.put(name, constructor);
} catch (Exception e) {
}
}
return constructor;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
}