Android APK加载外部资源文件

1、背景

看了点东西,写点东西

2、资源加载

总所周知,Android资源加载通过Resource和AssetManager,比如一个textview的setText过程(api 21):

1、textView.setText(R.string.str);
//调用Resrouce的getText()
2public final void setText(int resid) {
        setText(getContext().getResources().getText(resid));
    }
//调用AssetManager的getResourceText()方法
3public CharSequence getText(int id) throws NotFoundException {
        CharSequence res = mAssets.getResourceText(id);
        if (res != null) {
            return res;
        }
        throw new NotFoundException("String resource ID #0x"
                                    + Integer.toHexString(id));
    }
//AssetManager的getResourceText()具体实现
4final CharSequence getResourceText(int ident) {
        synchronized (this) {
            TypedValue tmpValue = mValue;
            int block = loadResourceValue(ident, (short) 0, tmpValue, true);
            if (block >= 0) {
                if (tmpValue.type == TypedValue.TYPE_STRING) {
                    return mStringBlocks[block].get(tmpValue.data);
                }
                return tmpValue.coerceToString();
            }
        }
        return null;
    }

而AssetManager提供了一个方法addAssetPath()来加载外部资源

	/**
     * 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 final int addAssetPath(String path) {
        synchronized (this) {
            int res = addAssetPathNative(path);
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }

因此可以想办法通过手动创建这两个类的方式,达到加载外部资源的目的。

3、具体实现

3.1、创建AssetManager和Resource

为了调用addAssetPath方法,需要一个AssetManaer实例(该类是隐藏的,不能直接new),方法有:

  • 1、context的getAssets()方法返回
  • 2、反射

这里用方法2,为什么不用方法1,因为会把原来的资源文件替换掉。因此具体实现如下:

	private void initAssetManager(File apkFile) throws Exception {
		//创建AssetManager
        mAssetManager = AssetManager.class.newInstance();
        
        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        addAssetPathMethod.setAccessible(true);
        int result = (int) addAssetPathMethod.invoke(mAssetManager, apkFile.getAbsolutePath());
        Log.e(TAG, "initAssetManager: addAssetPath " + result);
		//创建Resource
		mResources = new Resources(mAssetManager, getResources().getDisplayMetrics(), getResources().getConfiguration());
    }

在application初始化中加载外部资源apk文件

	private AssetManager mAssetManager;
    private Resources mResources;

    @Override
    public void onCreate() {
        super.onCreate();
        File file = new File(getFilesDir(), "skin.apk");
        try {
            initAssetManager(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initAssetManager(File apkFile) throws Exception {
        mAssetManager = AssetManager.class.newInstance();
        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        addAssetPathMethod.setAccessible(true);
        int result = (int) addAssetPathMethod.invoke(mAssetManager, apkFile.getAbsolutePath());
        Log.e(TAG, "initAssetManager: addAssetPath " + result);
        //
        Method ensureStringBlocksMethod = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
        ensureStringBlocksMethod.setAccessible(true);
        ensureStringBlocksMethod.invoke(mAssetManager);

        mResources = new Resources(mAssetManager, getResources().getDisplayMetrics(), getResources().getConfiguration());

    }

    @Override
    public AssetManager getAssets() {
        return mAssetManager == null ? super.getAssets() : mAssetManager;
    }

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

4、简单测试

4.1、 主app

主app中编写BaseActivity用来获取加载外部资源的AssetManager和Resource

	private AssetManager mAssetManager;
    private Resources mResources;

    private boolean open = true;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAssetManager = getApplication().getAssets();
        mResources = getApplication().getResources();
    }


    @Override
    public AssetManager getAssets() {
        return mAssetManager != null && isOpen() ? mAssetManager : super.getAssets();
    }

    @Override
    public Resources getResources() {
        return mResources != null && isOpen() ? mResources : super.getResources();
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(getIdentifier(layoutResID));
    }

    @Override
    public <T extends View> T findViewById(int id) {
        return super.findViewById(getIdentifier(id));
    }

    protected boolean isOpen(){
        return open;
    }

    /**
     * 获取该id对应的外部资源的资源id
     * @param id
     * @return
     */
    public int getIdentifier(int id){
        try {
            String resourceEntryName = BaseActivity.super.getResources().getResourceEntryName(id);
            String resourceTypeName = BaseActivity.super.getResources().getResourceTypeName(id);
            Log.e(TAG, "getIdentifier: " + resourceEntryName + " " + resourceTypeName);
            int identifier = ((App)getApplication()).getResIdentifier(resourceEntryName, resourceTypeName);
            Log.e(TAG, "getIdentifier: " + identifier);
            return identifier > 0 ? identifier : id;
        } catch (Exception e){
            e.printStackTrace();
        }
        return id;
    }

添加布局文件和字符串资源

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

string.xml
<resources>
    <string name="app_name">AppPlugin</string>
    <string name="test_str">原本的数据</string>
</resources>

在activity中设置数据

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = findViewById(R.id.text);
        textView.setText(R.string.test_str);
        Toast.makeText(this, R.string.test_str, Toast.LENGTH_SHORT).show();

    }
}
4.2、资源包app

创建同样命名的布局文件和字符串资源(资源包不需要写java代码)

//添加多一个控件区分
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="皮肤内的布局" 
        android:layout_centerHorizontal="true"/>

</RelativeLayout>

string.xml
<resources>
    <string name="app_name">skin</string>
    <string name="test_str">皮肤里的数据111111</string>
</resources>

打包出来(Build --> Make Module )放在指定文件夹加载
在这里插入图片描述

4.3、加载资源

在application中加载外部资源

@Override
    public void onCreate() {
        super.onCreate();
        File file = new File(getFilesDir(), "skin-debug.apk");
        try {
            initAssetManager(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

效果对比:
在这里插入图片描述

发布了19 篇原创文章 · 获赞 25 · 访问量 5594

猜你喜欢

转载自blog.csdn.net/pxq10422/article/details/104128999