Android 插件化基础

Android 原生语言为java,java文件通过java虚拟机后会变为.class文件,java文件虽然能在JVM中运行,但是想在Android运行环境中执行还需要特殊处理,那就是dx处理,它会对.class文件翻译、重构、解释、压缩等操作。

dx 处理会使用到一个工具 dx.jar,这个文件位于 SDK 中,具体的目录大致为 你的sdk根目录/build-tools/任意版本里面。使用 dx 工具处理.class 文件,执行完之后就会生成一个.dex文件,这个 .dex 文件就可以直接在 Android 运行时环境执行,一般可以通过 PathClassLoader 去加载 dex 文件。

PathClassLoader 和 DexClassLoader的区别:前者只能加载内存中已经安装的apk中的dex,而后者可以加载sd卡中的apk/jar ,因此 DexClassLoader 是热修复和插件化的基础。

Android 中,apk 安装时,系统会使用 PathClassLoader 来加载apk文件中的dex,PathClassLoader的构造方法中,调用父类的构造方法,实例化出一个 DexPathList ,DexPathList 通过 makePathElements 在所有传入的dexPath 路径中,找到DexFile,存入 Element 数组,在应用启动后,所有的类都在 Element 数组中寻找,不会再次加载。

在热更新时,实现 DexClassLoader 子类,传入要更新的dex/apk/jar补丁文件路径(如sd卡路径中存放的patch.jar),通过反射拿到 DexPathList,得到补丁 Element 数组,再从Apk原本安装时使用的 PathClassLoader 中拿到旧版本的 Element 数组,合并新旧数组,将补丁放在数组最前面,这样一个类一旦在补丁 Element 中找到,就不会再次加载,这样就能替换旧 Element 中的旧类,实现热更新。

插件化主要的两个问题,一个是资源,一个是Activity,Activity也会加载很多资源文件,本篇文章重点从加载资源文件说起,进一步说说 AssetManager 的 addAssetPath 方法来实现资源的插件化。

Android资源文件分为两类,一种是res目录下存放的可编译性文件,编译时,系统会自动在R.java中生成资源文件的十六进制

第二种是assets目录下存放的资源文件,因为apk在编译的时候不会编译assets下的资源文件,所以不能通过R.XX的方式访问,因为apk下载后不会解压到本地,所以我们无法直接获取到assets的绝对路径,这时候就要借助AssetManager类的open方法来获取assets目录下的文件资源了。

AssetManager中有个addAssetPath(String path)方法,在App启动的时候,会把当前apk的路径传递进去,接下来AssetManager和Resources 就能访问apk的所有资源了,由于addAssetPath方法是不对外的,我们只能通过反射的方式获取,然后把插件apk的路径传递过去,就把插件的资源添加到资源池中了。

package com.demo.dynamicdemo11;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import opencv.wanmao.com.mylibrary.IDynamic;

public class MainActivity extends AppCompatActivity {

    private TextView tv;
    private Button button;
    private String apkName = "plugin1.apk";    //apk名称

    private AssetManager mAssetManager;
    private Resources mResources;
    private Resources.Theme mTheme;
    private String dexpath = null;    //apk文件地址
    private File fileRelease = null;  //释放目录
    private DexClassLoader classLoader = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv = findViewById(R.id.tv);
        button = findViewById(R.id.btn_6);


        File extractFile = this.getFileStreamPath(apkName);
        dexpath = extractFile.getPath();
        fileRelease = getDir("dex", 0); //0 表示Context.MODE_PRIVATE
        classLoader = new DexClassLoader(dexpath,
                fileRelease.getAbsolutePath(), null, getClassLoader());

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                loadResources();
                Class mLoadClassDynamic = null;
//                Dynamic
                try {
                    mLoadClassDynamic = classLoader.loadClass("opencv.wanmao.com.plug1.Dynamic");
                    Object dynamicObject = mLoadClassDynamic.newInstance();

                    IDynamic dynamic = (IDynamic) dynamicObject;
                    String content = dynamic.getStringForResId(MainActivity.this);
                    Log.e("----demo-->>", "msg:>>>>>>>" + content);
                    tv.setText(content);
                    Toast.makeText(getApplicationContext(), content + "", Toast.LENGTH_LONG).show();
                } catch (Exception e) {
                    Log.e("----demo-->>", "msg:" + e.getMessage());
                }
            }
        });
    }

    //反射获取AssetManager并执行addAssetPath方法
    //反射获取AssetManager 并间接获取得到Resources
    protected void loadResources() {
        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();
        }

        mResources = new Resources(mAssetManager, super.getResources().getDisplayMetrics(), super.getResources().getConfiguration());
        mTheme = mResources.newTheme();
        mTheme.setTo(super.getTheme());
    }

    @Override
    public AssetManager getAssets() {
        if (mAssetManager == null) {
            Log.e("----demo-->>", "mAssetManager is null");
            return super.getAssets();
        }
        Log.e("----demo-->>", "mAssetManager is not null");
        return mAssetManager;
    }

    @Override
    public Resources getResources() {
        if (mResources == null) {
            Log.e("----demo-->>", "mResources is null");
            return super.getResources();
        }
        Log.e("----demo-->>", "mResources is not null");
        return mResources;
    }

    @Override
    public Resources.Theme getTheme() {
        if (mTheme == null) {
            Log.e("----demo-->>", "Theme is null");
            return super.getTheme();
        }
        Log.e("----demo-->>", "Theme is not null");
        return mTheme;
    }
}

