DCloud_App_Array
DCloud_App_Array
  • 发布:2014-09-25 19:43
  • 更新:2023-12-15 23:11
  • 阅读:311105

Android平台第三方插件开发指导

分类:HTML5+

5+原生插件将不再继续维护,建议开发者升级应用为uni-app项目并使用uni原生插件uni原生插件文档

本文为5+app插件开发指南。uni-app请另外参考文档:https://ask.dcloud.net.cn/article/35428

欢迎有原生开发能力的朋友入群一起交流学习(入群请提供注册邮箱):
DCloud原生开发者群:
1群:256775471
2群:814228233
3群:455763866

技术架构

HTML5+ 基座扩展采用三层结构,JS层、PluginBridge层和Native层。 三层功能分别是:
JS层: 在Webview页面调用,触发Native层代码,获取执行结果。
PluginBridge层: 将JS层请求进行处理,触发Native层扩展插件代码。
Native层: 插件扩展的平台原生代码,负责执行业务逻辑并执行结果返回到请求页面。


开发者在开发扩展插件时需要为扩展插件编写对应的JS API,JS API将在HTML页面中触发调用对应Native扩展方法的请求,并负责接收运行结果。

开发者在编写扩展插件的JS时需要调用Javascript Plugin Bridge的API用来完成对Native层代码的调用和运行结果的返回。在实际应用中,开发者可以根据扩展方法的实际需求不同,将插件的扩展方法设置为同步执行或异步执行。

同步执行的扩展方法会阻塞当前JS代码的执行,直到Native层插件扩展方法执行完毕。异步扩展方法不会阻塞当前JS代码的执行,使用者需要设置回调方法接收Native层返回的执行结果,开发者需要在插件中调用 Native plugin brigde的方法将执行结果返回到请求页面。

工程示例请参考SDK内包含的HBuilder-Integrate工程,工程里已经整合了插件开发和集成方式的示例。

##扩展插件工作流程

异步方法的执行不会阻塞当前JS的运行,方法的执行结果会通过回调的方式返回。
插件异步扩展方法:


同步方法的执行会阻塞当前JS的运行,方法执行完成后可直接获取执行的结果。
插件同步扩展方法:

##扩展插件JS API 编写
开发者在实现JS层API时首先要定义一个插件类的别名,并需要在Android工程的assets\data\properties.xml文件中声明插件类别名和Native层扩展插件类的对应关系

<properties>  
	<features>  
		<feature name="plugintest" value="com.example.H5PlusPlugin.PGPlugintest"></feature>  
</properties>

示例中为方便查看删除了其他插件的代码,开发者在可根据5+应用使用插件的情况进行增删修改。
Feature节点下声明的插件将会在调用时创建相应的对象。

JS扩展方法的实现

同步返回JS扩展方法实现
在实现同步扩展方法时,开发者需要调用JS Plugin Bridge的window.plus.bridge.execSync()
方法,该方法可同步获取Native插件返回的运行结果。

void plus.bridge.execSync( String service, String action, Array<String> args );

service: 插件类别名
action: 调用Native层插件方法名称。
args: 参数列表。

异步返回JS扩展方法实现
在实现异步扩展方法时,开发者需要调用JS Plugin Bridge的plus.bridge.exec()方法,该方法会通知Native层插件执行指定方法,运行结果会通过回调的方式通知JS层

void plus.bridge.exec( String service, String action, Array<String> args );

service: 插件类别名
action: 调用Native层插件方法名称。
args: 参数列表。

扩展插件JS API代码示例:

