美文网首页Replugin学习RePlugin
Replugin 全面解析(5)

Replugin 全面解析(5)

作者: 蒋扬海 | 来源:发表于2017-09-09 22:30 被阅读0次

本篇我们来看看四大组件中的BroadcaseReceiverContentProvider。总体来说,这两个组件的生命周期相对简单,所以要在Replugin框架中处理插件的BroadcaseReceiverContentProvider更简单容易一些,框架中的代码逻辑也很好理解。

BroadcaseReceiver

广播我们分两步来讲解:

  • 注册广播
  • 接收广播

Replugin中广播的整体逻辑非常清楚简单,基本上可以用下面这张图来概括。

broadcastreceiver.jpg

注册广播

如果你看过Replugin 全面解析 (3) 你应该会记得在插件加载的过程中,有一个步骤就是解析Plugin中的BroadcastReceiver并注册,我们就从这里开始讲起。当然,在Replugin中通过代码注册广播跟原生并没有什么两样,不同的只是插件中在Manifest文件中静态注册的广播的注册方式有所不同。所以这里我们所讲的就是插件中静态广播的注册流程啦!

Loader.regReceivers先得到插件的名字,调用IPluginHost接口的regReceiver函数,将加载插件Dex时解析出来的广播信息通过远程调用注册到Persistent进程中。

private void regReceivers() throws android.os.RemoteException {
    String plugin = mPluginObj.mInfo.getName();

    if (mPluginHost == null) {
        mPluginHost = getPluginHost();
    }

    if (mPluginHost != null) { // 第一个参数是插件名,第二个参数是广播信息
        mPluginHost.regReceiver(plugin, ManifestParser.INS.getReceiverFilterMap(plugin));
    }
}

远程调用实际是调用Persistent进程中的PmHostSvc.regReceiver函数,这个函数会完成以下任务:

  • 创建PluginReceiverProxy对象,给它添加一个保存Action的Map对象mActionPluginComponents
  • 遍历插件中的广播,并将广播以及广播的IntentFilter信息都保存到mActionPluginComponents
  • PluginReceiverProxy实际上就是一个广播,所以将它注册到Android系统中,并将插件中所有广播的所有IntentFilter都添加到这个广播中。因此当系统发送广播时,所有匹配这些IntentFilter的广播都会首先被PluginReceiverProxy接收到。
public void regReceiver(String plugin, Map rcvFilMap) throws RemoteException {
    ......
    HashMap<String, List<IntentFilter>> receiverFilterMap = (HashMap<String, List<IntentFilter>>) rcvFilMap;
    // 遍历此插件中所有静态声明的 Receiver
    for (HashMap.Entry<String, List<IntentFilter>> entry : receiverFilterMap.entrySet()) {
        if (mReceiverProxy == null) {
            mReceiverProxy = new PluginReceiverProxy();
            mReceiverProxy.setActionPluginMap(mActionPluginComponents);
        }
        String receiver = entry.getKey(); 
        List<IntentFilter> filters = entry.getValue();
        if (filters != null) {
            for (IntentFilter filter : filters) {
                int actionCount = filter.countActions();
                while (actionCount >= 1) {
                    saveAction(filter.getAction(actionCount - 1), plugin, receiver);
                    actionCount--;
                }
                mContext.registerReceiver(mReceiverProxy, filter);//注册PluginReceiverProxy
            }
        }
    }
}

注册广播的过程就这是这么简单!

接收广播

接收广播正如上面所提到的,首先是PluginReceiverProxy.onReceive来处理。

这里会选择使用IPluginHost或者IPluginClient来进一步处理广播。在Replugin 全面解析 (4) 中有讲过这两者的区别。所以如果广播并不是Persistent进程中的,就会使用IPluginClient.onReceive来处理。

public void onReceive(Context context, Intent intent) {
    ......
    String action = intent.getAction();
    if (!TextUtils.isEmpty(action)) {
        ......
        List<String> receivers = new ArrayList<>(entry.getValue());
        for (String receiver : receivers) {
            try {
                ......
                if (process == IPluginManager.PROCESS_PERSIST) {
                    IPluginHost host = PluginProcessMain.getPluginHost();
                    host.onReceive(plugin, receiver, intent); // Persistent进程
                } else {
                    IPluginClient client = MP.startPluginProcess(plugin, process, new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST));
                    client.onReceive(plugin, receiver, intent); // 非Persistent进程
                }
            } catch (Throwable e) {
            }
        }
    }
}


上一步通过远程调用会调用到PluginProcessPer.onReceive,这一步实际上就是调用PluginReceiverHelper.onReceive函数。

  • 查找出插件的Context对象,也就是PluginContext对象
  • 通过PluginDexClassLoader加载BroadcastReceiver类并创建实例对象
  • UI线程调用BroadcastReceiver对象的onReceive函数,这样就完成了广播的处理
