一、系統實例化View的流程
我們進入AppCompatActivity
中的onCreate
方法:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//創建 AppCompatDelegate
final AppCompatDelegate delegate = getDelegate();
//初始化 Factory
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
1.1、創建 AppCompatDelegate
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
1.2、初始化 AppCompatDelegate
在抽象類AppCompatDelegate
的實現類AppCompatDelegateImpl
中實現
public void installViewFactory() {
//構建 LayoutInflater
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//當 Factory == null 時,設置 Factory2
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
執行 LayoutInflaterCompat#setFactory2
構建 Factory2
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
//設置 factory
inflater.setFactory2(factory);
//sdk 版本小於 21 執行 下面代碼
if (Build.VERSION.SDK_INT < 21) {
final LayoutInflater.Factory f = inflater.getFactory();
if (f instanceof LayoutInflater.Factory2) {
forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
} else {
forceSetFactory2(inflater, factory);
}
}
}
在LayoutInflater#setFactory2
中執行
public void setFactory2(Factory2 factory) {
// Factory2 只能創建一次,當創建後再執行拋出異常
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
//標誌位,使 m 只能夠創建一次
mFactorySet = true;
if (mFactory == null) {
//設置 mFactory
mFactory = mFactory2 = factory;
} else {
//當我們 HOOK 時執行,前提利用反射把標誌位 mFactorySet 至 false
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
我們用的動態換膚,就是利用HOOK技術。有兩種設置方法:
- 1)、在
super.onCreate(savedInstanceState)
方法之前執行 setFactory2 重新設置我們自己 Factory2 添加到裏面。 - 2)、利用反射技術把標誌位
mFactorySet
置爲false,然後setFactory2我們自己的 Factory。這樣再次設置 Factory2 就不會拋異常了。
private static class FactoryMerger implements Factory2 {
//再次設至的 Factory
private final Factory mF1, mF2;
//第一次設至的 Factory
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
@Nullable
public View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
@Nullable
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
1.3、Factory 類
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
public interface Factory {
@Nullable
View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs);
}
上面的代碼我們可以看到 Factory2
是繼承Factory
的接口。
1.4、初始化 xml 怎麼利用 Factory2
setContentView()
的過程就不跟蹤了,我們直接看createViewFromTag
方法。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//解析view標籤
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
//如果需要該標籤與主題相關,需要對context進行包裝,
//將主題信息加入context包裝類ContextWrapper
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
//設置Factory,來對View做額外的拓展,這塊屬於可定製的內容
try {
View view = tryCreateView(parent, name, context, attrs);
//如果此時不存在Factory,不管Factory還是Factory2,
//還是mPrivateFactory都不存在,那麼會直接對name直接進行解析
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果name中包含.即爲自定義View,
//否則爲原生SDK的View控件
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
//返回創建的 View
return view;
}
}
利用定義的 Factory 創建 View
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
//BlinkLayout是一種閃爍的FrameLayout,它包裹的內容會一直閃爍,類似QQ提示消息那種。
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
//利用 Factory2 接口創建
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
//利用 Factory 接口創建
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
//利用自定義的 Factory
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
然後執行 AppCompatDelegateImpl
裏面重載的 onCreateView
方法。
//Factory2 裏面的
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
//Factory 裏面的
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
//獲取屬性
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
//創建 AppCompatViewInflater
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
boolean inheritContext = false;
//把 xx 控件轉換成 AppCompatxx 控件
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
然後在AppCompatViewInflater
的createView
進行轉換
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
二、Factory 的使用
override fun onCreate(savedInstanceState: Bundle?) {
LayoutInflater.from(this).factory2 = object : LayoutInflater.Factory2{
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
if (TextUtils.equals(name, "TextView")) {
val btn = Button(this@MyActivity)
btn.text = "我是一個按鈕"
return btn
}
return null
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return null
}
}
super.onCreate(savedInstanceState)
setContentView(R.layout.my_lactivity)
}
上面的代碼就是 HOOK 技術的簡單運用,我們利用自己創建的Factory2
,把控件 TextView 轉換爲控件 Button 。
2.1、在生命週期中創建
我們也可以onActivityCreated
中創建 Factory2
。動態換膚就是利用這一點。
我們看Application
內部的ActivityLifecycleCallbacks
的源碼:
public interface ActivityLifecycleCallbacks {
default void onActivityPreCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { }
//監聽 Activity 的創建
void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
default void onActivityPostCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {}
default void onActivityPreStarted(@NonNull Activity activity) { }
void onActivityStarted(@NonNull Activity activity);
default void onActivityPostStarted(@NonNull Activity activity) {}
default void onActivityPreResumed(@NonNull Activity activity) { }
void onActivityResumed(@NonNull Activity activity);
default void onActivityPostResumed(@NonNull Activity activity) { }
default void onActivityPrePaused(@NonNull Activity activity) { }
void onActivityPaused(@NonNull Activity activity);
default void onActivityPostPaused(@NonNull Activity activity) { }
default void onActivityPreStopped(@NonNull Activity activity) { }
void onActivityStopped(@NonNull Activity activity);
default void onActivityPostStopped(@NonNull Activity activity) { }
default void onActivityPreSaveInstanceState(@NonNull Activity activity,@NonNull Bundle outState) { }
void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
default void onActivityPostSaveInstanceState(@NonNull Activity activity,@NonNull Bundle outState) { }
default void onActivityPreDestroyed(@NonNull Activity activity) {}
void onActivityDestroyed(@NonNull Activity activity);
default void onActivityPostDestroyed(@NonNull Activity activity{}
}
我們可以實現接口 ActivityLifecycleCallbacks
來監聽Activity 的生命週期。
Activity 的創建我們在Activity#onCreate
中執行監聽
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
mFragments.dispatchCreate();
dispatchActivityCreated(savedInstanceState);
...
}
private void dispatchActivityCreated(@Nullable Bundle savedInstanceState) {
getApplication().dispatchActivityCreated(this, savedInstanceState);
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i = 0; i < callbacks.length; i++) {
// 調用 onActivityCreated,進行創建監聽
((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(this,
savedInstanceState);
}
}
}
- 利用 map 存儲監聽事件
private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
new ArrayList<Application.ActivityLifecycleCallbacks>();
- 註冊監聽
public void registerActivityLifecycleCallbacks(
@NonNull Application.ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.add(callback);
}
}
- 註銷監聽
public void unregisterActivityLifecycleCallbacks(
@NonNull Application.ActivityLifecycleCallbacks callback) {
synchronized (mActivityLifecycleCallbacks) {
mActivityLifecycleCallbacks.remove(callback);
}
}
三、AssetManager 創建流程
首先我們執行ActivityThread#performLaunchActivity
方法
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
...
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
...
return activity;
}
執行ActivityThread#createBaseContextForActivity
方法
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
displayId = ActivityTaskManager.getService().getDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
//創建 ContextImpl
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
// The rotation adjustments must be applied before creating the activity, so the activity
// can get the adjusted display info during creation.
if (r.mPendingFixedRotationAdjustments != null) {
handleFixedRotationAdjustments(r.token, r.mPendingFixedRotationAdjustments,
r.overrideConfig);
r.mPendingFixedRotationAdjustments = null;
}
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
return appContext;
}
執行ContextImpl#createActivityContext
方法,創建 Context
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
String[] splitDirs = packageInfo.getSplitResDirs();
ClassLoader classLoader = packageInfo.getClassLoader();
if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
try {
classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
} catch (NameNotFoundException e) {
// Nothing above us can handle a NameNotFoundException, better crash.
throw new RuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
//創建 ContextImpl, ContextImpl是 Context的實現類
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
activityInfo.splitName, activityToken, null, 0, classLoader, null);
context.mIsUiContext = true;
context.mIsAssociatedWithDisplay = true;
context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
? packageInfo.getCompatibilityInfo()
: CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Create the base resources for which all configuration contexts for this Activity
// 設置 Resources 資源
context.setResources(resourcesManager.createBaseTokenResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader,
packageInfo.getApplication() == null ? null
: packageInfo.getApplication().getResources().getLoaders()));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
執行ContextImpl#createBaseTokenResources
方法
public @Nullable Resources createBaseTokenResources(@NonNull IBinder token,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader,
@Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#createBaseActivityResources");
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 (DEBUG) {
Slog.d(TAG, "createBaseActivityResources activity=" + token
+ " with key=" + key);
}
synchronized (this) {
// Force the creation of an ActivityResourcesStruct.
getOrCreateActivityResourcesStructLocked(token);
}
// Update any existing Activity Resources references.
updateResourcesForActivity(token, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
rebaseKeyForActivity(token, key);
synchronized (this) {
Resources resources = findResourcesForActivityLocked(token, key,
classLoader);
if (resources != null) {
return resources;
}
}
// Now request an actual Resources object.
return createResources(token, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
執行ContextImpl#createResources
方法創建Resources
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo,
List<ResourcesLoader> resourcesLoader) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
//利用單例模式獲取 Resources
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader,
resourcesLoader);
}
我們看 ResourcesManager
裏面的方法
//單例,懶漢式
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
if (sResourcesManager == null) {
sResourcesManager = new ResourcesManager();
}
return sResourcesManager;
}
}
public @Nullable Resources getResources(
@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader,
@Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
//創建 key,根據key 獲取 ResourcesImpl
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);
}
//創建資源 Resources
return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
獲取 Resources
資源
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);
}
//根據 key 獲取 ResourcesImpl
ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
if (resourcesImpl == null) {
return null;
}
//創建 Resources
if (activityToken != null) {
return createResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
}
執行ResourcesManager#findOrCreateResourcesImplForKeyLocked
方法
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
new ArrayMap<>();
private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
@NonNull ResourcesKey key) {
//從緩存中獲取 ResourcesImpl
ResourcesImpl impl = findResourcesImplForKeyLocked(key);
if (impl == null) {
//創建 ResourcesImpl
impl = createResourcesImpl(key);
if (impl != null) {
//加入緩存
mResourceImpls.put(key, new WeakReference<>(impl));
}
}
return impl;
}
執行ResourcesManager#createResourcesImpl
方法,,創建AssetManager
,幷包裝到 ResourcesImpl
中
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
//創建 AssetManager
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
//創建ResourcesImpl
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
通過Builder模式創建 AssetManager
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
final AssetManager.Builder builder = new AssetManager.Builder();
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();
}
執行ResourcesManager#loadApkAssets
方法創建 ApkAssets
包資源
private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
throws IOException {
final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
ApkAssets apkAssets = null;
if (mLoadedApkAssets != null) {
apkAssets = mLoadedApkAssets.get(newKey);
if (apkAssets != null && apkAssets.isUpToDate()) {
return apkAssets;
}
}
// Optimistically check if this ApkAssets exists somewhere else.
final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
if (apkAssetsRef != null) {
apkAssets = apkAssetsRef.get();
if (apkAssets != null && apkAssets.isUpToDate()) {
if (mLoadedApkAssets != null) {
mLoadedApkAssets.put(newKey, apkAssets);
}
return apkAssets;
} else {
// Clean up the reference.
mCachedApkAssets.remove(newKey);
}
}
// We must load this from disk.
if (overlay) {
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
} else {
apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
}
if (mLoadedApkAssets != null) {
mLoadedApkAssets.put(newKey, apkAssets);
}
mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
return apkAssets;
}
四、資源加載流程
在Resources
類中根據資源 id,獲取String。
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
@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));
}
在AssetManager
中執行 getResourceText
根據資源 id 獲取資源
@Nullable CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
final TypedValue outValue = mValue;
if (getResourceValue(resId, 0, outValue, true)) {
return outValue.coerceToString();
}
return null;
}
}
從上面的資源加載流程可得,Resources 類也是通過 AssetManager 類來訪問那些被編譯過的應用程序資源文件的,不過在訪問之前,它會先根據資源 ID 查找得到對應的資源文件名。 而 AssetManager 對象既可以通過文件名訪問那些被編譯過的,也可以訪問沒有被編譯過的應用程序資源文件。
- 根據名字獲取資源包的資源
// app原始的resource
private Resources mAppResources;
// 皮膚包的resource
private Resources mSkinResources;
/**
* 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;
}