Android的資源管理器的建立過程

最近在研究Android的插件化,插件化須要解決的問題大概有這樣的幾個,爲何須要插件化技術這個就不說啦。html

  • 資源訪問java

  • 組件的生命週期的管理android

參考:
Android資源管理框架(Asset Manager)簡要介紹和學習計劃
Android應用程序的Activity啓動過程簡要介紹和學習計劃
Android源碼分析-Activity的啓動過程數組

這篇文章淺解一下Android的資源管理器的建立過程。這裏說一下,其實不少人對插件化的資源加載有一個誤區呀,就是通常別人說插件化須要解決一個資源衝突的問題,這裏有一個很重要的問題。cookie

爲何會有資源衝突的問題?app

這個問題其實不該該放在最前面說的,可是不少人都有一個誤區,感受必須先說一下這個了。
首先看Android的資源分類,Android的資源可分爲兩大類。分別是Asserts和Res。框架

  • Assertside

assets類資源放在工程根目錄的Assets子目錄下,這些文件最終會被原裝不動地打包在apk文件中。若是咱們要在程序中訪問這些文件,那麼就須要指定文件名來訪問。函數

  • Res工具

res資源比較多,放一張圖吧,基本一看就明白。

clipboard.png

res資源類型.png
res資源大概是這樣的啦,固然還有raw以及xml等資源啦。在編譯打包的過程當中,會把資源文件打包成二進制文件(.xml文件打包成二進制文件,png文件進行優化等)。會對除了assets資源以外全部的資源賦予一個資源ID常量,而且會生成一個資源索引表resources.arsc。

這個resources.arsc文件記錄了全部的應用程序資源目錄的信息,包括每個資源名稱、類型、值、ID以及所配置的維度信息。咱們能夠將這個resources.arsc文件想象成是一個資源索引表,這個資源索引表在給定資源ID和設備配置信息的狀況下,可以在應用程序的資源目錄中快速地找到最匹配的資源。

這些資源ID被終會被定義爲Java常量值,保存在一個R.java文件中,與應用程序的其它源文件一塊兒被編譯到程序中,這樣咱們就能夠在程序或者資源文件中經過這些ID常量來訪問指定的資源。

資源ID的最終的格式是:0xPPTTNNNN
資源ID是一個4字節的無符號整數,其中,最高字節表示Package ID,次高字節表示Type ID,最低兩字節表示Entry ID。

  • PP Package ID至關因而一個命名空間,限定資源的來源。

Android系統當前定義了兩個資源命令空間,其中一個系統資源命令空間(好比咱們能夠直接引用系統提供好的主題等),它的Package ID等於0x01,另一個是應用程序資源命令空間,它的Package ID等於0x7F。系統資源包的Package ID就等於0x01,而咱們在應用程序中定義的資源的Package ID的值都等於0x7F,上圖就能夠看出來的。

  • TT Type ID是指資源的類型ID。

資源的類型有animator、anim、color、string和xml等等若干種,每一種都會被賦予一個ID。上圖也能夠看出來的。

  • NNNN Entry ID是指每個資源在其所屬的資源類型中所出現的次序。

注意,不一樣類型的資源的Entry ID有多是相同的,可是因爲它們的類型不一樣,咱們仍然能夠經過其資源ID來區別開來。
上面說這麼多就是想說,咱們寫的App一般狀況下資源等的ID都是0x7F開始的,插件化的時候,開發插件是看成一個App來開發的,打包的時候資源ID也是0x7F開始的,因此呢,這個插件與插件,插件與宿主的資源ID頗有多是同樣的。

下面說的是重點:
在插件化的開發過程當中,加載插件的資源能夠單首創建了用於訪問插件資源的AssertManager和Resource對象,即插件獨立使用一個資源管理器,這樣宿主訪問宿主的資源,插件訪問插件的資源,這樣子是不會出現資源衝突問題的。然而這個(插件使用單獨的資源管理器)在現實中是不切實際的,通常都會將插件的資源路徑添加到宿主的AssetManager中,這樣作的緣由是爲了插件與宿主之間的資源共享,資源共享的緣由主要是爲了減小插件的體積。
現階段對資源衝突的解決方案:

  • 修改aapt源碼,定製aapt工具編譯期間修改PP段。

