上一篇文章说到,当利用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
|
|
可以联想到,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();
}
}
|
1
2
|
private
HashMap private
HashMap
|
在这里,在plugins.xml文件中定义的name属性就是这个服务名称。
2)根据请求的服务名称和操作方法等,为这个请求找到对应的Plugin去处理。
1
|
String execResult = mPluginManager.exec(
"service"
,
"action"
, args);
|
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;
|
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),还有显示的内容(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) {
....
},
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);
}
|