Android与WebView的插件管理机制

行是知之始,知是行之成。这篇文章主要讲述Android与WebView的插件管理机制相关的知识,希望能为你提供帮助。
上一篇文章说到,当利用WebViewClient或者WebChromeClient来处理由html页面传过来的请求的时候,都会将相应的服务名称,操作方法和相应的參数数据传给一个叫PluginManager的类。

PluginManager类的作用是什么?
大家知道,当利用android原生环境的功能。比方照像机。比方相冊等,这些功能都是非常分散的,说不清楚什么时候是须要这些功能,什么时候是不须要这些功能的,所以我们希望可以像插件一样。须要的时候就载入进来,不须要的时候不去理他,而PluginManager类就是一个这种管理类。

它主要负责几件事情:
1)进入HTML页面的时候,去载入我们定义好的控件。

mPluginManager = new PluginManager(this); mPluginManager.loadPlugin();


那么PluginManager怎么知道本个应用要载入多少plugin来去响应由Html页面来的请求呢?
我们是通过一个叫plugin.xml配置文件来定义的。


< plugins> < plugin name=" App" class=" com.lms.xxx.bridge.plugin.App" /> < plugin name=" Toast" class=" com.lms.xxx.plugin.Toast" /> < plugin name=" Dialog" class=" com.lms.xxx.bridge.plugin.Dialog" /> < plugin name=" User" class=" com.lms.xxx.bridge.plugin.User" /> < /plugins>




比方在上面的配置文件里,我们会载入App, Toast, Dialog 和 User 这几个plugin。能够联想到,Toast和Dialog都是Android原生环境下的显示窗体,我们尽管用html页面来实现界面,可是为了保持整个应用的一致性,我们就会用到原生环境中的Toast或者我们自己定义的对话框等控件。
须要用到什么,就在这里定义什么。

我们再来看一下loadPlugin方法:

