Android小知识-如何加载外部dex文件中的类

本平台的文章更新会有延迟,你们能够关注微信公众号-顾林海,包括年末前会更新kotlin由浅入深系列教程,目前计划在微信公众号进行首发,若是你们想获取最新教程,请关注微信公众android

想要了解插件化,首先得知道如何加载外部的dex文件,这里的插件APK会存放在主APP的assets目录中,用于模拟服务器下载插件。web

第一步:建立主项目和插件项目bash

先建立咱们的主项目,并在项目中建立一个插件依赖库,取名为pluginlibrary,主项目依赖pluginlibrary。服务器

主项目建立完毕后,接着建立插件项目,将项目中的app模块复制到主项目并重命名为plugin,同时也依赖pluginlibrary。微信

修改settings.gradle文件,以下:app

include ':app',':plugin'':pluginlibrary'
复制代码

从新编译一下。ide

第二步:编译插件APKgradle

将pluginlibrary依赖库编译成jar包,并放在插件项目plugin的lib目录下,不是libs目录,经过compileOnly引用pluginlibrary的jar包,compileOnly只会在编译时用到相应的jar,打包成APK后不会存在于APK中。ui

pluginlibrary编译jar包,在pluginlibrary的build.gradle的配置以下:this

apply plugin: 'com.android.library'

android {
    compileSdkVersion 28

    defaultConfig {
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

task clearJar(type: Delete){
    delete 'build/outputs/pluginlibray.jar'
}

task makePluginLibraryJar(type: Copy){
    from ('build/intermediates/packaged-classes/release/')
    into ('build/outputs/')
    include ('classes.jar')
    rename ('classes.jar', 'pluginlibrary.jar')
}

makePluginLibraryJar.dependsOn(clearJar,build)
复制代码

编译完成后能够从右侧的Gradle面板的other分组中找到makePluginLibraryJar命令:

微信图片_20181009100628.png

双击makePluginLibraryJar命令进行编译,能够看到底部输出编译成功:

BUILD SUCCESSFUL in 4s
50 actionable tasks: 2 executed, 48 up-to-date
10:04:10: Task execution finished 'makePluginLibraryJar'.
复制代码

在pluginlibrary/build/outputs/下看到pluginlibrary.jar:

微信截图_20181009100901.png

在plugin项目中建立lib文件夹并将pluginlibrary.jar复制到lib目录下:

plugin项目的build.gradle修改以下:

    compileOnly files("lib/pluginlibrary.jar")
复制代码

第三步:加载外部dex

在编译pluginlibrary.jar以前在项目中建立一个接口:

package com.plugin.administrator.pluginlibrary;

public interface IPluginBean {
    void setUserName(String name);
    String getUserName();
}

复制代码

在插件plugin项目中就建立一个类:

package com.plugin.administrator.myapplication;

import com.plugin.administrator.pluginlibrary.IPluginBean;

public class UserInfo implements IPluginBean {

    private String name="billgu";

    @Override
    public void setUserName(String s) {
        this.name=s;
    }

    @Override
    public String getUserName() {
        return name;
    }
}

复制代码

编译插件plugin项目,将生成的apk复制到主项目的assets目录下。

接下来就是主项目编写加载外部DEX文件了,须要把assets目录下的plugin-debug.apk复制到/data/data/files目录下,这步操做放在Activity的attachBaseContext方法中:

private String apkName = "plugin-debug.apk";    //apk名称
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        try {
            extractAssets(newBase, apkName);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    public static void extractAssets(Context context, String sourceName) {
        AssetManager am = context.getAssets();
        InputStream is = null;
        FileOutputStream fos = null;
        try {
            is = am.open(sourceName);
            File extractFile = context.getFileStreamPath(sourceName);
            fos = new FileOutputStream(extractFile);
            byte[] buffer = new byte[1024];
            int count = 0;
            while ((count = is.read(buffer)) > 0) {
                fos.write(buffer, 0, count);
            }
            fos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            closeSilently(is);
            closeSilently(fos);
        }

    }

    private static void closeSilently(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (Throwable e) {
        }
    }
复制代码

如何从APK中读取dex文件,须要借助于DexClassLoader ,声明以下:

   DexClassLoader (String dexPath,
              String optimizedDirectory,
             String libraryPath, 
              ClassLoader parent)
复制代码
  • dexPath:  指目标类所在的jar/apk文件路径, 多个路径使用 File.pathSeparator分隔, Android里面默认为 “:”

  • optimizedDirectory: 解压出的dex文件的存放路径,以避免被注入攻击,不可存放在外置存储。

  • libraryPath :目标类中的C/C++库存放路径。

  • parent: 父类装载器

在onCreate方法中进行初始化DexClassLoader:

     private String mDexPath = null;    //apk文件地址
    private File mFileRelease = null;  //释放目录
    private DexClassLoader mClassLoader = null;
    
    private void initDexClassLoader(){
        File extractFile = this.getFileStreamPath(apkName);
        mDexPath = extractFile.getPath();
        mFileRelease = getDir("dex", 0); //0 表示Context.MODE_PRIVATE
        mClassLoader = new DexClassLoader(mDexPath,
                mFileRelease.getAbsolutePath(), null, getClassLoader());
    }
复制代码

生成插件APK的classLoader后就能够加载插件plugin-debug.apk中的任何类了。

点击按钮事件以下:

buttonGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                Class mLoadClassBean;
                try {
                    mLoadClassBean = mClassLoader.loadClass("com.plugin.administrator.myapplication.UserInfo");
                    Object beanObject = mLoadClassBean.newInstance();

                    IPluginBean pluginBean= (IPluginBean) beanObject;
                    pluginBean.setUserName("顾林海");
                    Toast.makeText(getApplicationContext(), pluginBean.getUserName(), Toast.LENGTH_LONG).show();

                } catch (Exception e) {

                }
            }
        });
复制代码

加载插件plugin中的UserInfo类,调用setUserName和getUserName方法,点击按钮Toast显示“顾林海”。至此加载外部dex文件中的类就结束了。

838794-506ddad529df4cd4.webp.jpg

搜索微信“顾林海”公众号,按期推送优质文章。

相关文章
相关标签/搜索