上一篇看了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加载进来的。

搞定!!!

更多相关文章

  1. NPM 和webpack 的基础使用
  2. 【阿里云镜像】使用阿里巴巴DNS镜像源——DNS配置教程
  3. Activity学习日记(一)
  4. Android源代码下载
  5. Android(安卓)文件的上传
  6. Android的电话功能介绍
  7. Android之使用XMLPull解析xml(二)
  8. android 考研appwidget的实现
  9. Mac电脑开发Android(安卓)app如何连接真机

随机推荐

  1. 安卓模拟器Android(安卓)SDK Manager 无
  2. Android(安卓)Gradle Plugin版本和Gradle
  3. android之应用程序启动第三方应用
  4. Android基于Dialog实现加载框
  5. android 升级webview的方法
  6. Android(安卓)系统提供媒体库 URI 与 数
  7. 史上最全的常用开发工具类收集(持续更新中
  8. Android(安卓)中文api (81)——InputMeth
  9. android离线下载的相关知识
  10. Android使用criteria选择合适的地理位置