例如:atlas

  • 修改aapt的產物,即編譯後期從新整理插件Apk的資源,編排ID。

例如:Small
這兩種方案均可以,修改aapt的源代碼,雖說比較麻煩,可是其實須要修改的代碼是比較少的,不過須要你有個Android的源碼的環境才能編譯出aappt可執行文件,修改aapt的產物,通常是寫gradle插件來實現,由於Android的源代碼是知道的,因此咱們能知道aapt最後生成的二進制文件的格式,而後本身整理資源的ID。
Android framework層的資源查找

1.咱們正常的使用資源的過程

在Android系統中,每個應用程序通常都會配置不少資源,用來適配不一樣密度、大小和方向的屏幕,以及適配不一樣的國家、地區和語言等等。這些資源是在應用程序運行時自動根據設備的當前配置信息進行適配的。這也就是說,給定一個相同的資源ID,在不一樣的設備配置之下,查找到的多是不一樣的資源。
這個查找過程對應用程序來講,是徹底透明的,這個過程主要是靠Android資源管理框架來完成的,而Android資源管理框架實際是由AssetManager和Resources兩個類來實現的。其中,Resources類能夠根據ID來查找資源,而AssetManager類根據文件名來查找資源。事實上,若是一個資源ID對應的是一個文件,那麼Resources類是先根據ID來找到資源文件名稱,而後再將該文件名稱交給AssetManager類來打開對應的文件的。
注:上面這段話出自美團Android資源混淆保護實踐
通常咱們查找Assert資源代碼以下:

try {
    getAssets().open("name");
} catch (IOException e) {
    e.printStackTrace();
}

查找Res資源代碼以下:

Resources resources = getResources();
String name = resources.getString(R.string.app_name);

繼續跟蹤resources.getString的實現:

clipboard.png

Res.getString.png

能夠看到確實是使用的AssetManager來處理的。

2.上下文環境Context的建立

前面說了,查找資源使用的是Resources和AssetManager,那咱們來跟蹤一下這兩個類的建立生成吧。

clipboard.png

mBase.getResource.png

clipboard.png

getResources.png
咱們會很清楚的發現這兩個類所有是由Context建立的,因此如今須要找到mBase即Context的生成過程,從上圖清晰可見的是mBase的生成時機是在attachBaseContext這個方法中,找到哪裏調用這個方法,最後在子類Activity中找到了調用的時機。

clipboard.png

Activity.attach.png

經過這個方法名,咱們大概就知道,這個方法是Activity建立的時候會調用的,如今咱們應該看看,一個Activity是怎樣建立出來的咯。

說到Activity的建立,首先應該想到Activity#startActivity方法的,從上往下看,顯然最後都是調用的Activity#startActivityForResult來實現的。

clipboard.png

startActivityForResult.png

能夠發現真正打開Activity的實如今Instrumentation的execStartActivity方法中,咱們去看他的實現:

clipboard.png

Instrumentation#execStartActivity.png

而後觀察,發現最後調用的是:

int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(),intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target, requestCode, 0, null, options);

這裏的ActivityManagerNative.getDefault返回IActivityManager對象,因爲須要啓動的Activity不必定是咱們當前進程的,好比Launcher在桌面啓動一個應用就會新開個進程的。這裏就會有IPC交互,這裏返回的IActivityManager,若是是和當前進程在同一個進程就是ActivityManagerNative的子類,由於IActivityManager接口裏面的方法它都沒有實現的,若是不在同一個進程這個IActivityManager對象就是ActivityManagerProxy對象。

