Android插件化(三)加载插件apk中的Resource资源

Android加载插件apk中的Resource资源

简介

如何加载未安装apk中的资源文件呢?我们从android.content.res.AssetManager.java的源码中发现,它有一个私有方法addAssetPath,只需要将apk的路径作为参数传入,我们就可以获得对应的AssetsManager对象,然后我们就可以使用AssetsManager对象,创建一个Resources对象,然后就可以从Resource对象中访问apk中的资源了。总结如下:

  • 1.新建一个AssetManager对象
  • 2.通过反射调用addAssetPath方法
  • 3.以AssetsManager对象为参数,创建Resources对象即可。

代码如下:

package net.mobctrl.hostapk;

import java.io.File;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;

/**
 * @Author Zheng Haibo
 * @PersonalWebsite http://www.mobctrl.net
 * @version $Id: LoaderResManager.java, v 0.1 2015年12月11日 下午7:58:59 mochuan.zhb
 *          Exp $
 * @Description 动态加载资源的管理器
 */
public class BundlerResourceLoader {

    private static AssetManager createAssetManager(String apkPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            try {
                AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(
                        assetManager, apkPath);
            } catch (Throwable th) {
                System.out.println("debug:createAssetManager :"+th.getMessage());
                th.printStackTrace();
            }
            return assetManager;
        } catch (Throwable th) {
            System.out.println("debug:createAssetManager :"+th.getMessage());
            th.printStackTrace();
        }
        return null;
    }

    /**
     * 获取Bundle中的资源
     * @param context
     * @param apkPath
     * @return
     */
    public static Resources getBundleResource(Context context){
        AssetsManager.copyAllAssetsApk(context);
        File dir = context.getDir(AssetsManager.APK_DIR, Context.MODE_PRIVATE);
        String apkPath = dir.getAbsolutePath()+"/BundleApk.apk";
        System.out.println("debug:apkPath = "+apkPath+",exists="+(new File(apkPath).exists()));
        AssetManager assetManager = createAssetManager(apkPath);
        return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
    }

}

DEMO

注意:我们使用Resources对象,获取资源时,传递的ID必须是离线apk中R文件对应的资源的ID。如果使用getIdentifier方法,第一个参数是资源名称,第二个参数是资源类型,第三个参数是离线apk的包名,切记第三个参数。

Resources resources = BundlerResourceLoader.getBundleResource(getApplicationContext());
        imageView = (ImageView)findViewById(R.id.image_view_iv);
        if(resources != null){
            String str = resources.getString(resources.getIdentifier("test_str", "string", "net.mobctrl.normal.apk"));
            String strById = resources.getString(0x7f050001);//注意,id参照Bundle apk中的R文件
            System.out.println("debug:"+str);
            Toast.makeText(getApplicationContext(),strById, Toast.LENGTH_SHORT).show();

            Drawable drawable = resources.getDrawable(0x7f020000);//注意,id参照Bundle apk中的R文件
            imageView.setImageDrawable(drawable);
        }

上述代码是加载离线apk中的字符串和Drawable资源,那么layout资源呢?

问题引入

我们使用LayoutInflate对象,一般使用方法如下:

View view = LayoutInflater.from(context).inflate(R.layout.main_fragment, null);

其中,R.layout.main_fragment我们可以通过上述方法获取其ID,那么关键的一步就是如何生成一个context?直接传入当前的context是不行的。

解决方案有2个:

- 1.创建一个自己的ContextImpl,Override其方法。

- 2.通过反射,直接替换当前context的mResources私有成员变量。<>br

当然,我们是使用第二种方案:

    @Override
    protected void attachBaseContext(Context context) {
        replaceContextResources(context);
        super.attachBaseContext(context);
    }

    /**
     * 使用反射的方式,使用Bundle的Resource对象,替换Context的mResources对象
     * @param context
     */
    public void replaceContextResources(Context context){
        try {
            Field field = context.getClass().getDeclaredField("mResources");
            field.setAccessible(true);
            field.set(context, mBundleResources);
            System.out.println("debug:repalceResources succ");
        } catch (Exception e) {
            System.out.println("debug:repalceResources error");
            e.printStackTrace();
        }
    }

我们在Activity的attachBaseContext方法中,对Context的mResources进行替换,这样,我们就可以加载离线apk中的布局了。

资源文件的打包过程

如果想要做到插件化,需要了解Android资源文件的打包过程,这样可以为每一个插件进行编号,然后按照规则生成R文件。例如,以携程DynamicAPK为例,它将插件的R文件按照如下规则:

  • 1.R文件为int型,前8位代表插件的Id,其中两个特殊的Id:Host是0x7f,android系统自带的是以0x01开头.
  • 2.紧跟着的8位是区分资源类型的,比如layout,id,string,dimen等
  • 3.后面16位是资源的编号

按照上述规则生成对应的插件apk。然后在运行时,我们可以写一个ResourceManager类,它继承自Resource对象,然后所有的Activity,都将其context的mResource成员变量修改为ResourceManager类,然后Override其方法,然后在加载资源时,根据不同的id的前缀,查找对应插件的Resource即可。也就是说,用一个类做分发。

