Android与WebView的插件管理机制

上一篇文章说到,当利用WebViewClient或者WebChromeClient来处理由html页面传过来的请求的时候,都会将对应的服务名称,操作方法和对应的参数数据传给一个叫PluginManager的类。

PluginManager类的作用是什么?

大家知道,当利用Android原生环境的功能,比如照像机,比如相册等,这些功能都是很分散的,说不清楚什么时候是需要这些功能,什么时候是不需要这些功能的,所以我们希望能够像插件一样,需要的时候就加载进来,不需要的时候不去理他,而PluginManager类就是一个这样的管理类。

它主要负责几件事情:

1)进入HTML页面的时候,去加载我们定义好的控件。

?
1
2
mPluginManager = new PluginManager( this );
mPluginManager.loadPlugin();

那么PluginManager怎么知道本个应用要加载多少plugin来去响应由Html页面来的请求呢?

我们是通过一个叫plugin.xml配置文件来定义的。

?
1
2
3
4
5
6
     "App" class = "com.lms.xxx.bridge.plugin.App" >
     "Toast" class = "com.lms.xxx.plugin.Toast" >
     "Dialog" class = "com.lms.xxx.bridge.plugin.Dialog" >  
     "User" class = "com.lms.xxx.bridge.plugin.User" >


比如在上面的配置文件中,我们会加载App, Toast, Dialog 和 User 这几个plugin。

可以联想到,Toast和Dialog都是Android原生环境下的显示窗口,我们虽然用html页面来实现界面,但是为了保持整个应用的一致性,我们就会用到原生环境中的Toast或者我们自定义的对话框等控件。

需要用到什么,就在这里定义什么。

我们再来看一下loadPlugin方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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定义如下:

?
1
2
private HashMap "" > configs = new HashMap "" >();
private HashMap "" > plugins = new HashMap "" >();

通过loadPlugin方法,我们就将在plugins.xml中定义好的插件,给加载到configs中去了,configs里存放的只是类名,而plugins存放的才是实现,不过我们这里不需要关心这个。

在这里,在plugins.xml文件中定义的name属性就是这个服务名称。

2)根据请求的服务名称和操作方法等,为这个请求找到对应的Plugin去处理。

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

看一下exec方法,

?
1
2
3
4
5
6
public String exec(String service, String action, JSONObject args) throws PluginNotFoundException {
         IPlugin plugin = getPlugin(service);
     ...
     PluginResult result = plugin.exec(action, args);
     ...    
}

在上面的逻辑可以看到,PluginManager会利用getPlugin方法拿出对应的服务,如下:

?
1
2
3
4
5
6
7
8
9
10
11
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是一个接口,其定义如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的子类去继承。

?
1
2
3
public abstract class Plugin implements IPlugin {
 
     protected DroidHtml5 context;

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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中定义一个对应的对象,如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
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来处理。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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文件中,方便统一管理,所以一般这个文件内容就会像下面这样:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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) {
         ....
     },
"white-space:pre" ...
     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);   
 
}

你可能感兴趣的:(移动平台)