Android 动态加载资源

资源文件分类

1.android资源文件分为两类:
第一类是res目录下存放的可编译资源文件,编译时,系统会自动在R.java中生成资源文件的十六进制值,如下所示:

public final class R {
	public static final class id {
		public static final int action0 = 0x7f0b006d;
		...
	}
}

访问这种资源比较假单,使用Context的getResources方法得到Resorce对象,进而通过Resources的getXXX方法得到各种资源:

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

第二类是assets目录下存放的原始资源文件,apk在编译时不会编译assets下的资源文件,我们通过AssetManager对象来访问,AssetManager又来源于Resources类的getAssets方法:

Resources resources = getResources();
AssetManager am = getResources().getAssets();
InputStream is = getResources().getAssets().open("filename");

Resources是加载资源的重点。Resources内部各种方法其实都是间接调用AssetManager的内部方法,AssetManager负责向系统要资源。
在这里插入图片描述

访问外部资源原理

加载资源的原理推荐查看Android 换肤之资源(Resources)加载源码分析
Android资源动态加载以及相关原理分析

这里只是简单的说一下

context.getResources().getText()
##Resources
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
        CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                + Integer.toHexString(id));
    }

 ##ResourcesImpl
 public AssetManager getAssets() {
        return mAssets;
    }

内部是调用了mResourcesImpl去访问的,这个对象是ResourcesImpl类型,最后是通过AssetManager去访问资源的。现在可以得出一个结论,AssetManager是真正加载资源的对象,而Resources是app层面API调用的类。

AssetManager
/**
 * Provides access to an application's raw asset files; see {@link Resources}
 * for the way most applications will want to retrieve their resource data.
 * This class presents a lower-level API that allows you to open and read raw
 * files that have been bundled with the application as a simple stream of
 * bytes.
 */
public final class AssetManager implements AutoCloseable {

   /**
     * 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
     */
    @UnsupportedAppUsage
    public int addAssetPath(String path) {
        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
    }
}

这里非常的关键,需要解释一下,首先AssetManager是资源管理器,专门负责加载资源的,它内部有个隐藏方法addAssetPath,是用于加载指定路径下的资源文件,也就是说你把apk/jar的路径传给它,它就能把资源数据读到AssetManager,然后就可以访问了。

但是有个问题,虽然实际加载资源的是AssetManager,但是我们通过API访问的确是Resources对象,所以看下Resources对象的构造方法

ResourcesImpl的创建
/**
     * Create a new Resources object on top of an existing set of assets in an
     * AssetManager.
     *
     * @param assets Previously created AssetManager.
     * @param metrics Current display metrics to consider when
     *                selecting/computing resource values.
     * @param config Desired device configuration to consider when
     *               selecting/computing resource values (optional).
     */
    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
        this(null);
        mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
    }

看到这个构造方法,有点感觉了吧。可以通过AssetManager对象去构造mResourcesImpl对象,之前也分析过资源访问是通过mResourcesImpl.getAssets().getXXX()方法来完成的,那现在就有办法解决加载外部apk资源的问题了。

创建ResourcesImpl需要4个参数:

  • 参数一: AssetManager 具体资源管理(重要)

  • 参数二: DisplayMetrics 屏幕的一些封装
    通过getResources().getDisplayMetrics().density 获取过屏幕的密度
    通过getResources().getDisplayMetrics().widthPixels 获取过屏幕的宽度等

  • 参数三: Configuration 一些配置信息

  • 参数四: DisplayAdjustments 资源的兼容性等

加载外部apk资源的解决思路

首先,我们需要有3个工程:一个是宿主工程,用来加载外部资源;另一个是插件工程,用来提供外部资源。还有一个是公共库,定义了获取资源的接口方法。宿主工程和插件工程都引入该公共库。引入的方法:

File => Project Structure =>
在这里插入图片描述

插件工程
  1. 字符串资源定义
<string name="hello_message">Hello</string>
  1. 图片资源定义
    在drawable文件夹里放一个名为ic_baseline_train_24.png的图片

创建读取资源的类:

public class UIUtils implements IDynamic {
    public String getTextString(Context context){
        return context.getResources().getString(R.string.hello_message);
    }

    public Drawable getImageDrawable(Context ctx){
        return ctx.getResources().getDrawable(R.drawable.ic_baseline_train_24);
    }

    public View getLayout(Context ctx){
        LayoutInflater layoutInflater = LayoutInflater.from(ctx);
        View view = layoutInflater.inflate(R.layout.activity_main,null);
        return view;
    }
}

