目录

Android黑科技动态加载(一)之Java中的ClassLoader
Android黑科技动态加载(二)之Android中的ClassLoader
Android黑科技动态加载(三)之动态加载资源
Android黑科技动态加载(四)之插件化开发

项目地址

我们的认识

我们都知道, 在Android中我们获取一个资源只需要使用Context.getResource().getXXXX()就可以获取到对应的资源文件. 那么如果我们想要加载其他应用的res内容, 那么就应该构造出他们环境的Resource. 有了Resource还不行, 我们还需要获取资源文件的ID, 其中ID我们可以通过R.java文件通过反射获取.

所以我们的目标就是(分别对于已安装的应用和未安装的应用):

  • 构造出Resource
  • 获取资源的ID

ResourceBundle就是我们的资源包, 其中只有两张图片

android_resource_bundle_load.gif Android黑科技动态加载(三)之动态加载资源_第1张图片 android_resource_bundle_struct.png Android黑科技动态加载(三)之动态加载资源_第2张图片 android_resource_bundle_content.png

已经安装的应用

获取Resource

对于已经安装的应用, 获取Resource的方法很简单, 只要获取到Context就可以获取对应环境下的Resource了, 其中有一个方法Context.createPackageContext(String packageName, int flags)可以根据包名获取已经安装应用的Context.

首先我们建一个Bean来存储已经加载的资源

public class LoadedResource {    public Resources resources;    public String packageName;    public ClassLoader classLoader;}

然后我们就可以写加载的方法

/** * 获取已安装应用资源 *  * @param packageName */public LoadedResource getInstalledResource(String packageName) {    LoadedResource resource = mResources.get(packageName);  // 先从缓存中取, 没有就去加载    if (resource == null) {        try {            Context context = mContext.createPackageContext(packageName,                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);            resource = new LoadedResource();            resource.packageName = packageName;            resource.resources = context.getResources();            resource.classLoader = context.getClassLoader();            mResources.put(packageName, resource);  // 得到结果缓存起来        } catch (Exception e) {            e.printStackTrace();        }    }    return resource;}

至此, 我们就能获取到了Resource

获取资源ID

根据上面的思路, 我们使用反射区获取. 大概看一下R文件的结构

package com.example.resourcebundle;public final class R {    public static final class attr {    }    public static final class drawable {        public static final int image=0x7f020000;        public static final int image1=0x7f020001;    }    public static final class mipmap {        public static final int ic_launcher=0x7f030000;    }    public static final class string {        public static final int app_name=0x7f040000;    }    ...}

对应的资源类型都有一个静态内部类, 那么我们就可以使用反射无获取对应的数值

/** * 获取资源ID *  * @param packageName   包名 * @param type          对应的资源类型, drawable mipmap等 * @param fieldName * @return */public int getResourceID(String packageName, String type, String fieldName) {    int resID = 0;    LoadedResource installedResource = getInstalledResource(packageName);   // 获取已安装APK的资源    if (installedResource != null) {        String rClassName = packageName + ".R$" + type; // 根据匿名内部类的命名, 拼写出R文件的包名+类名        try {            Class cls = installedResource.classLoader.loadClass(rClassName);    //  加载R文件            resID = (Integer) cls.getField(fieldName).get(null);    //  反射获取R文件对应资源名的ID        } catch (Exception e) {            e.printStackTrace();        }    } else {        Log.w(TAG, "resource is null:" + packageName);    }    return resID;}

现在我们加载已经安装APK的资源的编码就已经完成.

调用

getDrawable("com.example.resourcebundle", "image1")

未安装的应用

我们先看一下getDrawable方法是怎么去获取资源的

public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)        throws NotFoundException {    final TypedValue value = obtainTempTypedValue();    try {        final ResourcesImpl impl = mResourcesImpl;        impl.getValue(id, value, true);        return impl.loadDrawable(this, value, id, theme, true);    } finally {        releaseTempTypedValue(value);    }}

上面代码我们可以看到, 资源其实是通过impl代理去拿到的, 继续...

void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)        throws NotFoundException {    boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);    if (found) {        return;    }    throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));}

然后再通过assets去代理获取, 继续看看assets从哪里设置的

public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,        @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {    mAssets = assets;    mMetrics.setToDefaults();    mDisplayAdjustments = displayAdjustments;    updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());    mAssets.ensureStringBlocks();}

再寻找ResourcesImpl的构造函数从哪里调用

public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {    this(null);    mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());}

最后, 我们回到Resource的构造函数中, 也就是说正确调用Resources的构造函数, 那么我们就能构造出正确的Resource

但是, 如何得到一个AssetManager呢? 大家请参考文章Android应用程序资源管理器(Asset Manager)的创建过程分析
, 里面说明了AssetManager的加载原理和过程. 我们按部就班反射调用public final int addAssetPath(String path)方法去添加资源文件路径.

那么我们现在就可以编码了

