插件化架构深入剖析<二>-----插庄式实现Service&动态广播启动机制剖析

基础巩固---DexClassLoader是如何加载插件中的类?

在上次https://www.cnblogs.com/webor2006/p/12267707.html插件化的学习中已经实现了Activity的跳转了,在继续开启新的学习之前,先来对这块代码的原理进行一下剖析:

其实关于Android中是如何加载类的在当时https://www.cnblogs.com/webor2006/p/10708754.html手写热修复tinker中已经详细阐述过了,但是时隔这么久了有些细节也忘了,另外还有一些新的细节可以挖掘的,所以这里再重新对其进行梳理一下,温故知新:

先来看一下Android类加载器的类结构:

其中对于我们平常接触最多的就是标红的两个,而对于这次插件化而言咱们是用的DexClassLoader来进行插件类的加载的,如下:

好,接下来从这个构造分析起,点进去发现看不到源码:

由于我们目前是用的9.0的API,而目前没有下9.0关于dalvik的源码,目前只有8.0的,而我本地vpn此时又挂了,改用在线http://androidxref.com/9.0.0_r3/的方式吧体验又比较差,所以有一个笨办法,将相关要读的源码上这个在线网站上查找,然后再手动将其下载下来并放到本地sdk的源码中既可,比如:

然后手动再将相关的源码下到本地,不过这种下载比较麻烦,得一个个进去才行,比如:

不过有个小技巧就是先记住这个下载地址,比如这个类的下载地址为:http://androidxref.com/9.0.0_r3/raw/libcore/dalvik/src/main/java/dalvik/annotation/AnnotationDefault.java

那很显然我们要下载其它类的话,直接只要改路径既可,这样就比一个个要进到java源码中再下还是要稍稍快些,具体就不多说了,纯体力活,当然如果有vpn的话这一些烦锁的操作就省啦,当然可以多学一种看源码的方式有益无害~~

下载完之后,我们再手动将下下来的源码放到SDK中的源码目录中既可,如下:

由于目录工程的tartgetSdk用的是10.0,所以将API改为28:

好,此时就可以在Android Studio正常的读源码啦,先来看DexClassLoader的构造:

调用父类的构造了:

好,此时再看一下DexPathList构造做了啥?

其中dexElements是不是相当的熟,对于像tinker这样热修复的实现主要是靠这个东东:

好,来看一下makeDexElements做了啥? 

其实就是遍历apk中的dex文件,每个dex都会封装成一个Element对象,然后此时再看一下loadDexFile()的细节:

此时又创建了一个DexFile对象,看一下它构建的细节:

然后此时打开DexFile文件了:

最终是由NDK底层来打开Dex文件的,当loadDexFile()之后,则会将其放到Element的集合中:

用图总结一下这句话的背后流程:

 

接下来我们在代理类中会用这个ClassLoader进行类的查找,如下:

其中getClassLoader()是重写过了的,返回的就是我们自己创建的DexClassLoader,如下:

接下来则来看一下整个查找类的过程,这样整个原理就清楚了:

此时它的流程在当时手写tinker博文中已经详细阐述过了,双亲委派机制就展现得淋漓尽致,最终会调用它子类的,如下:

此时跳到子类来看一下查找的细节:

看到它是不是就非常之熟了,它里面肯定就是遍历在初始化Dex的Elements数组中进行类的查找嘛,跟进去再确定一下:

最终查看类还是通过ndk底层来查的,最后跟进去搂一眼:

至此~~关于整个插件中类的查找流程就分析完了,有之前热修复的基础的话其实还是不难的。

插庄式实现Service的调用:

在之前已经实现在插件中Activity的调用了,四大组件中还有一个比较重要的是Service,那它又是如何来调用呢?由于木有上下文,所以肯定还得依赖于插桩的Service,其整体实现思路跟Activity其实差不多,下面来实现一下:

先来在公共模块处定义一个抽象接口:

package com.android.pluginstand;

import android.app.Service;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.IBinder;