IActivityManager在Binder的Server端的實現是ActivityManagerService。因此最後startActivity調用的是ActivityManagerService的startActivity方法。

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
            resultWho, requestCode, startFlags, profilerInfo, options,
            UserHandle.getCallingUserId());
    }

    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
                false, ALLOW_FULL_ONLY, "startActivity", null);
        return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, options, false, userId, null, null);
    }
}

而後調用的是

ActivityStackSupervisor#startActivityMayWait
--->ActivityStackSupervisor#startActivityLocked
--->ActivityStackSupervisor#startActivityUncheckedLocked
--->ActivityStack#startActivityLocked
--->ActivityStackSupervisor#resumeTopActivitiesLocked
--->ActivityStackSupervisor#resumeTopActivityLocked
--->ActivityStack#resumeTopActivityInnerLocked
--->ActivityStackSupervisor#startSpecificActivityLocked
--->ActivityStackSupervisor#realStartActivityLocked

過程很是複雜,在最後的方法裏面調用了

app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,System.identityHashCode(r), r.info, newConfiguration(mService.mConfiguration),new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

,這裏的app.thread對象是IApplicationThread接口類型的對象。
IApplicationThread對象的Server端的實現是ApplicationThreadNative的子類ApplicationThread(它是ActivityThread的內部類),Proxy本地的代理實現是ApplicationThreadProxy。
最後調用的是下面的方法:

public final class ActivityThread {
    private class ApplicationThread extends ApplicationThreadNative {

        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
               // 省略
               sendMessage(H.LAUNCH_ACTIVITY, r);
        }
  }
}

函數中的處理就是Android的消息系統的正常處理流程了,由於這個是ActivityThread的內部類,那麼對應的Handler應該在ActivityThread裏面的,最後能夠找到是H.handleMessage處理的啦。
下面調用的是ActivityThread的:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent);
--->
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent);

來看看這個方法:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
// 省略
ComponentName component = r.intent.getComponent();
// 省略
Activity activity = null;
try {
    // 建立Activity
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    r.intent.prepareToEnterProcess();
    if (r.state != null) {
        r.state.setClassLoader(cl);
    }
} catch (Exception e) {
}

try {
    // 建立Applicatipn
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    if (activity != null) {
        // 建立Context即上面說的mBase。
        Context appContext = createBaseContextForActivity(r, activity);
        CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
        Configuration config = new Configuration(mCompatConfiguration);
        if (customIntent != null) {
            activity.mIntent = customIntent;
        }
        r.lastNonConfigurationInstances = null;
        activity.mStartedActivity = false;
        int theme = r.activityInfo.getThemeResource();
        if (theme != 0) {
            activity.setTheme(theme);
        }
        activity.mCalled = false;
        // 調用onCreate
        if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
}   
    r.paused = true;
    mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
    throw e;
} catch (Exception e) {
}
return activity;

}