public static void onPluginReceiverReceived(final String plugin, final String receiverName, final HashMap<String, BroadcastReceiver> receivers, final Intent intent) {
    ......
    // 使用插件的 Context 对象
    final Context pContext = Factory.queryPluginContext(plugin);
    ......
    String key = String.format("%s-%s", plugin, receiverName);
    BroadcastReceiver receiver = null;
    if (receivers == null || !receivers.containsKey(key)) {
        try {
            // 使用插件的 ClassLoader 加载 BroadcastReceiver
            Class c = loadClassSafety(pContext.getClassLoader(), receiverName);
            if (c != null) {
                receiver = (BroadcastReceiver) c.newInstance();
                if (receivers != null) {
                    receivers.put(key, receiver);
                }
            }
        } catch (Throwable e) {
        }
    } else {
        receiver = receivers.get(key);
    }
    if (receiver != null) {
        final BroadcastReceiver finalReceiver = receiver;
        // 转到 ui 线程
        Tasks.post2UI(new Runnable() {
            @Override
            public void run() {
                finalReceiver.onReceive(pContext, intent);
            }
        });
    }
}

唯一要在最后说明的一点是,由于广播的特殊性,不需要任何额外的设计,就可以完全支持原生广播的所有特性。

  • Host可以向Plugin发送广播,Plugin也可以向Host或者其他Pluglin发送广播。
  • 外部应用可以向Host或者Plugin发送广播,Plugin也可以向外部应用发送广播。
  • 插件内动态注册广播跟原生应用做法也是一样的,并且支持所有原生广播特性。

广播相关的内容就讲解这么多,是不是很简单明了?

ContentProvider

ContentProvider的设计理念跟BroadcastReceiver非常相似,对插件中ContentProvider的访问是通过坑位ContentProvider来代理的,然后坑位ContentProvider 会根据uri去访问插件中的目标ContentProvider以完成数据的CRUD(create, remove, query, delete)操作。先通过一张图来做一个大概了解:

contentprovider.jpg

整个过程分为以下几步:

  • 插件中通过反射调用PluginProviderClient的CRUD操作函数,以uri作为参数
  • 将目标uri组装成一个newUrinewUri指向目标provider所在进程的一个坑位provider
  • 坑位privider接收到操作请求以后从newUri中解析出目标uri
  • 通过ClassLoader加载并创建目标provider的实例,并显示调用对应的CRUD操作

Replugin 全面解析(3) 中,我们在Plugin的环境初始化过程中提到过PluginProviderClient,在replugin-host-lib中也有一个同名的类。在Plugin中需要访问provider的时候,就会调用replugin-pugin-lib中的PluginProviderClient类,它会通过反射调用replugin-host-lib中的PluginProviderClient的方法,我们以insert操作为例。

replugin-pugin-lib中的PluginProviderClient.insert,如果RePluginFramework没有初始化,说明当前的插件是被当作普通应用在使用,所以直接使用原生的provider操作。

public static Uri insert(Context c, Uri uri, ContentValues values) {
    if (!RePluginFramework.mHostInitialized) {
        return c.getContentResolver().insert(uri, values);  // 原生操作
    }

    try { // 反射调用replugin-host-lib中的PluginProviderClient.insert
        return (Uri) ProxyRePluginProviderClientVar.insert.call(null, c, uri, values);
    } catch (Exception e) {
    }
    return null;
}

replugin-host-lib中的PluginProviderClient.insert,这里最重要的一步就是toCalledUri

public static Cursor query(Context c, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
    Uri turi = toCalledUri(c, uri);
    return c.getContentResolver().query(turi, projection, selection, selectionArgs, sortOrder, cancellationSignal);
}

PluginProviderClient.toCalledUri负责将上面的uri参数组装成一个新的newUri,这里要注意:

  • 如果通过ContextUri找不到插件,说明并不是要访问插件中的provider,所以就不需要组装newUri,直接通过系统逻辑访问即可。
  • 如果发现要访问的provider是插件中,则去组装newUri
public static Uri toCalledUri(Context c, Uri uri) {
    String pn = fetchPluginByContext(c, uri);
    if (pn == null) {
        return uri;
    }
    return toCalledUri(c, pn, uri, IPluginManager.PROCESS_AUTO);
}

组装newUri,目的就是将原来的uri于坑位provider的uri组合在一起,成为一个新的uri,但实际上这个uri是用来访问坑位privider的。这里要完成几件事情:

  • 通过Uri中插件名和authority找到想要访问的provider所在的进程
  • 找到对应进程中的坑位providerauthority
  • 将坑位providerauthority与插件providerauthority组合在一起形成newUri,组合规则请看下面代码中的注释部分
