动态加载资源

1.资源分类

一个项目的目录大致入下,其中assets和res都是资源文件
在这里插入图片描述

assets中的资源:

1.通过AssetsManager访问;
2.在打包的时候不会编译成二进制文件;
3.不会在R.java中生成id;
4.在assets目录下可以创建子文件夹;

res文件中的资源:

1.通过context.getResource()访问;
2.会被编译成二进制文件,raw文件除外
3.会在编译时生成R文件

系统构造Resource的过程参考Resource管理器

2.加载其他已安装应用的资源文件:

2.1 实现关键代码:

1.通过Context的createPackageContext方法得到其他App的Context,从而得到Resource对象

	Context remoteContext = mContext.createPackageContext(packageName, flag);
	Resource remoteResource = context.getResources()

2.由于Resource中的资源只能通过ID来访问,现在我们只知道资源名,所以要通过查询R文件把资源名转化为资源ID:

	//根据packageName和资源类型拼写R文件的类名:
	String rClassName = packageName + ".R$" + type;    
	//通过反射加载R文件:
	Class cls = installedResource.classLoader.loadClass(rClassName);  
	//查询R文件得到资源名称对应的资源ID
    resID = (Integer) cls.getField(fieldName).get(null);   

3.有了Resource和资源ID,就可以轻易获得我们需要的资源:

	resources.getString(resourceID)
2.2 将上面的逻辑简单封住成一个工具类:

1.首先创建一个JavaBean装载解析结果

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

2.实现工具类:

public static class InstalledResourceLoader {
    static final InstalledResourceLoader loader = new InstalledResourceLoader ();
    private Context mContext;
    private Map<String, LoadedResource> mResources = new HashMap<String, LoadedResource>();

    private InstalledResourceLoader () {}

    public void init(Context context) {
        mContext = context.getApplicationContext();
    }

    public LoadedResource getInstalledResource(String packageName) {
    	// 先从缓存中取, 没有就去加载
        LoadedResource resource = mResources.get(packageName);    
        if (resource == null) {
            try {
            	//createPackageContext可以创建其它程序的Context
            	//通过创建的这个Context,就可以访问该软件包的资源,甚至可以执行其它软件包的代码
                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;
    }

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

    public String getString(String packageName, String fieldName) {
        String s = null;
        int resourceID = getResourceID(packageName, "string", fieldName);
        LoadedResource installedResource = getInstalledResource(packageName);
        if (installedResource != null) {
            s = installedResource.resources.getString(resourceID);
        }
        return s;
    }
}

3.加载未安装应用的资源文件:

3.1 实现关键代码:

1.构造一个路径去存放解压优化后的dex文件,这个路径是为了构造一个远端APK的类加载器:

	File dexDir = mContext.getDir("dex", Context.MODE_PRIVATE);
	mDexDir = dexDir.getAbsolutePath();

2.构造远端APK的类加载器,这个加载器用来加载R文件:

	loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null, mContext.getClassLoader());   

3.通过远端APK文件的完整路径获取PackageInfo,从而获取远端APK的包名:

	PackageInfo info = mContext.getPackageManager()
			.getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
	String packageName = info.packageName;

4.为AssetManager设置远端APK路径,然后用这个AssetManager构造Resources,这样就得到了这个APK中的Resource对象:

 	AssetManager assetManager = AssetManager.class.newInstance();    
    Class cls = AssetManager.class;
    Method method = cls.getMethod("addAssetPath", String.class);
    method.invoke(assetManager, resourcePath);    
    Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),

5.由于Resource中的资源只能通过ID来访问,现在我们只知道资源名,所以要通过查询R文件把资源名转化为资源ID:

	//根据packageName和资源类型拼写R文件的类名:
	String rClassName = packageName + ".R$" + type;    
	//通过反射加载R文件:
	Class cls = installedResource.classLoader.loadClass(rClassName);  
	//查询R文件得到资源名称对应的资源ID
    resID = (Integer) cls.getField(fieldName).get(null);   