看到這裏,咱們終於找到了Activity裏面的mBase變量是怎麼生成的啦。如今看createBaseContextForActivity這個方法就知道,Context的真正實現了一些咱們想知道方法的類是哪一個啦。

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {

int displayId = Display.DEFAULT_DISPLAY;
try {
    displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
}
// 建立Context
ContextImpl appContext = ContextImpl.createActivityContext(
        this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
// 省略。。。
return baseContext;

}

這裏咱們終於知道,Context的最終的實現類是ContextImpl啦。
Android應用程序窗口的運行上下文環境是經過ContextImpl類來描述的,即每個Activity組件都關聯有一個ContextImpl對象。ContextImpl類繼承了Context類,它與Activity組件的關係如圖所示:
圖片取自Android應用程序窗口(Activity)的運行上下文環境(Context)的建立過程分析

clipboard.png

ContextImpl類與Activity類的關係圖.jpg

這裏咱們解決了Activity裏面的Context是怎麼生成的問題。

  1. AssetManager的建立過程

上面咱們已經知道了,Activity裏面的Context的建立了,那麼那兩個獲取資源的方法在ContextImpl裏面是怎樣的呢。

public class ContextImpl {
@Override
public AssetManager getAssets() {
    return getResources().getAssets();
}
@Override
public Resources getResources() {
    return mResources;
}
}

ContextImpl類的成員函數getResources返回的是一個Resources對象,有了這個Resources對象以後,咱們就能夠經過資源ID來訪問那些被編譯過的應用程序資源了。ContextImpl類的成員函數getAssets返回的是一個AssetManager對象,有了這個AssetManager對象以後,咱們就能夠經過文件名來訪問那些被編譯過或者沒有被編譯過的應用程序資源文件了。事實上,Resources類也是經過AssetManager類來訪問那些被編譯過的應用程序資源文件的,不過在訪問以前,它會先根據資源ID查找獲得對應的資源文件名。

首先看ContextImpl的Resources對象的產生過程:

private ContextImpl(ContextImpl container, ActivityThread mainThread,
        LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
        Display display, Configuration overrideConfiguration, int createDisplayWithId) {
    mResourcesManager = ResourcesManager.getInstance();
    Resources resources = packageInfo.getResources(mainThread);
    if (resources != null) {
        if (displayId != Display.DEFAULT_DISPLAY
                || overrideConfiguration != null
                || (compatInfo != null && compatInfo.applicationScale
                        != resources.getCompatibilityInfo().applicationScale)) {
            resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
                    packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
                    packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
                    overrideConfiguration, compatInfo);
        }
    }
    mResources = resources;
}

參數packageInfo指向的是一個LoadedApk對象,這個LoadedApk對象描述的是當前正在啓動的Activity組所屬的Apk。用來訪問應用程序資源的Resources對象是經過調用參數packageInfo所指向的是一個LoadedApk對象的成員函數getResources來建立的。這個Resources對象建立完成以後,因爲應用程序的一些其餘設置可能改變,須要從新生成Resource,最終生成的對象,就會保存在ContextImpl類的成員變量mResources中。這兩處生成Resources的方法最終都會調用到ResourcesManager的getTopLevelResources方法。

Resources getTopLevelResources(String resDir, String[] splitResDirs,
        String[] overlayDirs, String[] libDirs, int displayId,
        Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
    final float scale = compatInfo.applicationScale;
    Configuration overrideConfigCopy = (overrideConfiguration != null)
            ? new Configuration(overrideConfiguration) : null;
    ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
    Resources r;
    synchronized (this) {
    // 省略
    AssetManager assets = new AssetManager();
    // 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 (resDir != null) {
        if (assets.addAssetPath(resDir) == 0) {
            return null;
        }
    }
    if (splitResDirs != null) {
        for (String splitResDir : splitResDirs) {
            if (assets.addAssetPath(splitResDir) == 0) {
                return null;
            }
        }
    }
    if (overlayDirs != null) {
        for (String idmapPath : overlayDirs) {
            assets.addOverlayPath(idmapPath);
        }
    }
    if (libDirs != null) {
        for (String libDir : libDirs) {
            if (libDir.endsWith(".apk")) {
                // Avoid opening files we know do not have resources,
                // like code-only .jar files.
                if (assets.addAssetPath(libDir) == 0) {
                }
            }
        }
    }

    //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
    DisplayMetrics dm = getDisplayMetricsLocked(displayId);
    Configuration config;
    final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
    final boolean hasOverrideConfig = key.hasOverrideConfiguration();
    if (!isDefaultDisplay || hasOverrideConfig) {
        config = new Configuration(getConfiguration());
        if (!isDefaultDisplay) {
            applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config);
        }
        if (hasOverrideConfig) {
            config.updateFrom(key.mOverrideConfiguration);
            if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
        }
    } else {
        config = getConfiguration();
    }
    r = new Resources(assets, dm, config, compatInfo);
    // 省略
        return r;
    }
}

