本平臺的文章更新會有延遲,你們能夠關注微信公衆號-顧林海,包括年末前會更新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命令:
雙擊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:
在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文件中的類就結束了。
搜索微信「顧林海」公衆號,按期推送優質文章。