6.有了Resource和资源ID,就可以轻易获得我们需要的资源:

	resources.getString(resourceID)
3.2 将上面的逻辑简单封住成一个工具类:

1.创建一个远端APK作为被加载对象,该项目中含有一个id为client_string的字符串,打包并放入sd卡根目录;

2.创建一个JavaBean装载解析结果

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

3.然后编写相关的工具类:

public class UnInstalledResourceLoader {
    static final UnInstalledResourceLoader loader = new UnInstalledResourceLoader ();
    private Context mContext;
    private Map<String, LoadedResource> mRescources = new HashMap<String, LoadedResource>();
    //由于解析APK只能用DexClassLoader,构造DexClassLoader需要一个路径mDexDir,作为dex解压缩优化后的存放目录
    private String mDexDir;
    
    private UnInstalled() {
    }
    
	//这里初始化主要是构造一个mDexDir,因为这个路径是构造DexClassLoader的必要参数
    public void init(Context context) {
        mContext = context.getApplicationContext();
        File dexDir = mContext.getDir("dex", Context.MODE_PRIVATE);
        if (!dexDir.exists()) {
            dexDir.mkdir();
        }
        mDexDir = dexDir.getAbsolutePath();
    }

    public int getResourceID(String packageName, String type, String fieldName) {
        int resID = 0;
        LoadedResource recource = mRescources.get(packageName);
        //寻找类名为com.example.client.R$string的类
        String rClassName = packageName + ".R$" + type;
        try {
            Class cls = recource.classLoader.loadClass(rClassName);
            //在com.example.client.R$string类中获取名为fieldName的成员变量的值,它就是这个资源的id
            resID = (Integer) cls.getField(fieldName).get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return resID;
    }

    public String getString(String packageName, String fieldName) {
        String s = null;
        int resourceID = getResourceID(packageName, "string", fieldName);
        LoadedResource recource = mRescources.get(packageName);
        if (recource != null) {
            s= recource.resources.getString(resourceID);
        }
        return s;
    }

    public LoadedResource loadResource(String resourcePath) {
        LoadedResource loadResource = null;
        //获取未安装APK的PackageInfo
        //这里必须要有sd卡读权限,否则info返回值为null
        PackageInfo info = mContext.getPackageManager().getPackageArchiveInfo(resourcePath, PackageManager.GET_ACTIVITIES);
        if (info != null) {    // 获取成功
        	// 先从缓存中取, 存在则直接返回, 不重复添加. 否则就搜索添加
            loadResource = mRescources.get(info.packageName);    
            if (loadResource == null) {
                try {
                	// 创建AssetManager实例
                    AssetManager assetManager = AssetManager.class.newInstance();    
                    Class cls = AssetManager.class;
                    Method method = cls.getMethod("addAssetPath", String.class);
                    // 反射设置资源加载路径
                    method.invoke(assetManager, resourcePath);    
                    Resources resources = new Resources(assetManager, mContext.getResources().getDisplayMetrics(),
                    // 构造出正确的Resource
                    mContext.getResources().getConfiguration());    
                    loadResource = new LoadedResource();
                    loadResource.resources = resources;
                    //这里解析得到的包名为com.example.client
                    loadResource.packageName = info.packageName;
                    // 设置正确的类加载器, 因为需要去加载R文件
                    loadResource.classLoader = new DexClassLoader(resourcePath, mDexDir, null,
                            mContext.getClassLoader());   
                    mRescources.put(info.packageName, loadResource);    // 缓存
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return loadResource;
    }
}

4.记得动态获取APK资源一定要SD卡读写权限:

 	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

	private boolean getPermission() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
           } 
    }

	@Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == 1) {                    //这里的requestCode 就是动态申请权限时传入的最后一个参数
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
              
            } 
        }
    }

5.最后验证是否可以成功调用资源文件:

    String url = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-release.apk";
    LoadedResource loadResource = ResourceManager.unInstalled().loadResource(url);
    String ss = ResourceManager.unInstalled().getString(loadResource.packageName, "client_string");
    Log.d("clientString = ", ss);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值