getTopLevelResources來得到一個Resources對象的時候,須要指定要獲取的Resources對象所對應的Apk文件路徑,這個Apk文件路徑就保存在LoadedApk類的成員變量mResDir中,這裏還可能有其餘的資源路徑,也能夠添加。
這樣就建立出了Resources對象和AssertManager對象啦。

下面看AssetManager類的構造函數和成員函數addAssetPath的實現,接着再看Resources類的構造函數的實現。

public AssetManager() {
    synchronized (this) {
        if (DEBUG_REFS) {
            mNumRefs = 0;
            incRefsLocked(this.hashCode());
        }
        init(false);
        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
        ensureSystemAssets();
    }
}

AssetManager類的構造函數是經過調用另一個成員函數init來執行初始化工做的。在初始化完成當前正在建立的AssetManager對象以後,AssetManager類的構造函數還會調用另一個成員函數ensureSystemAssets來檢查當前進程是否已經建立了一個用來訪問系統資源的AssetManager對象。

若是用來訪問系統資源的AssetManager對象尚未建立的話,那麼AssetManager類的成員函數ensureSystemAssets就會建立而且初始化它,而且將它保存在AssetManager類的靜態成員變量sSystem中。注意,建立用來訪問系統資源和應用程序資源的AssetManager對象的過程是同樣的,區別只在於它們所要訪問的Apk文件不同。
addAssetPath的C++實如今android_util_AssetManager.cpp
C++實在太差。
經過搜索java對應的方法便可找到C++對應的方法啦。

{
"addAssetPathNative",
"(Ljava/lang/String;Z)I",
(void*) android_content_AssetManager_addAssetPath
}
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,

jstring path, jboolean appAsLib)

{

ScopedUtfChars path8(env, path);
if (path8.c_str() == NULL) {
    return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
    return 0;
}
int32_t cookie;
bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib);
return (res) ? static_cast<jint>(cookie) : 0;

}
額,而後調用到了AssetManager的addAssetPath方法。
這個文件在AssetManager.cpp

bool AssetManager::addAssetPath(

const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset)

{

AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
    realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
    ap.path = realPath;
} else {
    ap.path = path;
    ap.type = ::getFileType(path.string());
        return false;
    }
}
// Skip if we have it already.
for (size_t i=0; i<mAssetPaths.size(); i++) {
    if (mAssetPaths[i].path == ap.path) {
        if (cookie) {
            *cookie = static_cast<int32_t>(i+1);
        }
        return true;
    }
}
ap.isSystemAsset = isSystemAsset;
mAssetPaths.add(ap);

// new paths are always added at the end
if (cookie) {
    *cookie = static_cast<int32_t>(mAssetPaths.size());
}

ifdef ANDROID

// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
    oap.isSystemAsset = isSystemAsset;
    mAssetPaths.add(oap);
}

endif

if (mResources != NULL) {
    appendPathToResTable(ap, appAsLib);
}
return true;

}
若是全局變量kAppZipName的值不等於NULL的話,那麼它的值通常就是被設置爲「classes.jar」,這時候就表示應用程序的資源文件是保存在參數path所描述的一個目錄下的一個classes.jar文件中。全局變量kAppZipName的值通常被設置爲NULL,而且參數path指向的是一個Apk文件。
AssetManager類的成員函數addAssetPath首先是要檢查參數path指向的是一個文件或者目錄,而且該文件或者目錄存在,不然的話,它就會直接返回一個false值,而不會再繼續往下處理了。
若是已經添加過了,那麼AssetManager類的成員函數addAssetPath就不會再繼續往下處理了。若是達到條件就會把路徑添加到成員變量mAssetPaths所描述的一個Vector中去。

下面是Resources的建立過程。

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
        CompatibilityInfo compatInfo) {
    mAssets = assets;
    mMetrics.setToDefaults();
    if (compatInfo != null) {
        mCompatibilityInfo = compatInfo;
    }
    updateConfiguration(config, metrics);
    assets.ensureStringBlocks();
}

