上一篇文章说到,当利用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"         >        


         </plugin></plugin></plugin></plugin></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定义如下:




private          HashMap<string, string=         ""         > configs =          new          HashMap<string, string=         ""         >();        


         private          HashMap<string, iplugin=         ""         > plugins =          new          HashMap<string, iplugin=         ""         >();</string,></string,></string,></string,>



通过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();        


                  }        


                  


         }





从上面的代码,相信大家很容易就能够理解了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);            


                  


         }