AndroidとWebViewのプラグイン管理メカニズム


前の記事では、WebViewClientまたはWebChromeClientを使用してhtmlページからのリクエストを処理すると、対応するサービス名、操作方法、および対応するパラメータデータがPluginManagerというクラスに渡されます.
PluginManagerクラスの役割は何ですか?
Androidのオリジナル環境を利用した機能、例えばカメラ、例えばアルバムなど、これらの機能は分散しており、いつこれらの機能が必要なのか、いつこれらの機能が必要なのか、いつ必要なのか分からないので、プラグインのように、必要なときにロードして、必要でないときに相手にしないことを望んでいます.PluginManagerクラスはこのような管理クラスです.
主にいくつかのことを担当しています.
1)HTMLページに入るときは、定義したコントロールをロードします.
mPluginManager = new PluginManager(this);
mPluginManager.loadPlugin();

では、PluginManagerは、Htmlページからのリクエストに応答するためにどのくらいのpluginをロードするかをどのように知っていますか?
私たちは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は次のように定義されます.
private HashMap<String, String> configs = new HashMap<String, String>();
private HashMap<String, IPlugin> plugins = new HashMap<String, IPlugin>();

loadPluggin法によりpluinsに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);	

}

終わります.