Resources類的成員變量mConfiguration指向的是一個Configuration對象,用來描述設備當前的配置信息。
Resources類的成員函數updateConfiguration首先是根據參數config和metrics來更新設備的當前配置信息,例如,屏幕大小和密碼、國家地區和語言、鍵盤配置狀況等等,接着再調用成員變量mAssets所指向的一個Java層的AssetManager對象的成員函數setConfiguration來將這些配置信息設置到與之關聯的C++層的AssetManager對象中去。

/*package*/ final void makeStringBlocks(StringBlock[] seed) {
    final int seedNum = (seed != null) ? seed.length : 0;
    final int num = getStringBlockCount();
    mStringBlocks = new StringBlock[num];
    for (int i=0; i<num; i++) {
        if (i < seedNum) {
            mStringBlocks[i] = seed[i];
        } else {
            mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
        }
    }
}

AssetManager類的成員變量mStringBlocks指向的是一個StringBlock數組,其中,每個StringBlock對象都是用來描述一個字符串資源池AssetManager類的成員變量mStringBlocks就是用來保存全部的資源表中的資源項值字符串資源池的。
若是資源還沒讀取出來,那麼會先讀取的。也會將系統資源表裏面的資源項值字符串資源池也一塊兒拷貝到成員變量mStringBlokcs所描述的一個數組中去。getStringBlockCount這個方法獲取的資源數,含有系統資源表的個數sysNum的。

if (i < seedNum) {
            mStringBlocks[i] = seed[i];

這裏若是有疑問的話,實際上是這樣的:
用來訪問系統資源包的AssetManager對象就保存在AssetManager類的靜態成員變量sSystem中,而且這個AssetManager對象是最早被建立以及初始化的。也就是說,當執行到這一步的時候,全部系統資源表的資源項值字符串資源池已經讀取出來,它們就保存在AssetManager類的靜態成員變量sSystem所描述的一個AssetManager對象的成員變量mStringBlocks中,所以,只將它們拷貝到當前正在處理的AssetManager對象的成員變量mStringBlokcs的前sysNum個位置上去就能夠了。

這裏,就分析完成Android應用程序資源管理器的建立的初始化過程了,主要就是建立和初始化AssetManager和Resources,其中,初始化操做包括設置AssetManager對象的資源文件路徑以及設備配置信息等。

因此咱們想要加載一個插件的資源,首先要肯定是宿主和插件是否是須要共享資源,須要共享的話,可能只是調用AssetManager.addAssetPath(),把插件apk的地址傳遞進來,而後進行便可,可是這個方案的前提是已經解決 資源的衝突問題。
另一種方案就是,插件使用一個全新的Resources對象。

public class LoadResources {

public static class PluginResource {
    public Resources resources;
    public AssetManager assetManager;
    public Resources.Theme theme;
}

public static PluginResource getPluginResources(String apkPath, Resources supResource, Resources.Theme supTheme) {
    try {
        PluginResource resource = new PluginResource();
        //建立AssetManager
        AssetManager newAssetManager = AssetManager.class.newInstance();
        Method addAssetPathMethod = newAssetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
        addAssetPathMethod.setAccessible(true);
        addAssetPathMethod.invoke(newAssetManager, apkPath);
        Method ensureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
        ensureStringBlocks.setAccessible(true);
        ensureStringBlocks.invoke(newAssetManager);
        //建立咱們本身的Resource
        Resources newResource = new Resources(newAssetManager,
                supResource.getDisplayMetrics(), supResource.getConfiguration());
        Resources.Theme newTheme = newResource.newTheme();
        newTheme.setTo(supTheme);
        resource.assetManager = newAssetManager;
        resource.resources = newResource;
        resource.theme = newTheme;
        return resource;
    } catch (Exception e) {
    }
    return null;
}

}而後複寫Activity的三個對應的方法,在須要的時候返回想要的對象便可。

相關文章
相關標籤/搜索