目录
Android黑科技动态加载(一)之Java中的ClassLoader
Android黑科技动态加载(二)之Android中的ClassLoader
Android黑科技动态加载(三)之动态加载资源
Android黑科技动态加载(四)之插件化开发
参考
Android插件化框架系列之类加载器
Android动态加载之ClassLoader详解
Java虚拟机与Android虚拟机加载对象的不同
在Java虚拟机中加载的对象主要是字节码, 也就是class
文件. 而Android
虚拟机中主要加载的是dex
文件, dex
文件就是把class
优化整合打包的结果 . 我们知道一个apk
其实就是一个打包的文件
, 运行的时候会释放出dex
包. 当然优化后的odex
是主要加载的对象, 主要优点是比dex
加载地快.
ClassLoader分类
在上一篇博客中, 我们知道Java虚拟机的ClassLoader有三个:
BootStrap ClassLoader
Extension ClassLoader
App ClassLoader
并且ClassLoader加载类的顺序准守双亲委托机制
.
在Android虚拟机中, 也有三个ClassLoader, 并且加载顺序也是准守
双亲委托机制
:
BootClassLoader
: 主要用于加载系统的类,包括java和android系统的类库PathClassLoader
: 主要用于加载应用内中的类, 它加载的路径是固定的, 因此无法指定DexClassLoader
: 可以用于加载任意路径的zip,jar或者apk文件, 可以实现动态加载
应用
好了, 既然我们现在知道DexClassLoader
可以加载任意路径的zip,jar或者apk文件. 那么到底有什么用呢? 回到我们的目的, 我们为什么要学习ClassLoader? 不就是实现运行时动态加载服务器上下载的临时补丁或者插件么?
假设我们现在有一个服务, 用于提供用户信息管理的. 而我们的用户信息是托管在第三方后端云上, 现在由于某些因素, 需要更换后端云. 所以我们也需要重新去实现我们的用户信息管理类. 在不发布新版本的情况下, 我们可以使用上面讲到的技术去实现.
IUserService.java
package top.august.i;
public interface IUserService {
String login(String username, String password);
String logout();
}
该接口定义了用户服务的标准, 我们导出jar包
UserService.jar
UserPlugin
我们新建Android Studio工程
UserPlugin
, 然后引入jar包.
然后我们对IUserService
进行实现
public class UserService implements IUserService {
private String username;
@Override
public String login(String s, String s1) {
if (s.equals(s1)) {
username = s;
return "后端云A:登录成功!";
}
return "后端云A:登录失败...";
}
@Override
public String logout() {
if (TextUtils.isEmpty(username)) {
return "后端云A:未登录...";
}
username = null;
return "后端云A:注销成功!";
}
}
这里我们用户验证就简单地对比一下用户名跟密码是否一样...
然后我们输出APKUserPlugin.apk
HostApp
接下来我们新建Android Studio工程
HostApp
然后把
UserPlugin.apk
放到assets
目录下把
UserService.jar
放到libs下面并且引用
其实这里UserPlugin.apk
可以放到任意路径下. 因为这才是我们服务器的情况, 把插件APK放到任意位置. 这里只是为了方便读写, 所以才放到assets目录下
PluginUtil
然后我们新建工具类
package com.example.hostapp;
import android.content.Context;
import com.example.i.IUserService;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import dalvik.system.DexClassLoader;
/**
* Created by August on 2017/3/18.
*/
public class PluginUtil {
private static final String PLUGIN_NAME = "UserPlugin.apk";
/**
* 加载插件APP的类
*
* @param context
* @return
* @throws IOException
*/
public static IUserService load(Context context) throws IOException {
String src = context.getFilesDir().getAbsolutePath() + File.separator + PLUGIN_NAME; // 插件APK存放的位置
String dest = context.getFilesDir().getAbsolutePath() + File.separator + "plugin" + File.separator; // 插件APK转换为dex的文件夹, 也就是DexClassLoader加载的classPath
// 创建dest目录
File destFile = new File(dest);
if (!destFile.exists()) {
destFile.mkdir();
} else if (!destFile.isDirectory()) {
destFile.delete();
destFile.mkdir();
}
try {
copyPlugin(context, src); // 把插件从assets拷贝出来, 如果是从网络下载下来的 那么就可以从下载文件的路径拷贝过来
DexClassLoader classLoader = new DexClassLoader(src, dest, null, context.getClassLoader()); // 构造自己的DexClassLoader
Class<?> cls = classLoader.loadClass("com.example.userplugin.impl.UserService"); // 加载插件APK中的UserService类
IUserService service = (IUserService) cls.newInstance(); // 返回实例
return service;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 拷贝插件APP到插件目录
*
* @param context
* @param dest
* @throws IOException
*/
private static void copyPlugin(Context context, String dest) throws IOException {
InputStream is = null;
FileOutputStream fos = null;
is = context.getAssets().open(PLUGIN_NAME);
fos = new FileOutputStream(dest);
int len;
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
}
}
MainActivity
package com.example.hostapp;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;
import com.example.i.IUserService;
import java.io.IOException;
public class MainActivity extends AppCompatActivity {
private IUserService mService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void load(View v) {
try {
mService = PluginUtil.load(this);
if (mService != null) {
Toast.makeText(this, "加载插件APP成功", Toast.LENGTH_SHORT).show();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
Toast.makeText(this, "加载插件APP失败", Toast.LENGTH_SHORT).show();
}
public void login(View v) {
if (mService != null) {
Toast.makeText(this, mService.login("August1996", "August1996"), Toast.LENGTH_SHORT).show();
}
}
public void logout(View v) {
if (mService != null) {
Toast.makeText(this, mService.logout(), Toast.LENGTH_SHORT).show();
}
}
}
activity_main
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="load"
android:text="加载" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="login"
android:text="登录" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="logout"
android:text="注销" />
</LinearLayout>
效果图
这是在Android 5上面的效果 实现了我们的目标->动态加载.
但是在Android4.1上面会报错. Android插件化框架系列之类加载器文章中指出了原因是因为Android虚拟机加载Class的差异, 把插件应用中的compile
改成provided
方式就可以了, 具体区别看Android Studi添加依赖那些事.
)
文章有不足的地方, 希望大家帮忙纠正.[拜托][拜托][拜托]