前面 学习了Java和Android里的类加载机制,了解了一些系统的ClassLoader,那么回到最初的起点,关于动态加载的使用。这方面的内容也有很多,暂且分为三部分,先做一个简单的demo体会动态加载,然后是动态加载资源,最后是动态加载activity。这篇是第一部分。
1、编写接口类
把要实现的方法抽象成公共接口,处理之后放在项目里方便调用。 这里只做一个简单的输入字符串功能。
package com.fish.test2;
public interface User {
String SayHello();
}
2、另写一个类实现接口。
package com.fish.test3;
import com.fish.test2.User;
public class UserImpl implements User{
@Override
public String SayHello() {
// TODO Auto-generated method stub
return "Hello, Test for demo.";
}
}
3、将接口类和实现方法的类分别打成jar包,把后者用AndroidSDK自带的dx工具优化一下。一般dx.bat在sdk目录下的/platform-tools里,但是我这里没找到,就去/build-tools下任意一个版本的目录里都可以找到。
把UserImpl.jar拷贝到这个目录下,执行命令
dx --dex --output=UserImpl_tmp.jar UserImpl.jar
其实是把java代码优化成dex文件,而至于jar这个格式.
JAR 文件格式以流行的 ZIP 文件格式为基础。与 ZIP 文件不同的是,JAR 文件不仅用于压缩和发布,而且还用于部署和封装库、组件和插件程序,并可被像编译器和 JVM 这样的工具直接使用。
无论加载jar还是apk,和加载dex是差不多的,因为前面类加载机制里我们看到这几种格式都能加载,会从jar或apk中解压提取出dex出来。所以这里我们姑且就处理成jar包,作为所谓的插件在项目里动态加载。
4、创建Android工程,包名要和上面的包名相同不然会报错。把接口类的jar包放到libs下,把优化后的test.jar放到手机的sdcard下。
adb push test.jar /sdcard
然后在AndroidManifest.xml里添加读取外存权限。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
5、编写MainActivity。添加个点击按钮和点击事件。
package com.fish.test2;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import dalvik.system.DexClassLoader;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button)findViewById(R.id.button1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//getDir("dex1", 0)会在/data/data/**package/
//创建一个名叫”app_dex1“的文件夹
File dexOutputDir = getDir("dex1",0);
//在sdcard目录下的test.jar的路径
String path = Environment.getExternalStorageDirectory().toString()+File.separator+"test.jar";
//第一个参数是待加载的dex文件目录
//也就是test.jar的目录
//第二个参数是解压优化后的dex文件目录
//该位置要可读写且仅该应用可读写(安全性考虑)
//因此放在/data/dat/下
//第三个参数没用到so库,设置null
//第四个参数是父类加载器
DexClassLoader cl = new DexClassLoader(path, dexOutputDir.getAbsolutePath(), null, getClassLoader());
Class libProviderClass = null;
try{
//要写完整的类名
libProviderClass = cl.loadClass("com.fish.test2.UserImpl");
//实例化接口类的对象
User lib = (User)libProviderClass.newInstance();
//调用里面SayHello()方法输出字符串
Toast.makeText(MainActivity.this, lib.SayHello(), Toast.LENGTH_LONG).show();
}catch (Exception e){
e.printStackTrace();
}
}
});
}
}
到这里步骤就结束了,编译运行即可。但是过程中出现了不少问题,单独记录。
Error:Error converting bytecode to dex: Cause: Dex cannot parse version 52 byte code. This is caused by library dependencies that have been compiled using Java 8 or above. If you are using the ‘java’ gradle plugin in a library submodule add targetCompatibility = ‘1.7’ sourceCompatibility = ‘1.7’ to that submodule’s build.gradle file.
无法解析JDK1.8,在app/build.gradle的dependencies里的最开始添加
apply plugin: 'java'
sourceCompatibility = 1.7
targetCompatibility = 1.7
但是出现新的错误
Error:The ‘java’ plugin has been applied, but it is not compatible with the Android plugins. 删除apply plugin: ‘java’后报上面第一个错
后来得知as2.3.3默认是JDK1.7,所以配置一下让它支持1.8
配置流程,红框为添加内容
编译通过,带有一个warning,且button无效。
无效的原因是换了个模拟器,忘了把test.jar放进sdcard,所以找不到插件类。
然后再测试
成功调用了插件类里的SayHello()方法,输出了指定的字符串。