Android插件化相关资料

时间: 2024-12-09 11:09:27

Android插件化(三)加载插件apk中的Resource资源的相关文章

Android下将图片加载到内存中

Android的系统的标准默认每个应用程序分配的内存是16M.所以来说是非常宝贵的,在创建应用的时候要尽可能的去节省内存,但是在加载一些大的文件的时候,比如图片是相当耗内存的,一个1.3M的图片,分辨率是2560X1920(宽X高)图片当加载到手机内存的时候就会请求19M的一块内存,这是远远超出了系统自带的内存空间,这时候应用程序就会挂掉,所以我们要进行图片的缩放处理,下面我就来带大家创建一个用来图片缩放的应用: 应用效果图如下: 核心代码的实现: package com.examp.loadp

android插件化开发——加载广播

阅读本文前,先阅读前面几篇: http://blog.csdn.net/u013022222/article/details/51171720 引言 在android开发过程中,我们不可避免的会使用广播,比如,侦听开机,侦听短信. 而对于广播,我想很多人都知道他有两种类型,动态广播,通过代码在runtime进行register, 像这样: IntentFilter intentFilter = new IntentFilter("com.chan.plugin.receiver");

Android插件化开发---运行未安装apk中的Service

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法.其实一般的插件开发主要也就是加载个Activity,读取一些资源图片之类的.但是总有遇到特殊情况的时候,比如加载Service. 要动态加载Service,有两种思路:一是通过NDK的形式,将Service通过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现

基于.NET MVC的高性能IOC插件化架构(二)之插件加载原理

上一篇博文简单介绍了下插件化的代码组成部门:http://www.cnblogs.com/gengzhe/p/4390932.html 这篇博客主要讲解下插件化实现的原理,先面先讲解几个概念: 一.契约 插件与系统必须有契约,系统才能发现插件并正确加载插件,我采用的是所有插件都实现Sun.Core里面的IPlugin接口. 二.自述 插件在被加载的时候,需要告诉系统,我是什么类型的插件,我的guid,我依赖的程序集,我的状态与权限,我的配置信息等等,这个行为是插件的自我描述,简称自述. 三.配置

android开发之Fragment加载到一个Activity中

Fragments 是android3.0以后添加的.主要是为了方便android平板端的开发.方便适应不同大小的屏幕.此代码是为了最简单的Fragment的使用,往一个Activity中添加Fragment,主要涉及的知识点有:1.Fragment类的创建,2.Fragment的添加3.无UI的 Fragment的添加,根据Tag找回Fragment Fragment对应的Xml布局文件, <LinearLayout xmlns:android="http://schemas.andro

android插件开发——加载插件

在阅读本博文的时候,我假设你已经阅读了我之前写的几篇.猛击此处 通过前面的几篇博客,我们解决了如何启动一个并没有在ActivityManifest.xml中声明的activity.但是有很多细心的读者私信我说,我们所有的例子里,插件都是和主工程在一起的呀,我们如何从外部加载一个apk获得dex呢? 本节就是解决这个问题. 在学习本节之前,有一些非常重要的概念需要提一下.比如类加载器的概念. 我们知道在java里面,有很多种加载器,如果按层次划分的话,可以分为 在加载类的时候,他们采用委托机制,比

Android插件化开发之OpenAtlas插件启动方式与插件启动广播

到现在为止已经写了6篇文章了 Android插件化开发之OpenAtlas初体验 Android插件化开发之OpenAtlas生成插件信息列表 Android插件化开发之OpenAtlas资源打包工具补丁aapt的编译 Android插件化开发之OpenAtlas插件适配 Android插件化开发之解决OpenAtlas组件在宿主的注册问题 Android插件化开发之OpenAtlas中四大组件与Application功能的验证 这篇文章主要介绍一下OpenAtlas插件的几种启动方式,在Atl

Qt中如何 编写插件 加载插件 卸载插件

Qt中如何 编写插件 加载插件 卸载插件是本文要介绍的内容.Qt提供了一个类QPluginLoader来加载静态库和动态库,在Qt中,Qt把动态库和静态库都看成是一个插件,使用QPluginLoader来加载和卸载这些库.由于在开发项目的过程中,要开发一套插件系统,就使用了Qt的这套类库. 一 编写插件 编写一个Qt的插件需要以下步骤 1.声明一个插件类, 2.定义一个类,实现这个插件类定义的接口,定义的这个类必须从QObject集成下来. 3.使用Q_INTERFACESQ_INTERFACE

【转】Selenium2(WebDriver)总结(一)---启动浏览器、设置profile&amp;加载插件

基本读踩过的坑,泪流满面··· 本文主要记录下在使用selenium2/webdriver时启动各种浏览器的方法.以及如何加载插件.定制浏览器信息(设置profile)等 环境搭建可参考我的另一篇文章:http://www.cnblogs.com/puresoul/p/3483055.html 一.Driver下载地址: http://docs.seleniumhq.org/download/ 二.启动firefox浏览器(不需要下载驱动,原生支持) 1.firefox安装在默认路径下: 1 /