public void loadPlugin() { int identifier = context.getResources().getIdentifier(" plugins" , " xml" , context.getPackageName()); if (identifier == 0) { pluginConfigurationMissing(); }XmlResourceParser xml = context.getResources().getXml(identifier); try {int eventType = -1; while ((eventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { if (eventType == XmlResourceParser.START_TAG) { String name = xml.getName(); if (" plugin" .equals(name)) { String pluginName = xml.getAttributeValue(null, " name" ); String className = xml.getAttributeValue(null, " class" ); configs.put(pluginName, className); }} }} catch (XmlPullParserException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }


能够看到,这就是解析plugins.xml文件。然后将相应的插件类名给放到configs中,configs定义例如以下:
【Android与WebView的插件管理机制】
private HashMap< String, String> configs = new HashMap< String, String> (); private HashMap< String, IPlugin> plugins = new HashMap< String, IPlugin> ();


通过loadPlugin方法,我们就将在plugins.xml中定义好的插件,给载入到configs中去了。configs里存放的仅仅是类名。而plugins存放的才是实现,只是我们这里不须要关心这个。
在这里。在plugins.xml文件里定义的name属性就是这个服务名称。
2)依据请求的服务名称和操作方法等,为这个请求找到相应的Plugin去处理。

String execResult = mPluginManager.exec(" service" , " action" , args);


看一下exec方法,

public String exec(String service, String action, JSONObject args) throws PluginNotFoundException { IPlugin plugin = getPlugin(service); ... PluginResult result = plugin.exec(action, args); ... }

在上面的逻辑能够看到。PluginManager会利用getPlugin方法拿出相应的服务,例如以下:
public IPlugin getPlugin(String pluginName) throws PluginNotFoundException { String className = configs.get(pluginName); if(className==null){ throw new PluginNotFoundException(pluginName); } if (plugins.containsKey(className)) { return plugins.get(className); } else { return addPlugin(className); } }




这样,我们就拿到了一个实现了IPlugin接口中的Plugin实现类。
IPlugin是一个接口,其定义例如以下:

public interface IPlugin { public static final String SERVICE = " service" ; public static final String ACTION = " action" ; public static final String ARGS = " args" ; /** * 运行请求 * * @param action *功能 * @param args *參数 * @return pluginResult 结果 */ public PluginResult exec(String action, JSONObject args)throws ActionNotFoundException;


里面定义的最重要的方法就是exec方法。每个我们自己定义的插件都要实现这个接口,只是在这里。我们先实现了一个抽象基类Plugin。在里面实现一些公共的逻辑,而对于详细的实现,再由Plugin的子类去继承。

public abstract class Plugin implements IPlugin { protected DroidHtml5 context;


比方,我们拿上面的Toast类。其就会继承Plugin。然后依据相应的服务去实现相应的逻辑,调用原生环境的Toast。


public class Toast extends Plugin { @Override public PluginResult exec(String action, JSONObject args) throws ActionNotFoundException { if (" makeTextShort" .equals(action)) { return makeTextShort(args); }else if (" makeTextLong" .equals(action)) { return makeTextLong(args); } else { throw new ActionNotFoundException(" Toast" , action); } } private PluginResult makeTextShort(JSONObject args) {try { String text = args.getString(" text" ); android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_SHORT).show(); } catch (JSONException e) { e.printStackTrace(); return PluginResult.newErrorPluginResult(e.getMessage()); } return PluginResult.newEmptyPluginResult(); } private PluginResult makeTextLong(JSONObject args) {try { String text = args.getString(" text" ); android.widget.Toast.makeText(context, text, android.widget.Toast.LENGTH_LONG).show(); } catch (JSONException e) { e.printStackTrace(); return PluginResult.newErrorPluginResult(e.getMessage()); } return PluginResult.newEmptyPluginResult(); }}




从上面的代码。相信大家非常easy就行理解了Plugin机制了。3)从Html页面来调用。
我们在Android原生环境定义了这么一套Plugin机制。那么在Html里面,也能够有这种一套接口方法,来相应不同的Plugin,所以我们在javascript中也会定义各种各样的对象。
比方上面描写叙述的Toast插件,我们能够在javascript中定义一个相应的对象。例如以下:

var Toast = { makeTextShort : function(text) {return exec(" Toast" , " makeTextShort" , JSON.stringify(text)); }, makeTextLong : function(text) {return exec(" Toast" , " makeTextLong" , JSON.stringify(text)); }}


这里,我们能够看到Toast的makeTextShort方法,会调用上一篇文章中讲到的exec方法,由于弹窗显示这样的东西肯定是同步的,不会说做了一会流程,突然间就跑出一个框来。告诉我,你刚才做错了。对吧。
而在这里,我们就会将服务名(Toast)。操作方法(makeTextShort)。还有显示的内容(JSON.stringfy(text))等通过exec方法,然后利用WebChromeClient的onJsPrompt方法。将命令传递给PluginManager,由PluginManager来处理。
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {System.out.println(" onJsPrompt:defaultValue:" + defaultValue + " |" + url + " ," + message); JSONObject args = null; JSONObject head = null; try { // message:{" service" : " XX" , " action" : " xx" } head = new JSONObject(message); if (defaultValue != null & & !defaultValue.equals(" " )) { try { args = new JSONObject(defaultValue); } catch (Exception e) { e.printStackTrace(); } }String execResult = mPluginManager.exec(head.getString(IPlugin.SERVICE), head.getString(IPlugin.ACTION), args); result.confirm(execResult); return true;



4)我们会把这些定义的插件对象,还有同步(exec),异步运行(exec_sync)的方法都写到一个javascript文件里,方便统一管理,所以一般这个文件内容就会像以下这样:

var Toast = { makeTextShort : function(text) {return exec(" Toast" , " makeTextShort" , JSON.stringify(text)); }, makeTextLong : function(text) {return exec(" Toast" , " makeTextLong" , JSON.stringify(text)); } } var Dialog = { ... } var AndroidHtml5 = { .... /* * exec_asyn调用的方法 @params {JSONObject} cmd 服务名和动作命令 @params {String} args 參数 */ callNative : function(cmd, args, success, fail) { .... }, < span style=" white-space:pre" > < /span> ... callBackJs : function(result,key) { ... } }; /* * Html5与Android同步交互接口 */ var exec = function(service, action, args) { var json = { " service" : service, " action" : action }; var result_str = prompt(JSON.stringify(json), args); var result; try { result = JSON.parse(result_str); } catch (e) { console.error(e.message); } ... } /* * Html5与Android异步交互接口 */ var exec_asyn = function(service, action, args, success, fail) { var json = { " service" : service, " action" : action }; var result = AndroidHtml5.callNative(json, args, success, fail); }


结束。



























    推荐阅读