public static Uri toCalledUri(Context context, String plugin, Uri uri, int process) {
    ......
    // content://com.qihoo360.mobilesafe.PluginUIP
    if (process == IPluginManager.PROCESS_AUTO) {
        process = getProcessByAuthority(plugin, uri.getAuthority());
        if (process == PROCESS_UNKNOWN) {
            return uri;
        }
    }

    String au;
    if (process == IPluginManager.PROCESS_PERSIST) {
        au = PluginPitProviderPersist.AUTHORITY;
    } else if (PluginProcessHost.isCustomPluginProcess(process)) {
        au = PluginProcessHost.PROCESS_AUTHORITY_MAP.get(process);
    } else {
        au = PluginPitProviderUI.AUTHORITY;
    }
// 插件名plugin_name
// 插件provider的uri => content://com.qihoo360.contacts.abc/people?id=9
// 坑位provider看到uri => content://com.qihoo360.mobilesafe.Plugin.NP.UIP
// 组合后的newUri => content://com.qihoo360.mobilesafe.Plugin.NP.UIP/plugin_name/com.qihoo360.contacts.abc/people?id=9
    String newUri = String.format("content://%s/%s/%s", au, plugin, uri.toString().replace("content://", ""));
    return Uri.parse(newUri);
}

在UI进程,persistent进程以及Replugin默认提供的三个自定义进程中都各自有一个坑位provider,他们的authority是各不相同的,但他们都是PluginPitProviderBase的子类。这几个provider是:

  • PluginPitProviderUI
  • PluginPitProviderPersist
  • PluginPitProviderP0
  • PluginPitProviderP1
  • PluginPitProviderP2

通过newUri就可以访问坑位provider了,insert操作自然是providerinsert函数来处理,而他们都是PluginPitProviderBase的子类,而且没有提供自己的insert函数,所以自然就会使用PluginPitProviderBase提供的insert函数

  • 第一步从参数uri中解析出目标provider的uri,就是从上面的newUri中将插件名后面的部分取出来
  • PluginProviderHelper.getProvider加载目标provider类名并通过反射创建一个实例
  • 使用这个目标provider实例真正执行insert操作,实现最终的数据插入操作

这三个小步骤的代码都很容易理解,有兴趣可以看看源码哦~

public Uri insert(Uri uri, ContentValues values) {
    PluginProviderHelper.PluginUri pu = mHelper.toPluginUri(uri);
    if (pu == null) {
        return null;
    }
    ContentProvider cp = mHelper.getProvider(pu);
    if (cp == null) {
        return null;
    }
    return cp.insert(pu.transferredUri, values);
}

好了,Replugin中ContentProvider的原理就是这样,也很好理解!

注意:

  • 外部应用不能访问插件中的provider
  • 不过插件中是可以访问外部应用的provider
  • Host要访问插件中的provider需要使用PluginProviderClient提供的方法
  • 当然,插件之间是可以相互访问对方的provider

总结

通过这四篇分析,Replugin的大部分核心原理已经都分析过了,当然框架中有很多细节或者小的设计并没有能够全部覆盖到,在需要的时候可以看看代码。在Replugin中还有两个gradle插件,已经有人写过两篇比较详细的分析:

Replugin整体的设计很巧妙,支持的原生特性也很多,当然在目前的阶段坑定也还有一些不能支持的特性,希望在以后的版本中能够逐步得到支持!目前只是在360相关的少数应用上使用,虽然崩溃率很低(万分之三),但还需要接受大面积使用,不同应用场景的应用的检验,在这个过程中发现问题逐步完善!

相关文章

  • Replugin 全面解析(5)

    本篇我们来看看四大组件中的BroadcaseReceiver和ContentProvider。总体来说,这两个组件...

  • Replugin 全面解析(3)

    上一篇分析中我们分析了Replugin框架Host端的一些核心概念,还梳理了Activity启动的流程,但是有两个...

  • Replugin 全面解析 (2)

    Activity作为四大组件中最重要的组件,在Replugin中对它的支持的架构设计也是最复杂的,所以本篇分析我们...

  • Replugin 全面解析(1)

    前言 Replugin 已经开源一个月了,最近几天终于抽出时间来研究研究,这里将我的一些心得体会写下来,分享给大家...

  • Replugin 全面解析 (4)

    在前两篇分析的基础上,这篇我们来看看Replugin是如何支持Service组件的。 本篇会包含以下内容: Ser...

  • RePlugin 插件化框架介绍与使用说明

    RePlugin GitHub 主页RePlugin Wiki 主页RePlugin 原理剖析全面插件化:RePl...

  • Replugin源码解析之replugin-plugin-gra

    概述 该部分基础知识在Gradle学习-----Gradle自定义插件及Replugin源码解析之replugin...

  • RePlugin使用总结

    Replugin是什么?由360推出的(完整的?稳定的?适合全面使用的?)插件优化方案RePlugin项目地址 1...

  • 全面插件化时代RePlugin来临

    一、RePlugin简介 RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案。我们“逐>词”拆...

  • 360 RePlugin插件化-项目接入

    RePlugin是一套完整的、稳定的、适合全面使用的,占坑类插件化方案,由360手机卫士的RePlugin Tea...

网友评论

    本文标题:Replugin 全面解析(5)

    本文链接:https://www.haomeiwen.com/subject/jhgajxtx.html