上一篇看了java的ClassLoader,我们知道,Android虽然是用java开发,但是Android虚拟机可不认识什么.class文件,Android会将所有的.class文件,打包成一个.dex文件后,进行加载。而这被封装在BaseDexClassLoader类里,但是Android通常用到的,都是它的两个子类PathClassLoader和DexClassLoader。
上源码:
package dalvik.system;
import java.io.File;
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
throw new RuntimeException("Stub!");
}
}
我们看到DexClassLoader和PathClassLoader都继承了BaseDexClassLoader。再进一步看看
package dalvik.system;
import java.io.File;
import java.net.URL;
import java.util.Enumeration;
public class BaseDexClassLoader extends ClassLoader {
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
throw new RuntimeException("Stub!");
}
protected Class> findClass(String name) throws ClassNotFoundException {
throw new RuntimeException("Stub!");
}
protected URL findResource(String name) {
throw new RuntimeException("Stub!");
}
protected Enumeration findResources(String name) {
throw new RuntimeException("Stub!");
}
public String findLibrary(String name) {
throw new RuntimeException("Stub!");
}
protected synchronized Package getPackage(String name) {
throw new RuntimeException("Stub!");
}
public String toString() {
throw new RuntimeException("Stub!");
}
}
我们看到BaseDexClassLoader父类是ClassLoader。而这个ClassLoader就是上篇Java里的那个。
接下来我们讲讲,Android里常用的PathClassLoader和DexClassLoader。
PathClassLoader这哥们是干啥的呢,它用来加载系统的apk、还有安装到手机的Apk里的.dex。也就是说,当一个apk,被安装到手机后,它的.dex中的class文件都是被PathClassLoader来加载的,不信,上代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "" + MainActivity.class.getClassLoader());
}
}
运行一下,看结果:
2020-04-15 14:26:19.325 7782-7782/? D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/cn.whm.hot-fNwGGljMJdWzbyP2KOGCfg==/base.apk"],nativeLibraryDirectories=[/data/app/cn.whm.hot-fNwGGljMJdWzbyP2KOGCfg==/lib/arm64, /system/lib64, /product/lib64]]]
DexClassLoader:
相比于PathClassLoader的局限性,DexClassLoader比较强大,它可以从SD卡上加载包含class.dex的.jar和.apk文件,这也是插件化和热修复的基础。在不需要更新apk的情况下,完成需要使用.dex的加载。
使用DexClassLoader来实现热修复
第1步:新建一个Android项目,写一个接口IAction:
package cn.whm.hot.impl;
/**
* Created by juwuguo on 2020-04-14.
*/
public interface IAction {
String doAction();
}
第2步:建一个ActionException类
package cn.whm.hot.impl;
/**
* Created by juwuguo on 2020-04-14.
*/
public class ActionException implements IAction {
@Override
public String doAction() {
return "something wrong here!!!";
}
}
第3步:MainActivity 类
package cn.whm.hot.activity;
import androidx.appcompat.app.AppCompatActivity;
import cn.whm.hot.R;
import cn.whm.hot.impl.IAction;
import cn.whm.hot.impl.ActionException;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button btn_say;
private IAction iAction;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "" + MainActivity.class.getClassLoader());
setContentView(R.layout.activity_main);
btn_say = findViewById(R.id.btn_say);
setListener();
}
private void setListener() {
btn_say.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
iAction = new ActionException();
Log.e(TAG, "say_hotfix.jar not exist");
Toast.makeText(MainActivity.this, iAction.doAction(), Toast.LENGTH_LONG).show();
}
});
}
}
运行后,点击按钮
第4步:新建一个java项目:
记得必须在想同的包名下,新建接口IAction;
package cn.whm.hot.impl;
public interface IAction {
String doAction();
}
第5步:新建一个ActionNormal类,实现接口,模拟新操作
package cn.whm.hot.impl;
public class ActionNormal implements IAction {
public String doAction() {
System.out.println("============"+ActionNormal.class.getClassLoader());
return "Everything is right!!!";
}
}
第六步:切换到java项目src下,执行javac -d . cn/whm/hot/impl/IAction.java cn/whm/hot/impl/ActionNormal.java
在这一步,吃大亏了,墨迹半天,必须在这个src目录下执行javac命令,否则编译出来的class文件,在下一步使用dex命令的时候,就会报错。切记!!!
执行完后,目录下出现IAction.class和ActionNormal.class文件
第7步:在src下,执行jar cvf /Users/hello/Downloads/input.jar cn/whm/hot/impl/IAction.class cn/whm/hot/impl/ActionNormal.class 命令,将两个class文件打包成input.jar包,并保存在Downloads目录下。
第8步:执行dx --dex --output=/Users/hello/Downloads/output.jar /Users/hello/Downloads/input.jar命令,将input.jar包里的.class优化成dex文件。最终这个output.jar就是我们需要的jar包。
第9步:把这个jar包,复制到手机的SD卡目录里。
第10步:修改MainActivity代码:
package cn.whm.hot.activity;
import androidx.appcompat.app.AppCompatActivity;
import cn.whm.hot.R;
import cn.whm.hot.impl.IAction;
import cn.whm.hot.impl.ActionException;
import dalvik.system.DexClassLoader;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Button btn_say;
private IAction iAction;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "" + MainActivity.class.getClassLoader());
setContentView(R.layout.activity_main);
btn_say = findViewById(R.id.btn_say);
setListener();
}
/**
* dx --dex --output=/Users/hello/Desktop/backProject/HotFixProject/output.jar /Users/hello/Desktop/backProject/HotFixProject/src.jar
*/
private void setListener() {
btn_say.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
File jarFile = new File(Environment.getExternalStorageDirectory().getPath() + "/XZLFile/media/output.jar");
if (!jarFile.exists()) {
iAction = new ActionException();
Log.e(TAG, "output.jar not exist");
Toast.makeText(MainActivity.this, iAction.doAction(), Toast.LENGTH_LONG).show();
} else {
//需要有读写权限
DexClassLoader classLoader = new DexClassLoader(jarFile.getAbsolutePath(), getExternalCacheDir().getAbsolutePath(), null, getClassLoader());
try {
Class clazz = classLoader.loadClass("cn.whm.hot.impl.ActionNormal");
iAction = (IAction) clazz.newInstance();
Toast.makeText(MainActivity.this, iAction.doAction(), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
}
判断jar包是否存在,然后使用DexClassLoader加载执行。再次运行,如图:
细心的看客有没有注意到第5步里我打的日志
2020-04-15 15:23:04.162 14279-14279/cn.whm.hot I/System.out: ============dalvik.system.DexClassLoader[DexPathList[[zip file "/storage/emulated/0/XZLFile/media/output
证实它就是通过DexClassLoader加载进来的。
搞定!!!