Replugin学习RePlugin

Replugin 全面解析(5)

2017-09-09  本文已影响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函数,这个函数会完成以下任务:

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函数。

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

整个过程分为以下几步:

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,这里要注意:

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的。这里要完成几件事情:

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函数

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

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相关的少数应用上使用,虽然崩溃率很低(万分之三),但还需要接受大面积使用,不同应用场景的应用的检验,在这个过程中发现问题逐步完善!

上一篇 下一篇

猜你喜欢

热点阅读