编译好该插件工程后,我们将生成的apk文件命名为plugin1.apk,将该apk文件复制到宿主文件的assets目录下:

#build.gradle

assemble.doLast {
    android.applicationVariants.all { variant ->
        // Copy Release artifact to HostApp's assets and rename
        if (variant.name == "release") {
            variant.outputs.each { output ->
                File originFile = output.outputFile
                println originFile.absolutePath
                copy {
                    from originFile
                    into "$rootDir/app/src/main/assets"
                    rename(originFile.name, "plugin1.apk")
                }
            }
        }
    }
}
宿主工程

我们创建一个宿主工程,并在应用启动的时候将assets下的插件apk复制到sd卡下 /data/data/包名/files的路径下面,然后加载插件工程生成的apk文件,并显示出插件里的资源。

public class BaseActivity extends Activity {
    private AssetManager mAssetManager;
    public Resources mResources;
    private Resources.Theme mTheme;

    protected HashMap<String, PluginInfo> plugins = new HashMap<String, PluginInfo>();

    private String dexPath1,dexPath2;   //apk文件地址
    private String fullReleaseFilePath; //释放目录
    private String plugin1name = "plugin1.apk";
    private String plugin2name = "plugin2.apk";

    public ClassLoader classLoader1,classLoader2;
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        Utils.extractAssets(newBase,plugin1name);
        Utils.extractAssets(newBase,plugin2name);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        genegatePluginInfo(plugin1name);
        genegatePluginInfo(plugin2name);

        fullReleaseFilePath = getDir("dex",0).getAbsolutePath();
        dexPath1 = this.getFileStreamPath(plugin1name).getPath();
        dexPath2 = this.getFileStreamPath(plugin2name).getPath();

        classLoader1 = new DexClassLoader(dexPath1,
                fullReleaseFilePath,null,getClassLoader());
        classLoader2 = new DexClassLoader(dexPath2,
                fullReleaseFilePath,null,getClassLoader());
    }

/**
     * 加载外部的插件,生成插件对应的ClassLoader
     * @param pluginName
     */
    protected void genegatePluginInfo(String pluginName) {
        File extractFile = this.getFileStreamPath(pluginName);
        File fileRelease = getDir("dex", 0);
        String dexpath = extractFile.getPath();
        DexClassLoader classLoader = new DexClassLoader(dexpath, fileRelease.getAbsolutePath(), null, getClassLoader());

        plugins.put(pluginName, new PluginInfo(dexpath, classLoader));
    }

 /**
     * 重要
     * 通过反射,创建AssetManager对象,调用addAssetPath方法,把插件Plugin的路径添加到这个AssetManager对象中
     * @param dexPath
     */
    protected void loadResources(String dexPath) {
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath);
            mAssetManager = assetManager;
        } catch (Exception e) {
            e.printStackTrace();
        }
        Resources superRes = super.getResources();
        mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }
 }
 
 /**
     * 重要
     * 重写Acitivity的getAsset,getResources和getTheme方法
     * mAssetManager是指向插件的,如果这个对象为空,就调用父类ContextImpl的getAssets方法,
     * 这个时候得到的AssetManager对象就指向宿主HostApp,读取的资源也就是HostApp中的资源
     * @return
     */
 @Override
    public AssetManager getAssets() {
        if(mAssetManager == null){
            return super.getAssets();
        }
        return mAssetManager;
    }

    @Override
    public Resources getResources() {
        if(mResources == null){
            return super.getResources();
        }
        return mResources;
    }

    @Override
    public Resources.Theme getTheme() {
        if(mTheme == null){
            return super.getTheme();
        }
        return mTheme;
    }

这里创建了一个基类BaseActivity来做 加载APK资源之前的准备工作。真正的加载APK资源是在MainActivity中:

public class MainActivity extends BaseActivity {

    private TextView textView;
    private ImageView imageView;
    private LinearLayout layout;
    private Button btn1,btn2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text);
        imageView = findViewById(R.id.imageview);
        layout = findViewById(R.id.layout);

        btn1 = findViewById(R.id.btn1);
        btn2 = findViewById(R.id.btn2);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PluginInfo pluginInfo = plugins.get("plugin1.apk");

                loadResources(pluginInfo.getDexPath());

//                doSomething(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
                doSomethingOther(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
//                doSomethingAnother(pluginInfo.getClassLoader(),"com.chinatsp.plugin1");
            }
        });

        btn2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                PluginInfo pluginInfo = plugins.get("plugin2.apk");

                loadResources(pluginInfo.getDexPath());

