Android黑科技动态加载(二)之Android中的ClassLoader
目录
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
效果图
Android 5.0这是在Android 5上面的效果 实现了我们的目标->动态加载.
但是在Android4.1上面会报错. Android插件化框架系列之类加载器文章中指出了原因是因为Android虚拟机加载Class的差异, 把插件应用中的compile
改成provided
方式就可以了, 具体区别看Android Studi添加依赖那些事.
)
文章有不足的地方, 希望大家帮忙纠正.[拜托][拜托][拜托]
更多相关文章
- Android面试基础
- 使用html,javascript,css,phonegap创建开发android应用程序
- android系统定制从听说到入门二
- 在eclipse的android工程里引用android sdk之外的类和方法
- 深度解析Android中字体设置
- Android(安卓)插件安装 和MyEclipse的Windows下面没有Android(安
- 基于Android官方Paging Library的RecyclerView分页加载框架
- Android(安卓)弹出窗口 PopupWindow
- Android(安卓)网格视图(GirdView)简易适配器的使用