;
JarLoader.java
然后打包为Jar文件
这里笔者导出为Loader.jar文件。有一点需要注意,就是不要把ILoader.jar接口打包进去,因为后期可能会包错: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation ,出现这个错误的原因就是打包的Jar中有接口,然后在下面的文件中又定义了接口,出现了两个接口文件,所以报错。如果这里把ILoader接口打包进去,那么在下面的测试中就不要再定义相同的ILoader接口了。
到这里我们就把Jar文件打包成功了,接下来了需要把这个Jar文件用dx工具进行处理,dx工具在Android SDK 的tools中已经提供了,一般在android-SDK/build-tools目录下。
将上面的Loader.jar文件拷贝一份到dx同级的目录下,然后执行如下命令:
dx --dex --output=Loader_dex.jar Loader.jar
然后将生成的Loader_dex.jar文件,拷贝到手机的SD根目录下面(手机SD的根目录就是:/storage/emulated/0,读者也可以使用 Environment.getExternalStorageDirectory() 查看)
接下来就可以使用如下的代码进行加载:
其中ILoader.java接口与Loader.jar中的ILoader接口保持一直。
package com.example.test;
import java.io.File;
import com.example.interf.ILoader;
import dalvik.system.DexClassLoader;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.util.Log;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadJar();
* @Title LoadJar
* @Description 项目工程中必须定义接口(包名都要一致), 而被引入的第三方jar包实现这些接口,然后进行动态加载 。
* 相当于第三方按照接口协议来开发, 使得第三方应用可以以插件的形式动态加载到应用平台中。
* @return void
private void loadJar(){
File dexoutputdir = getDir("dex1",0);
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Loader_dex.jar";
DexClassLoader loader = new DexClassLoader(dexPath,dexoutputdir.getAbsolutePath(),null,getClassLoader());
try {
Class clz = loader.loadClass("com.example.interf.JarLoader");
ILoader iShowToast = (ILoader) clz.newInstance();
Toast.makeText(this,iShowToast.sayHi(),Toast.LENGTH_LONG).show();
} catch (Exception e){
Log.d("dd",e.toString());
MainActivity.java
在这个类中定义的核心方法是loadJar()
private void loadJar(){
File dexoutputdir = getDir("dex1",0);
String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Loader_dex.jar";
DexClassLoader loader = new DexClassLoader(dexPath,dexoutputdir.getAbsolutePath(),null,getClassLoader());
try {
Class clz = loader.loadClass("com.example.interf.JarLoader");
ILoader iShowToast = (ILoader) clz.newInstance();
Toast.makeText(this,iShowToast.sayHi(),Toast.LENGTH_LONG).show();
} catch (Exception e){
Log.d("dd",e.toString());
接下来笔者解释一下上面这个方法中核心类DexClassLoader。
此处需要注意DexClassLoader的四个参数:
参数1 dexPath:待加载的dex文件路径,如果是外存路径,一定要加上读外存文件的权限( <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ),否则会报与报错,Android4.4 KitKat及以后的版本需要此权限,之前的版本不需要该权限。
参数2 optimizedDirectory:解压后的dex存放位置,此位置一定要是可读写且仅该应用可读写(安全性考虑),所以只能放在data/data下。本文getDir(“dex1”, 0)会在/data/data/**package/下创建一个名叫”app_dex1“的文件夹,其内存放的文件是自动生成Loader_dex.dex;需要注意,data/data文件夹只有在手机root之后,才看得到。
参数3 libraryPath:指向包含本地库(so)的文件夹路径,可以设为null。
参数4 parent:父级类加载器,一般可以通过Context.getClassLoader获取到,也可以通过ClassLoader.getSystemClassLoader()取到。
到这里动态加载Jar就结束了。笔者接下来总结一下思路,首先把jar文件经过dx工具处理,然后把处理后的文件放到手机的SD根目录下面,然后利用反射加载调用方法。上面其实只是实现热更新的一半,加载的Jar文件完全可以从服务器下载手机后,然后再在手机端加载,这样可以对手机上的APP进行实时的更新以及防止反编译。
2.如何加载未安装的APK
上面介绍了如何动态加载jar文件,接下来介绍如何加载未安装的APK。
首先新建一个Android项目:
定义一个接口ISayHello.java
package com.example.loaduninstallapkdemo;
public interface ISayHello {
public String sayHello();
ISayHello.java
然后新建Activity,实现ISayHello接口:
package com.example.loaduninstallapkdemo;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity implements ISayHello{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
@Override
public String sayHello() {
return "Hello, this apk is not installed";
MainActivity.java
然后把该工程的APK拷贝到手机的SD根目录下面,
接下来就可以使用如下的代码进行动态加载了,下面的加载过程和上面的类似,只是不再需要定义接口了。
package com.example.test;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.view.Menu;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadUnInstallAPK();
private void loadUnInstallAPK(){
String path = Environment.getExternalStorageDirectory() + File.separator;
String filename = "UninstallApkActivity.apk";
// 4.1以后不能够将optimizedDirectory设置到sd卡目录, 否则抛出异常.
File optimizedDirectoryFile = getDir("dex", 0) ;
DexClassLoader classLoader = new DexClassLoader(path + filename, optimizedDirectoryFile.getAbsolutePath(),
null, getClassLoader());
try {
// 通过反射机制调用, 包名为com.example.loaduninstallapkdemo, 类名为MainActivity
Class mLoadClass = classLoader.loadClass("com.example.loaduninstallapkdemo.MainActivity");
Constructor constructor = mLoadClass.getConstructor(new Class[] {});
Object testActivity = constructor.newInstance(new Object[] {});
// 获取sayHello方法
Method helloMethod = mLoadClass.getMethod("sayHello", null);
helloMethod.setAccessible(true);
Object content = helloMethod.invoke(testActivity, null);
Toast.makeText(MainActivity.this, content.toString(), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
MainActivity.java
3.如何加载已经安装的APK
在介绍了如何动态加载jar,加载未安装的APK后,接下来介绍如何加载已经安装的APK,
首先将制作一个简单的APK,然后把它安装的手机上面。
package com.example.installapkdemo;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainActivity.java
将该APK安装到手机后,接下来就可以进行加载了。
同样和加载未安装的APK类似,项目中也不需要定义接口。
package com.example.test;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.util.Log;
import android.view.Menu;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadInstalledApk();
private void loadInstalledApk(){
try {
String pkgName = "com.example.installapkdemo";
Context context = createPackageContext(pkgName,
Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE) ;
// 获取动态加载得到的资源
Resources resources = context.getResources() ;
// 获取该apk中的字符串资源"hello_world", 并且toast出来,apk换肤的实现就是这种原理
String toast = resources.getString(resources.getIdentifier("hello_world", "string", pkgName) ) ;
Toast.makeText(MainActivity.this, toast, Toast.LENGTH_SHORT).show() ;
Class cls = context.getClassLoader().loadClass(pkgName + ".MainActivity");
// 跳转到该Activity
startActivity(new Intent(context, cls)) ;
} catch (NameNotFoundException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
Log.d("", e.toString()) ;
MainActivity.java
原文链接:
Android动态加载jar,apk的实现