探究Android之ClassLoader

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

搞定!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值