在此记录下Android中的路径问题:

Context.getFilesDir

获取路径:/data/user/0/应用包名/files
该目录是应用的文件存储目录,应用被卸载时,该目录一同被系统删除。默认存在,默认具备读写权限(6.0系统可以不用向用户申请)

Context.getCacheDir

获取路径:/data/user/0/应用包名/cache
该目录是应用的文件缓存目录,应用被卸载时,该目录一同被系统删除。默认存在,默认具备读写权限。不同于getFileDir,该目录下的文件在系统内存紧张时,会被清空文件,来腾出空间供系统使用,著名的图片加载库ImageLoader就是在没有外置存储读写权限时使用此文件夹。getFileDir,不会因为系统内存不足而被清空。(6.0系统可以不用向用户申请)

Context.getObbDir

获取路径:/storage/emulated/0/Android/obb/应用包名 
该目录是应用的数据存放目录,一般被用来存放游戏数据包obb文件。默认存在,可读写(6.0系统可以不用向用户申请)

Context.CodeCacheDir

获取路径:/data/user/0/应用包名/code_cache
默认存在,可读写。(6.0系统可以不用向用户申请)

Context.getExternalFilesDir

获取路径:(以下载目录为准) /storage/emulated/0/Android/data/应用包名/files/Download
默认存在,可读写。(6.0系统可以不用向用户申请)

Context.getExternalCacheDir

获取路径:/storage/emulated/0/Android/data/应用包名/cache
默认存在,可读写。(6.0系统可以不用向用户申请)

Context.getDatabasePath

获取路径:/data/user/0/应用包名/databases/参数名
默认不存在,可读写。(6.0系统可以不用向用户申请)

Context.getDir

获取路径:/data/user/0/应用包名/app_参数名
默认存在,可读写。分为Private等三个权限,private代表仅能自己访问。(6.0系统可以不用向用户申请)

Context.getPackageCodePath

获取路径:/data/app/应用包名-1/base.apk
默认存在,获取apk包路径

Context.getRootDirectory

获取路径:/system
默认存在,不可读写(除非具备root权限)

Environment.getExternalStorageDirectory

获取路径:/storage/emulated/0
默认存在,声明权限则可读写(6.0和以后系统还需要向用户申请同意才可以)

Environment.getExternalStoragePublicDirectory

获取路径:/storage/emulated/0/Download(以下载目录为例)
默认存在,声明权限则可读写(6.0和以后系统还需要向用户申请同意才可以)

Context.getFileStreamPath

获取路径:/data/data/应用包名/files/download(示例download)
该目录是应用的文件存储目录,应用被卸载时,该目录一同被系统删除。默认存在,默认具备读写权限(6.0系统可以不用向用户申请

 

File f1=Environment.getDataDirectory();                                 //  /data
File f2=Environment.getDownloadCacheDirectory();                        //  /cache
File f3=Environment.getRootDirectory();                                 //  /system
File f4= context.getCacheDir();                                         //  /data/data/com.example.fileexiststest/cache
File f5= context.getDatabasePath("abc.db");                             //  /data/data/com.example.fileexiststest/databases/abc.db
File f6= context.getFilesDir();                                         //  /data/data/com.example.fileexiststest/files
File f7= context.getFileStreamPath("test2.txt");                        //  /data/data/com.example.fileexiststest/files/test2.txt

 

发布了15 篇原创文章 · 获赞 0 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qinggancha/article/details/103810884
今日推荐