! function(root, factory) {  
	if (typeof exports == 'object' && typeof module != 'undefined') {  
		module.exports = factory()  
	} else if (typeof define == 'function' && define.amd) {  
		define(factory)  
	} else {  
		/**  
		 * 插件对象直接扩展到 window 对象上  
		 * 这个对象的名字,需要自己填写一下。例如:plugintest  
		 */  
		var moduleName = 'plugintest';  
		root[moduleName] = factory()  
	}  
}(this, function() {  
	var _BARCODE = 'plugintest';  
	var plugintest = {  
		PluginTestFunction: function(Argus1, Argus2, Argus3, Argus4, successCallback, errorCallback) {  
			var success = typeof successCallback !== 'function' ? null : function(args) {  
					successCallback(args);  
				},  
				fail = typeof errorCallback !== 'function' ? null : function(code) {  
					errorCallback(code);  
				};  
			callbackID = plus.bridge.callbackId(success, fail);  
  
			return plus.bridge.exec(_BARCODE, "PluginTestFunction", [callbackID, Argus1, Argus2, Argus3, Argus4]);  
		},  
		PluginTestFunctionArrayArgu: function(Argus, successCallback, errorCallback) {  
			var success = typeof successCallback !== 'function' ? null : function(args) {  
					successCallback(args);  
				},  
				fail = typeof errorCallback !== 'function' ? null : function(code) {  
					errorCallback(code);  
				};  
			callbackID = plus.bridge.callbackId(success, fail);  
			return plus.bridge.exec(_BARCODE, "PluginTestFunctionArrayArgu", [callbackID, Argus]);  
		},  
		PluginTestFunctionSync: function(Argus1, Argus2, Argus3, Argus4) {  
			return plus.bridge.execSync(_BARCODE, "PluginTestFunctionSync", [Argus1, Argus2, Argus3, Argus4]);  
		},  
		PluginTestFunctionSyncArrayArgu: function(Argus) {  
			return plus.bridge.execSync(_BARCODE, "PluginTestFunctionSyncArrayArgu", [Argus]);  
		}  
	};  
	return plugintest;  
});

####Html使用示例

<!DOCTYPE HTML>  
<html>  
	<head>  
		<meta charset="utf-8"/>  
		<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>  
		<meta name="HandheldFriendly" content="true"/>  
		<meta name="MobileOptimized" content="320"/>  
		<title>H5Plugin</title>  
		<script type="text/javascript" src="./js/common.js"></script>  
		<script type="text/javascript" src="./js/test.js"></script>  
		<script type="text/javascript">  
  
  		function pluginShow() {  
            plus.plugintest.PluginTestFunction("Html5","Plus","AsyncFunction","MultiArgument!", function( result ) {alert( result[0]  + "_" + result[1]  + "_" + result[2]  + "_" + result[3] );},function(result){alert(result)});  
        }  
          
        function pluginShowArrayArgu() {  
            plus.plugintest.PluginTestFunctionArrayArgu( ["Html5","Plus","AsyncFunction","ArrayArgument!"], function( result ) {alert( result );},function(result){alert(result)});  
        }  
          
        function pluginGetString()  
        {  
            alert(plus.plugintest.PluginTestFunctionSync("Html5","Plus","SyncFunction","MultiArgument!"));  
        }  
          
        function pluginGetStringArrayArgu()  
        {  
            var Argus = plus.plugintest.PluginTestFunctionSyncArrayArgu(["Html5","Plus","SyncFunction","ArrayArgument!"]);  
            alert( Argus.RetArgu1 + "_" +  Argus.RetArgu2 + "_" + Argus.RetArgu3  + "_" + Argus.RetArgu4 );  
        }  
      
		</script>  
		<link rel="stylesheet" href="./css/common.css" type="text/css" charset="utf-8"/>  
	</head>  
	<body>  
		<header>  
			<div class="nvbt" onclick="back();"><div class="iback"></div></div>  
			<div class="nvtt">PluginTest</div>  
		</header>  
		<div id="dcontent" class="dcontent">  
			<br/>  
			 <div class="button" onclick="pluginShow()">PluginTestFunction()</div>  
            <div class="button" onclick="pluginShowArrayArgu()">PluginTestFunctionArrayArgu()</div>  
            <div class="button" onclick="pluginGetString()">PluginTestFunctionSync()</div>  
            <div class="button" onclick="pluginGetStringArrayArgu()">PluginTestFunctionSyncArrayArgu()</div>	  
			<br/>  
		</div>  
	</body>  
</html>

uni-app 使用示例
uni-app 中无需主动监听 plusready,可以直接调用 plus 相关的 API。

<template>  
	<view>  
		<button @click="pluginShow">pluginShow</button>  
	</view>  