public interface InterfaceService {
    void onCreate();

    void onStart(Intent intent, int startId);

    int onStartCommand(Intent intent, int flags, int startId);

    void onDestroy();

    void onConfigurationChanged(Configuration newConfig);

    void onLowMemory();

    void onTrimMemory(int level);

    IBinder onBind(Intent intent);

    boolean onUnbind(Intent intent);

    void onRebind(Intent intent);

    void onTaskRemoved(Intent rootIntent);

    void attach(Service proxyService);
}

在插件那块也得封装一个BaseService,来将所有里面的Service进行统一,如下:

然后里面新建一个具体的子类:

注意:此时也不需要在清单文件中对这个服务进行注册的,好,接下来再回到宿主搞一个插桩服务,其实现逻辑基本上代理的Activity类似,直接贴出代码了:

package com.android.pluginarchstudy;

import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;

import com.android.pluginstand.InterfaceService;

import java.lang.reflect.Constructor;

public class ProxyService extends Service {
    private String serviceName;
    private InterfaceService interfaceService;

    @Override
    public IBinder onBind(Intent intent) {
        init(intent);
        return null;
    }

    private void init(Intent intent) {
        serviceName = intent.getStringExtra("serviceName");

        //加载service 类
        try {
            //插件oneService
            Class<?> aClass = getClassLoader().loadClass(serviceName);
            Constructor constructor = aClass.getConstructor(new Class[]{});
            Object in = constructor.newInstance(new Object[]{});

            interfaceService = (InterfaceService) in;
            interfaceService.attach(this);

            Bundle bundle = new Bundle();
            bundle.putInt("from", 1);
            interfaceService.onCreate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance().getDexClassLoader();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (interfaceService == null) {
            init(intent);
        }
        return interfaceService.onStartCommand(intent, flags, startId);
    }

    @Override
    public boolean onUnbind(Intent intent) {
        interfaceService.onUnbind(intent);
        return super.onUnbind(intent);
    }
}

然后在清单中注册一下:

好,接下来应用一下,咱们在插件跳转到第二个界面中来开启咱们的服务:

 

很显然此时的startService需要重写一下,因为上下文的问题:

然后此时还需要重写个东东:

最终得在代理Activity中重写一下startService才行,因为得转到咱们的ProxyService才行的,如下:

 

ok,一切就续,验证一下看是否正常,先来build一个插件更新到sdcard中,然后再运行宿主app,运行如下:

完美实现~~

插庄式实现动态广播的调用:

对于Android的广播注册都知道分为静态和动态注册,这里先来实现一下动态广播,因为静态广播的要复杂一些,待下一次再来仔细学习,这里先把动态广播的调用给实现了,其实现思路基本雷同,先来定义公共方法:

然后再在插件中定义一个基类的广播:

此时也不需要往清单中进行广播的注册,但是如果是静态广播肯定还是得注册的,不过这个之后再说,目前焦点是来调用动态广播既可,接下来则来注册一下这个广播,还是在插件中点击跳转到第二个界面时进行注册,如下:

此时同样得重写这个registerReceiver,不多解释了:

好,接下来则需要回到宿主来写一个插桩的Receiver,逻辑类似,直接贴出代码,如下:

然后在清单中注册一下:

最后还得到我们的代理Activity中来重写一下registerReceiver方法,因为我们在插件中这样调用了:

所以:

最后咱们来发送应用一下,咱们还是到插件的主界面中增加一个按钮进行发送测试:

此时又因为上下文的问题得重写一个sendBroadcast才行:

好,代码一切就绪,接下来重新更新一个插件,并运行宿主看一下:

妥妥的,不过这里还有个待优化的代码,就是注册广播了还没有对它做注销处理,比较简单下面来写一下:

再重写一下宿主的unregisterReceiver方法:

ok,还有另一种静态广播的调用下一次再学~~

posted on 2020-02-06 15:00  cexo  阅读(356)  评论(0编辑  收藏  举报

导航