//                doSomething(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
//                doSomethingOther(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
                doSomethingAnother(pluginInfo.getClassLoader(),"com.chinatsp.plugin2");
            }
        });

        System.out.println(getString(R.string.hello));
    }

/**
     * 通过反射,获取插件中的类,构造出插件类的对象uiUtils,再反射调用插件类对象UIUtils中的方法
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomething(ClassLoader cl,String uiUtilsPkgName) {
        try {
            Class clazz = cl.loadClass(uiUtilsPkgName + ".UIUtils");
            Object uiUtils = RefInvoke.createObject(clazz);
            String str = (String) RefInvoke.invokeInstanceMethod(uiUtils, "getTextString", Context.class, this);
            textView.setText(str);

            Drawable drawable = (Drawable) RefInvoke.invokeInstanceMethod(uiUtils, "getImageDrawable", Context.class, this);
            imageView.setBackground(drawable);

            layout.removeAllViews();

            View view = (View) RefInvoke.invokeInstanceMethod(uiUtils, "getLayout",Context.class,this);
            layout.addView(view);

        } catch (Exception e) {
            Log.e("DEMO", "msg:" + e.getMessage());
        }
    }

 /**
     * 直接反射获取插件类中的R文件R.java的内部类,获取内部类中资源文件对应生成的16进制的值,也就R.string.xxx
     * R.drawable.xxx对应的值,通过getResources方法的getxxx方法来获取资源文件
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomethingOther(ClassLoader cl,String uiUtilsPkgName) {
        try {
            Class stringClass = cl.loadClass(uiUtilsPkgName + ".R$string");
            int resId1 = (int) RefInvoke.getStaticFieldObject(stringClass,"hello_message");
            textView.setText(getResources().getString(resId1));

            Class drawableClass = cl.loadClass(uiUtilsPkgName + ".R$drawable");
            int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass,"ic_baseline_train_24");
            imageView.setBackground(getResources().getDrawable(resId2));

            Class layoutClass = cl.loadClass(uiUtilsPkgName + ".R$layout");
            int resId3 = (int) RefInvoke.getStaticFieldObject(layoutClass,"activity_main");
            View view = LayoutInflater.from(this).inflate(resId3,null);

            layout.removeAllViews();
            layout.addView(view);
        } catch (Exception e) {
            Log.e("DEMO", "msg:" + e.getMessage());
        }
    }

 /**
     * 通过反射,获取插件中的类,构造出插件类的对象dynamicObject,再直接调用插件类对象UIUtils中的方法
     * @param cl
     * @param uiUtilsPkgName
     */
    private void doSomethingAnother(ClassLoader cl,String uiUtilsPkgName) {
        Class mLoadClassDynamic = null;
        try {
            mLoadClassDynamic = cl.loadClass(uiUtilsPkgName + ".UIUtils");
            Object dynamicObject = mLoadClassDynamic.newInstance();
            IDynamic dynamic = (IDynamic) dynamicObject;
            String str = dynamic.getTextString(this);
            textView.setText(str);

            Drawable drawable = dynamic.getImageDrawable(this);
            imageView.setBackground(drawable);

            layout.removeAllViews();
            View view = dynamic.getLayout(this);
            layout.addView(view);

        } catch (Exception e) {
            Log.e("DEMO", "msg:" + e.getMessage());
        }
    }

}

Source Code

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android开发中,动态加载图文通常可以通过以下步骤实现: 1. 准备资源:首先需要准备好要加载的图文资源,包括图片和文本内容。可以将图片资源放置在res目录下的drawable文件夹中,文本内容可以以字符串的形式定义在strings.xml文件中。 2. 布局文件:创建一个布局文件,用于显示动态加载的图文内容。可以使用LinearLayout、RelativeLayout等布局容器,在布局中定义好要显示的ImageView和TextView等控件。 3. 加载图片:使用ImageView控件来显示图片,可以通过以下代码动态加载图片资源: ``` ImageView imageView = findViewById(R.id.imageView); imageView.setImageResource(R.drawable.image); ``` 其中,R.drawable.image代表要加载的图片资源。 4. 加载文本内容:使用TextView控件来显示文本内容,加载字符串资源可以通过以下代码实现: ``` TextView textView = findViewById(R.id.textView); textView.setText(getString(R.string.text)); ``` 其中,R.string.text代表要加载的字符串资源。 5. 将布局文件加载到Activity中:在Activity的onCreate方法中,通过setContentView方法将布局文件加载到Activity中: ``` @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } ``` 通过以上步骤,就可以实现在Android动态加载图文了。注意,以上代码仅示例,具体实现方式可以根据需求进行调整和扩展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值