</template>  
<script>  
// 扩展的 js 文件的位置:common/plugins.js  
	var plugins = require('../../common/plugins.js')  
	export default {  
		data() {  
			return {  
				plugins: plugins  
			};  
		},  
		methods: {  
			pluginShow() {  
				this.plugins.PluginTestFunction("Html5", "Plus", "AsyncFunction", "MultiArgument!", function(result) {  
					console.log(result[0] + "_" + result[1] + "_" + result[2] + "_" + result[3]);  
				}, function(result) {  
					console.log(result)  
				});  
			}  
		}  
	}  
</script>  
<style>  
  
</style>

##Android第三方插件开发指导

###准备
首先要下载HTML5+基座的Android版SDK点击下载解压后将HBuilder-Integrate工程导入到ADT中。
###uni-app插件开发
android平台:uni-app插件开发
###老接口实现方式
####开发步骤
创建插件类
开发者创建的扩展插件类应当继承自“IFeature”类实现第三方插件扩展。
创建插件类需要引入的包

import io.dcloud.common.DHInterface.AbsMgr;  
import io.dcloud.common.DHInterface.IFeature;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.common.util.JSUtil;

实现接口类的“excute”方法
excute方法负责接收由html页面发起的扩展插件调用请求,并负责根据传入参数运行执行对应的业务。
“execute”方法的“action”参数传入的是JS类的方法名,类型为String。开发者需要在方法中通过字符串匹配来处理请求的逻辑。“pArgs”参数为字符串列表对象,如JS层调用的方法有传入参数,会通过该参数传递到Native的方法中,方法的传入顺序和JS层传入顺序一致。
开发者在实现同步和异步接口运行结果返回时调用的API是不同的
同步执行方法:
同步执行方法在返回结果时可以直接将结果以return的形式返回给js层,返回的结果需要调用

JSUtil.wrapJsVar("Html5 Plus Plugin Hello1!",true);

处理要返回的字符串,也可调用其他方法处理其他返回值类型(参考io.dclod.util.JSUtil的返回方法)。
异步执行方法:
异步方法开发者需要调用

JSUtil.execCallback(pWebview, cbId, (which==AlertDialog.BUTTON_POSITIVE)?"ok":"cancel", JSUtil.OK, false, false); 

####代码实例

package com.example.H5PlusPlugin;  
  
import io.dcloud.common.DHInterface.AbsMgr;  
import io.dcloud.common.DHInterface.IFeature;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.common.util.JSUtil;  
  
import org.json.JSONArray;  
import org.json.JSONException;  
import org.json.JSONObject;  
  
import android.annotation.SuppressLint;  
import android.annotation.TargetApi;  
import android.os.Build;  
  
  
public class PGPlugintest implements IFeature  
{  
	@Override  
	public void init(AbsMgr arg0, String arg1) {  
		// TODO Auto-generated method stub  
  
	}  
  
  
	@SuppressWarnings("deprecation")  
	@TargetApi(Build.VERSION_CODES.CUPCAKE)  
	@SuppressLint("NewApi")  
	@Override  
	public String execute(final IWebview pWebview, final String action, final String[] pArgs) {  
		// TODO Auto-generated method stub  
		//Context context = pWebview.getContext();  
  
		if ("PluginTestFunction".equals(action))  
		{  
			String CallBackID = pArgs[0];  
			JSONArray newArray = new JSONArray();  
			newArray.put(pArgs[1]);  
			newArray.put(pArgs[2]);  
			newArray.put(pArgs[3]);  
			newArray.put(pArgs[4]);  
  
			JSUtil.execCallback(pWebview, CallBackID, newArray, JSUtil.OK, false);  
		}  
		else if("PluginTestFunctionArrayArgu".equals(action))  
		{  
			String ReturnString = null;  
			String CallBackID =  pArgs[0];  
			JSONArray newArray = null;  
			try {  
				  
				newArray = new JSONArray(pArgs[1]);			  
				String inValue1 = newArray.getString(0);  
				String inValue2 = newArray.getString(1);  
				String inValue3 = newArray.getString(2);  
				String inValue4 = newArray.getString(3);  
				ReturnString = inValue1 + "-" + inValue2 + "-" + inValue3 + "-" + inValue4;  
			} catch (JSONException e) {  
				// TODO Auto-generated catch block  
				e.printStackTrace();  
			}  
						  
			JSUtil.execCallback(pWebview, CallBackID, ReturnString, JSUtil.OK, false);  
			  
		}else if("PluginTestFunctionSync".equals(action))  
		{  
			String inValue1 = pArgs[0];  
			String inValue2 = pArgs[1];  
			String inValue3 = pArgs[2];  
			String inValue4 = pArgs[3];  
			  
			String ReturnValue = inValue1 + "-" + inValue2 + "-" + inValue3 + "-" + inValue4;  
			return JSUtil.wrapJsVar(ReturnValue,true);  
			  
		}else if("PluginTestFunctionSyncArrayArgu".equals(action))  
		{  
			JSONArray newArray = null;  
			JSONObject retJSONObj = null;  
			try {  
				newArray = new JSONArray(pArgs[0]);  
				String inValue1 = newArray.getString(0);  
				String inValue2 = newArray.getString(1);  
				String inValue3 = newArray.getString(2);  
				String inValue4 = newArray.getString(3);  
				  
				retJSONObj = new JSONObject();  
				retJSONObj.putOpt("RetArgu1", inValue1);  
				retJSONObj.putOpt("RetArgu2", inValue2);  
				retJSONObj.putOpt("RetArgu3", inValue3);  
				retJSONObj.putOpt("RetArgu4", inValue4);  
				  
			} catch (JSONException e1) {  
				// TODO Auto-generated catch block  
				e1.printStackTrace();  
			}		  
			  
			return JSUtil.wrapJsVar(retJSONObj);  
  
		}  
		return null;  
	}  
  