/** * 加载未安装应用资源包 *  * @param resourcePath * @return */public LoadedResource loadResource(String resourcePath) {    LoadedResource loadResource = null;    PackageInfo info = queryPackageInfo(resourcePath);  //  获取未安装APK的PackageInfo    if (info != null) { //  获取成功        loadResource = mRescources.get(info.packageName);   // 先从缓存中取, 存在则直接返回, 不重复添加. 否则就搜索添加        if (loadResource == null) {            try {                AssetManager assetManager = AssetManager.class.newInstance();   // 创建AssetManager实例                Class cls = AssetManager.class;                Method method = cls.getMethod("addAssetPath", String.class);                method.invoke(assetManager, resourcePath);  // 反射设置资源加载路径                Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),                        mContext.getResources().getConfiguration());    // 构造出正确的Resource                loadResource = new LoadedResource();                loadResource.resources = resources;                loadResource.packageName = info.packageName;                loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null,                        mContext.getClassLoader()); //  设置正确的类加载器, 因为需要去加载R文件                mRescources.put(info.packageName, loadResource);    // 缓存                Log.w(TAG, "build resource:" + resourcePath);            } catch (Exception e) {                e.printStackTrace();            }        }    }    Log.w(TAG, "load resource:" + resourcePath);    return loadResource;}/** * 获取未安装应用PackageInfo *  * @param resourcePath * @return */private PackageInfo queryPackageInfo(String resourcePath) {    return mContext.getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);}

既然我们现在已经获取了Resource, 那么下面获取资源文件就与上面是一样的

LoadedResource loadResource = loadResource("/storage/sdcard0/bundle.apk");Drawable drawable = getDrawable(loadResource.packageName, "image");

最后代码

LoadedResource.java

package com.example.host.res;import android.content.res.Resources;public class LoadedResource {    public Resources resources;    public String packageName; public ClassLoader classLoader;}

ResourceManager.java

package com.example.host.res;import java.io.File;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.res.AssetManager;import android.content.res.Resources;import android.graphics.drawable.Drawable;import android.util.Log;import dalvik.system.DexClassLoader;public class ResourceManager {    private static final String TAG = "ResourceManager";    private ResourceManager() {    }    public static void init(Context context) {        UnInstalled.sManager.init(context);        Installed.sManager.init(context);    }    public static UnInstalled unInstalled() {        return UnInstalled.sManager;    }    public static Installed installed() {        return Installed.sManager;    }    /**     * 针对于未安装应用     */    public static class UnInstalled {        static final UnInstalled sManager = new UnInstalled();        private Context mContext;        private Map mRescources = new HashMap();        private String mDexDir;        private UnInstalled() {        }        /**         * 初始化         *         * @param context         */        public void init(Context context) {            mContext = context.getApplicationContext();            File dexDir = mContext.getDir("dex", Context.MODE_PRIVATE);            if (!dexDir.exists()) {                dexDir.mkdir();            }            mDexDir = dexDir.getAbsolutePath();        }        /**         * 获取未安装应用资源的ID         *         * @param packageName         * @param fieldName         * @return         */        public int getResourceID(String packageName, String type, String fieldName) {            int resID = 0;            LoadedResource recource = getUnInstalledRecource(packageName);            String rClassName = packageName + ".R$" + type;            Log.w(TAG, "resource class:" + rClassName + ",fieldName:" + fieldName);            try {                Class cls = recource.classLoader.loadClass(rClassName);                resID = (Integer) cls.getField(fieldName).get(null);            } catch (Exception e) {                e.printStackTrace();            }            return resID;        }        /**         * 获取未安装应用Drawable         *         * @param packageName         * @param fieldName         * @return         */        public Drawable getDrawable(String packageName, String fieldName) {            Drawable drawable = null;            int resourceID = getResourceID(packageName, "drawable", fieldName);            LoadedResource recource = getUnInstalledRecource(packageName);            if (recource != null) {                drawable = recource.resources.getDrawable(resourceID);            }            return drawable;        }        /**         * 加载未安装应用资源包         *         * @param resourcePath         * @return         */        public LoadedResource loadResource(String resourcePath) {            LoadedResource loadResource = null;            PackageInfo info = queryPackageInfo(resourcePath);    //    获取未安装APK的PackageInfo            if (info != null) {    //   获取成功                loadResource = mRescources.get(info.packageName);    // 先从缓存中取, 存在则直接返回, 不重复添加. 否则就搜索添加                if (loadResource == null) {                    try {                        AssetManager assetManager = AssetManager.class.newInstance();    // 创建AssetManager实例                        Class cls = AssetManager.class;                        Method method = cls.getMethod("addAssetPath", String.class);                        method.invoke(assetManager, resourcePath);    // 反射设置资源加载路径                        Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),                                mContext.getResources().getConfiguration());    // 构造出正确的Resource                        loadResource = new LoadedResource();                        loadResource.resources = resources;                        loadResource.packageName = info.packageName;                        loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null,                                mContext.getClassLoader());    //   设置正确的类加载器, 因为需要去加载R文件                        mRescources.put(info.packageName, loadResource);    // 缓存                        Log.w(TAG, "build resource:" + resourcePath);                    } catch (Exception e) {                        e.printStackTrace();                    }                }            }            Log.w(TAG, "load resource:" + resourcePath);            return loadResource;        }        /**         * 获取未安装应用PackageInfo         *         * @param resourcePath         * @return         */        private PackageInfo queryPackageInfo(String resourcePath) {            return mContext.getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);        }        /**         * 获取未安装应用LoadResource         *         * @param packageName         * @return         */        public LoadedResource getUnInstalledRecource(String packageName) {            LoadedResource loadResource = mRescources.get(packageName);            if (loadResource == null) {                Log.w(TAG, "resource " + packageName + " not founded");            }            return loadResource;        }    }    /**     * 针对于已安装应用     */    public static class Installed {        static final Installed sManager = new Installed();        private Context mContext;        private Map mResources = new HashMap();        private Installed() {        }        /**         * 初始化         *         * @param context         */        public void init(Context context) {            mContext = context.getApplicationContext();        }        /**         * 获取已安装应用资源         *         * @param packageName         */        public LoadedResource getInstalledResource(String packageName) {            LoadedResource resource = mResources.get(packageName);    // 先从缓存中取, 没有就去加载            if (resource == null) {                try {                    Context context = mContext.createPackageContext(packageName,                            Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);                    resource = new LoadedResource();                    resource.packageName = packageName;                    resource.resources = context.getResources();                    resource.classLoader = context.getClassLoader();                    mResources.put(packageName, resource);    // 得到结果缓存起来                } catch (Exception e) {                    e.printStackTrace();                }            }            return resource;        }        /**         * 获取资源ID         *         * @param packageName         * @param type         * @param fieldName         * @return         */        public int getResourceID(String packageName, String type, String fieldName) {            int resID = 0;            LoadedResource installedResource = getInstalledResource(packageName);    // 获取已安装APK的资源            if (installedResource != null) {                String rClassName = packageName + ".R$" + type;    // 根据匿名内部类的命名, 拼写出R文件的包名+类名                try {                    Class cls = installedResource.classLoader.loadClass(rClassName);    //  加载R文件                    resID = (Integer) cls.getField(fieldName).get(null);    //  反射获取R文件对应资源名的ID                } catch (Exception e) {                    e.printStackTrace();                }            } else {                Log.w(TAG, "resource is null:" + packageName);            }            return resID;        }        /**         * 获取已加载应用Drawable         *         * @param packageName         * @param fieldName         * @return         */        public Drawable getDrawable(String packageName, String fieldName) {            Drawable drawable = null;            int resourceID = getResourceID(packageName, "drawable", fieldName);            LoadedResource installedResource = getInstalledResource(packageName);            if (installedResource != null) {                drawable = installedResource.resources.getDrawable(resourceID);            }            return drawable;        }    }}

activity_main.xml

    

MainActivity.java

package com.example.host;import android.app.Activity;import android.graphics.drawable.Drawable;import android.os.Bundle;import android.view.View;import android.widget.ImageView;import com.example.host.act.ActivityManager;import com.example.host.res.LoadedResource;import com.example.host.res.ResourceManager;public class MainActivity extends Activity {    ImageView imageView;    ActivityManager mPluginManager = ActivityManager.getInstance();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ResourceManager.init(this);        mPluginManager.init(this);        imageView = (ImageView) findViewById(R.id.imageView);    }    /**     * 加载已安装APK资源     *     * @param v     */    public void loadInstalledBundle(View v) {        Drawable drawable = ResourceManager.installed().getDrawable("com.example.resourcebundle", "image1");        if (drawable != null) {            imageView.setImageDrawable(drawable);        }    }    /**     * 加载未安装APK资源     *     * @param v     */    public void loadUninstalledBundle(View v) {        LoadedResource loadResource = ResourceManager.unInstalled().loadResource("/storage/sdcard0/bundle.apk");        Drawable drawable = ResourceManager.unInstalled().getDrawable(loadResource.packageName, "image");        if (drawable != null) {            imageView.setImageDrawable(drawable);        }    }}

测试加载安装APK资源就安装ResourceBundle应用, 测试加载未安装APK资源就把ResourceBundle应用放到指定位置

其中ResourceBundle只要包含image和image1的两个drawable就可以了

更多相关文章

  1. Android中的横竖屏、资源、国际化的使用
  2. android中的资源,资源与xml文件
  3. Android File Transfer – 在 Mac 上也能读取 Android 设备文件
  4. Android NDK入门实例 计算斐波那契数列一生成jni头文件
  5. android为文本框设置背景资源-shape
  6. Android studio project文件结构翻译

随机推荐

  1. Amazon 的平板能否威胁 Google
  2. Android开发者指南(1) —— Android Debu
  3. windows和linux下android sdk通用
  4. Android 性能优化之使用MAT分析内存泄露
  5. 【Android】学习笔记(9)——SQLite简单使
  6. 2012版辅助开发工具包(ADT)新功能特性介绍
  7. Mac下用Charles实现Android(安卓)http和h
  8. Android 内存优化
  9. android sql
  10. 【Android】 从头搭建视频播放器(1)——概