转载自:http://blog.youkuaiyun.com/cauchyweierstrass/article/details/51067729
动态加载资源原理
通常我们调用getResources()方法获取资源文件
- public Resources getResources() {
- return mResources;
- }
- mResources = mPackageInfo.getResources(mainThread);
- public Resources getResources(ActivityThread mainThread) {
- if (mResources == null) {
- mResources = mainThread.getTopLevelResources(mResDir,
- Display.DEFAULT_DISPLAY, null, this);
- }
- return mResources;
- }
- Resources getTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration, CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, compInfo.applicationScale, compInfo.isThemeable);
- Resources r;
- synchronized (mPackages) {
- // ...
- WeakReference<Resources> wr = mActiveResources.get(key);
- r = wr != null ? wr.get() : null;
- if (r != null && r.getAssets().isUpToDate()) {
- if (false) {
- Slog.w(TAG, "Returning cached resources " + r + " " + resDir
- + ": appScale=" + r.getCompatibilityInfo().applicationScale);
- }
- return r;
- }
- }
- AssetManager assets = new AssetManager();
- assets.setThemeSupport(compInfo.isThemeable);
- if (assets.addAssetPath(resDir) == 0) {
- return null;
- }
- // ...
- r = new Resources(assets, dm, config, compInfo);
- if (false) {
- Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
- + r.getConfiguration() + " appScale="
- + r.getCompatibilityInfo().applicationScale);
- }
- synchronized (mPackages) {
- WeakReference<Resources> wr = mActiveResources.get(key);
- Resources existing = wr != null ? wr.get() : null;
- if (existing != null && existing.getAssets().isUpToDate()) {
- // Someone else already created the resources while we were
- // unlocked; go ahead and use theirs.
- r.getAssets().close();
- return existing;
- }
- // XXX need to remove entries when weak references go away
- mActiveResources.put(key, new WeakReference<Resources>(r));
- return r;
- }
- }
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, compInfo.applicationScale, compInfo.isThemeable);
因此只要这个Map中包含多个指向不同资源路径的Resources对象或者说我们有指向不同路径的资源的Resources对象,就可以访问多个路径的资源,即有实现访问其他APK文件中的资源的可能。
创建Resources对象的主要逻辑为
- AssetManager assets = new AssetManager();
- assets.setThemeSupport(compInfo.isThemeable);
- if (assets.addAssetPath(resDir) == 0) {
- return null;
- }
- r = new Resources(assets, dm, config, compInfo);
AssetManager的构造函数:
- public AssetManager() {
- synchronized (this) {
- if (DEBUG_REFS) {
- mNumRefs = 0;
- incRefsLocked(this.hashCode());
- }
- init();
- if (localLOGV) Log.v(TAG, "New asset manager: " + this);
- ensureSystemAssets();
- }
- }
- static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
- {
- AssetManager* am = new AssetManager();
- if (am == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", "");
- return;
- }
- // 将Framework的资源文件添加到AssertManager对象的路径中。
- am->addDefaultAssets();
- ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
- env->SetIntField(clazz, gAssetManagerOffsets.mObject, (jint)am);
- }
- bool AssetManager::addDefaultAssets()
- {
- // /system
- const char* root = getenv("ANDROID_ROOT");
- LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
- String8 path(root);
- // kSystemAssets定义为static const char* kSystemAssets = "framework/framework-res.apk";
- // 因此,path为/system/framework/framework-res.apk,framework对应的资源文件
- path.appendPath(kSystemAssets);
- return addAssetPath(path, NULL);
- }
- /**
- * Add an additional set of assets to the asset manager. This can be
- * either a directory or ZIP file. Not for use by applications. Returns
- * the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public native final int addAssetPath(String path);
- static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz, jstring path)
- {
- ScopedUtfChars path8(env, path);
- if (path8.c_str() == NULL) {
- return 0;
- }
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- void* cookie;
- // 在native代码中完成添加资源路径的工作
- bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
- return (res) ? (jint)cookie : 0;
- }
受此过程的提醒,我们是不是可以自己创建一个Resources对象,让它的包含我们指定路径的资源,就可以实现访问其他的资源了呢?答案是肯定的,利用这个思想可以实现资源的动态加载,换肤、换主题等功能都可以利用这种方法实现。
于是,主要思想就是创建一个AssetManager对象,利用addAssetPath函数添加指定的路径,用其创建一个Resources对象,使用该Resources对象获取该路径下的资源。
需要注意的是addAssetPath函数是hide的,可以使用反射调用。
- public void loadRes(String path){
- try {
- assetManager = AssetManager.class.newInstance();
- Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
- addAssetPath.invoke(assetManager, path);
- } catch (Exception e) {
- }
- resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
- // 也可以根据资源获取主题
- }
- getPackageManager().getApplicationInfo("xxx", 0).sourceDir;
- @Override
- public Resources getResources() {
- return resources == null ? super.getResources() : resources;
- }
- @Override
- public AssetManager getAssets() {
- return assetManager == null ? super.getAssets() : assetManager;
- }
动态加载资源
两种不同风格的按钮,默认的是本应用提供的资源,还有一种作为另一个单独的插件APK程序存放在手机的其他路径中,当选择不同的风格时加载不同的图片资源。
插件APK仅仅包含了一些资源文件。
宿主程序的代码具体如下
- private AssetManager assetManager;
- private Resources resources;
- private RadioGroup rg;
- private ImageView iv;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- iv = (ImageView) findViewById(R.id.iv);
- rg = (RadioGroup) findViewById(R.id.rg);
- rg.setOnCheckedChangeListener(new OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(RadioGroup group, int checkedId) {
- switch (checkedId) {
- case R.id.default_skin:
- assetManager = null;
- resources = null;
- iv.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
- break;
- case R.id.skin1:
- String dexPath = "";
- try {
- dexPath = getPackageManager().getApplicationInfo("com.example.plugin", 0).sourceDir;
- } catch (NameNotFoundException e) {
- e.printStackTrace();
- }
- loadRes(dexPath);
- // 由于重写了getResources方法,因此这时返回的是我们自己维护的Resources对象,因此可以访问到他的编号id的资源
- iv.setImageDrawable(getResources().getDrawable(0x7f020000));
- break;
- }
- }
- });
- }
- public void loadRes(String path){
- try {
- assetManager = AssetManager.class.newInstance();
- Method addAssetPath = AssetManager.class.getMethod("addAssetPath", String.class);
- addAssetPath.invoke(assetManager, path);
- } catch (Exception e) {
- }
- resources = new Resources(assetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
- }
- @Override
- public Resources getResources() {
- return resources == null ? super.getResources() : resources;
- }
- @Override
- public AssetManager getAssets() {
- return assetManager == null ? super.getAssets() : assetManager;
- }
- public static final int ic_launcher=0x7f020000;
插件APK提供getImageId函数获取图片资源的id
- public class Plugin {
- public static int getImageId() {
- return R.drawable.ic_launcher;
- }
- }
- private void setImage(String dexPath) {
- DexClassLoader loader = new DexClassLoader(dexPath, getApplicationInfo().dataDir, null, this.getClass().getClassLoader());
- try {
- Class<?> clazz = loader.loadClass("com.example.plugin.Plugin");
- Method getImageId = clazz.getMethod("getImageId");
- int ic_launcher = (int) getImageId.invoke(clazz);
- iv.setImageDrawable(getResources().getDrawable(ic_launcher));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
插件管理的一种方式
对于每个插件,在AndroidManifest.xml中声明一个空的Activity,并添加他的action,比如:
- <activity
- android:name=".plugin" >
- <intent-filter>
- <action android:name="android.intent.plugin" />
- </intent-filter>
- </activity>
- PackageManager pm = getPackageManager();
- List<ResolveInfo> resolveinfos = pm.queryIntentActivities(intent, 0);
- ActivityInfo activityInfo = resolveinfos.get(i).activityInfo;
- dexPaths.add(activityInfo.applicationInfo.sourceDir);
- private AssetManager assetManager;
- private Resources resources;
- private LinearLayout ll;
- private ImageView iv;
- private Button btn;
- private List<String> dexPaths = new ArrayList<String>();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- iv = (ImageView) findViewById(R.id.iv);
- ll = (LinearLayout) findViewById(R.id.ll);
- btn = (Button) findViewById(R.id.btn);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- resources = null;
- iv.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
- }
- });
- Intent intent = new Intent("android.intent.plugin");
- PackageManager pm = getPackageManager();
- final List<ResolveInfo> resolveinfos = pm.queryIntentActivities(intent, 0);
- for (int i = 0; i < resolveinfos.size(); i++) {
- final ActivityInfo activityInfo = resolveinfos.get(i).activityInfo;
- dexPaths.add(activityInfo.applicationInfo.sourceDir);
- // 根据查询到的插件数添加按钮
- final Button btn = new Button(this);
- btn.setText("风格" +(i+1));
- btn.setTag(i);
- ll.addView(btn, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT));
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- int index = (Integer)btn.getTag();
- String dexPath = dexPaths.get(index);
- loadRes(dexPath);
- setImage(resolveinfos.get(index).activityInfo);
- }
- });
- }
- }
- private void setImage(ActivityInfo activityInfo) {
- DexClassLoader loader = new DexClassLoader(activityInfo.applicationInfo.sourceDir, getApplicationInfo().dataDir, null, this.getClass().getClassLoader());
- try {
- Class<?> clazz = loader.loadClass(activityInfo.packageName + ".Plugin");
- Method getImageId = clazz.getMethod("getImageId");
- int ic_launcher = (int) getImageId.invoke(clazz);
- iv.setImageDrawable(getResources().getDrawable(ic_launcher));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- public void loadRes(String path) {
- try {
- assetManager = AssetManager.class.newInstance();
- Method addAssetPath = AssetManager.class.getMethod("addAssetPath",
- String.class);
- addAssetPath.invoke(assetManager, path);
- } catch (Exception e) {
- e.printStackTrace();
- }
- resources = new Resources(assetManager, super.getResources()
- .getDisplayMetrics(), super.getResources().getConfiguration());
- }
- @Override
- public Resources getResources() {
- return resources == null ? super.getResources() : resources;
- }
- @Override
- public AssetManager getAssets() {
- return assetManager == null ? super.getAssets() : assetManager;
- }
com.example.plugin
|-- Plugin.java
com.example.plugin2
|-- Plugin.java
Plugin类的内容一样,为提供给宿主程序反射调用的类
注册空的activity
- <activity
- android:name=".plugin"
- android:label="@string/name" >
- <intent-filter>
- <action android:name="android.intent.plugin" />
- </intent-filter>
- </activity>
代码点此下载