	@Override  
	public void dispose(String arg0) {  
		// TODO Auto-generated method stub  
  
	}  
}

###新接口实现方式
创建插件类
创建一个继承自StandardFeature的类,实现第三方插件扩展。
创建插件类需要引入的包
import io.dcloud.DHInterface.IWebview;
import io.dcloud.DHInterface.StandardFeature;
import io.dcloud.util.JSUtil;
实现扩展方法
Native层扩展插件的方法名需要和JS Plugin Bridge里windows.plus.bridge.exec()或windows.plus.bridge.execSync()方法的第二个传入参数相同,否则无法调用到指定的方法。

public void PluginTestFunction(IWebview pWebview, JSONArray array)

扩展方法有两个传入参数
IWebview pWebview 发起请求的webview,
JSONArray array JS请求传入的参数

开发者在实现同步和异步接口运行结果返回时调用的API是不同的
同步执行方法:
同步执行方法在返回结果时可以直接将结果以return的形式返回给js层,返回的结果需要调用

JSUtil.wrapJsVar("Html5 Plus Plugin Hello1!",true);

处理要返回的字符串,也可调用其他方法处理其他返回值类型(参考io.dclod.util.JSUtil的返回方法)。
异步执行方法:
异步方法开发者需要调用

JSUtil.execCallback(pWebview, cbId, (which==AlertDialog.BUTTON_POSITIVE)?"ok":"cancel", JSUtil.OK, false, false); 

####代码示例

  
package com.example.H5PlusPlugin;  
import io.dcloud.common.DHInterface.IWebview;  
import io.dcloud.common.DHInterface.StandardFeature;  
import io.dcloud.common.util.JSUtil;  
  
import org.json.JSONArray;  
import org.json.JSONException;  
import org.json.JSONObject;  
  
public class PGPlugintest extends StandardFeature  
{     
    public void PluginTestFunction(IWebview pWebview, JSONArray array)  
    {  
        String CallBackID = array.optString(0);  
        JSONArray newArray = new JSONArray();  
        newArray.put(array.optString(1));  
        newArray.put(array.optString(2));  
        newArray.put(array.optString(3));  
        newArray.put(array.optString(4));  
        JSUtil.execCallback(pWebview, CallBackID, newArray, JSUtil.OK, false);  
  
    }  
  
    public void PluginTestFunctionArrayArgu(IWebview pWebview, JSONArray array)  
    {  
        String ReturnString = null;  
        String CallBackID =  array.optString(0);  
        JSONArray newArray = null;  
        try {  
  
            newArray = new JSONArray( array.optString(1));            
            String inValue1 = newArray.getString(0);  
            String inValue2 = newArray.getString(1);  
            String inValue3 = newArray.getString(2);  
            String inValue4 = newArray.getString(3);  
            ReturnString = inValue1 + "-" + inValue2 + "-" + inValue3 + "-" + inValue4;  
        } catch (JSONException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
  
        JSUtil.execCallback(pWebview, CallBackID, ReturnString, JSUtil.OK, false);  
    }  
  
    public String PluginTestFunctionSyncArrayArgu(IWebview pWebview, JSONArray array)  
    {  
        JSONArray newArray = null;  
        JSONObject retJSONObj = null;  
        try {  
  
            newArray = array.optJSONArray(0);  
            String inValue1 = newArray.getString(0);  
            String inValue2 = newArray.getString(1);  
            String inValue3 = newArray.getString(2);  
            String inValue4 = newArray.getString(3);  
  
            retJSONObj = new JSONObject();  
            retJSONObj.putOpt("RetArgu1", inValue1);  
            retJSONObj.putOpt("RetArgu2", inValue2);  
            retJSONObj.putOpt("RetArgu3", inValue3);  
            retJSONObj.putOpt("RetArgu4", inValue4);  
  
        } catch (JSONException e1) {  
            // TODO Auto-generated catch block  
            e1.printStackTrace();  
        }         
  
        return JSUtil.wrapJsVar(retJSONObj);  
    }  
  
    public String PluginTestFunctionSync(IWebview pWebview, JSONArray array)  
    {  
        String inValue1 = array.optString(0);  
        String inValue2 = array.optString(1);  
        String inValue3 = array.optString(2);  
        String inValue4 = array.optString(3);  
  
        String ReturnValue = inValue1 + "-" + inValue2 + "-" + inValue3 + "-" + inValue4;  
        return JSUtil.wrapJsVar(ReturnValue,true);  
    }  
  
}  

##三 关联JS插件名和原生类
在编写扩展插件时需要修改“/assets/data”中properties.xml文件,在其中添加JS对象名称和Android包的类名对应关系,SDK会根据对应的类名查找并生成相应的对象并执行对应的逻辑。


在应用的manifest.json文件中还需要添加扩展插件的应用使用权限

Android平台插件开发API

###io.dcloud.DHInterface.IFeature

扩展插件接口类,Android平台的扩展插件应当继承自此接口类并实现接口方法。
####常用方法说明:

String execute(IWebview pWebView, String action, String[] pArgs);

执行扩展插件的方法,调用native代码实现具体的操作。
pWebView: 扩展插件方法运行的窗口
action: 调用插件方法的名称
args: 调用插件方法使用的参数列表

###io.dcloud.util.JSUtil

####常用方法说明:

String wrapJsVar(String value, boolean isString);

转换JS层的返回值,也用于异步接口中回调函数的参数。
参数说明:
value: 要返回到JS层的值
**isString:**返回值类型是否为原始字符串
返回方法:
boolea类型: wrapJSVar( "true", false );
Number类型: wrapJsVar( "99", false );
String类型: wrapJsVar( "hello", true );
数组类型: wrapJsVar( "[1,2,3,4,5]", false );
JSON类型: wrapJsVar( "{'name':'value'}", false );

String execCallback(IWebview pWebView, String pCallbackId, String pMessage, int pStatus, boolean isJson, boolean pKeepCallback);

触发扩展插件中的回调方法。

参数说明:
**pWebView:**扩展插件方法运行的窗口
**pCallbackId:**回调函数的唯一标识
**pMessage:**回调函数的参数
**pStatus:**操作是否成功,成功则使用JSUtil.OK,否则使用错误代码
**isJson:**回调函数参数是否为JSON数据
**pKeepCallback:**是否可多次触发回调函数

##术语字典
JS Plugin Bridge: H5+ Plugin Bridge层JS部分API,用户通过调用相应的API触发Native层扩展插件相应方法的调用。
Native Plugin Bridge: H5+ Plugin Bridge层Native部分API,开发者通过实现接口类方法,实现由JS层触发Native层扩展插件逻辑。开发者调用API实现Native扩展方法运行结果得返回。
**Native层插件:**开发者使用原生语言实现的5+扩展插件,可被JS层通知调用。
**插件类别名:**JS层字符串,用来声明JS方法和Native层插件的对应关系

##IOS平台开发第三发插件请参考
IOS平台第三发插件开发指导

12 关注 分享
BruceAn haha2021a Mosaic9 Trust 小山三夏 l***@sina.com gaohuazi 犹行犹予 p***@qq.com 8***@qq.com l***@163.com 一抱一个胖猪猪

要回复文章请先登录注册

l***@21cn.com

l***@21cn.com

回复 拒绝抽烟 :
那NativeJS就是个摆设?当初架构有问题。
另外,这个插件方式只能针对function?没有针对object?为什么不搞成object方式,在js中直接引入,这样就不用一个个用js去写function了,非要搞得这么麻烦。
2020-05-07 11:55
拒绝抽烟

拒绝抽烟

回复 l***@21cn.com :
NativeJS性能极差
2020-05-07 09:38
l***@21cn.com

l***@21cn.com

感觉这么写很蛋疼,如果集成第三方SDK,自己还要原生写接口,又还要封装JS,太啰嗦了,为什么不直接NativeJS搞?
plus.android.importClass('Hello');
var hello = new Hello;
hello.say();

//评论居然不能删除?麻烦删掉前面个
2020-05-03 12:43
拒绝抽烟

拒绝抽烟

回复 3***@qq.com :
对,官方要考虑下5+也是有需求的,不能为了推广uni这么搞,开发者会对平台丧失信心,以后都不敢做大项目的,就只能玩玩短平快的小玩意了
2020-04-30 10:29
3***@qq.com

3***@qq.com

当初开发app,也是因为你们宣扬H5+mui有跨平台的特性,所以用了hbuilder,结果发现有些功能实现不了,必须用原生,又引导我们使用原生打包,现在有告诉我们不支持原生插件了,推荐我们改成uni-app,官方,能不能不要这么多变?我们的5+app怎么转uni-app?你倒是给个办法啊,当初开发app用了那么多的时间精力,就你一句话不要搞了?并不是每个项目都适合用小程序,人心凉了,最终伤害的是谁呢。。。
2020-04-27 08:28
z***@163.com

z***@163.com

项目使用5+app插件模式,H5使用MUI,native层自定义插件,开发完成后,上传到uni-app插件市场,H5可以调研uni-app插件市场插件吗?
2020-04-07 09:49
4***@qq.com

4***@qq.com

使用自定义基座运行在手机端时,提示打包未添加自定义的插件模块。但是明明已经配了,打基座的时候添加过了。HbuilderX也配置了。 但是生成本地资源包后放进app中是可以的,这是为什么?
2020-03-05 22:21
Gavin2020

Gavin2020

有人用这个集成第三方的module嘛 我集成之后在跳转第三方activity的时候 找不到那个activity但是第三方的工具类可以找到 Caused by: java.lang.NoClassDefFoundError: com.etop.activity.EtopScanVinActivity 在主程序也注册了这个activity
2020-01-03 17:23
w***@163.com

w***@163.com

最近工作中需要用到第三方插件,于是研究了了一下本地离线打包和第三方插件的开发,现在说说在这个过程中遇到的几个坑。
1.lib目录下的资源最好用HBuilder-Hello这个项目lib目录下的,如果使用SDK目录下的出现版本不匹配的问题,导致androidstudio编译不能通过,应该是资源包的版本和as的版本不匹配的问题,我猜的,有知道的同学还望赐教。
2.一开始我是把所有lib目录下的资源包都拷过来了,但是这样就有很多我没用到的包,打包出来的apk会很大,于是我就按照官网的文档,把不需要的都删掉了,然后就出现了一个很尴尬的情况,app一直卡在splash页进入不了主页面,经过我的一番测试,我发现是因为android-gif-drawable-release@1.2.17.aar这个包没有添加导致,也不知道是为啥,希望有知道的小伙伴,赐教一下。
2019-12-18 12:22
2***@qq.com

2***@qq.com

这个demo可以导入Android studio中使用吗???
2019-08-02 16:24