Cordova App集成第三方SDK
第一步,先测试SDK包打包安装后demo能否能正常在ios和android设备上。
IOS
获取证书标识的方法
两个同名的证书,重签名时会有提示
“./AppResignTool [证书名称或标识] [主应用的 BundleID] [插件的描述文件名称]
为什么要重签名?
因为IOS系统的限制,有的第三方插件SDK, 需要重签名才能使用
比如这个 NetAccessProvider.appex
重签名成功
钥匙串删除Apple developer 证书后,自动替换为iPhone Developer证书
第二步,
思路1 把对方的sdk封装成一个插件
学习最好的方法就是借鉴, 1.可以借鉴公司以前的项目封装 2. IOS没人封装过, 那么就可以查看第三方的插件是如何封装和调用Object-C 方法、如何在里面传参的 比如 cordova-plugin-badge 插件, 可以在Android和IOS上使用, 里面就有他的调用方法
思路2 IOS: 把我们的cordova工程嵌入他们的Object-C 原生项目中
首先,先学习自创插件
Android
1.创建cordova项目
cordova create demo xxx.xxx.xxx
demo --> 工程名 —— xxx.xxx.xxx --> 包名(config.xml文件内的id)
2.添加Android平台
3.安装plugman插件
yarn global add plugman
创建一个插件:
plugman create --name hnbhyoa-vpn --plugin_id cordova.hnbhyoa.vpn --plugin_version 1.0.0
创建插件的话 建议你不要创建在cordova项目中
把他放入专门放插件的目录
4.编写生成插件中的安卓代码-->当然也可以使用命令进行生产操作:(此命令在插件根目录下执行)
plugman platform add --platform_name android/ios
5.修改plugin.xml
<?xml version='1.0' encoding='utf-8'?> <plugin id="cordova.hnbhyoa.vpn" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <name>hnbhyoa-vpn</name> <!-- cordova-hnbhyoa-vpn.js 文件将被安装到plugins/cordova-hnbhyoa-vpn/www/文件夹下 --> <js-module name="cordova-hnbhyoa-vpn" src="www/hnbhyoa-vpn.js"> <!-- clobbers 元素是 js-module元素内标记,用于指定 module.exports 被插入在 window 对象的命名空间 --> <clobbers target="cordova.plugins.vpn"/> </js-module> <!-- Android平台 --> <platform name="android"> <!-- target 指定文件被复制到相对于 cordova 项目根路径的路径这里复制到了 platforms/android/app/src/main/res/xml --> <config-file parent="/*" target="res/xml/config.xml"> <!-- feature代表此插件提供的一个功能模块,name是此功能模块的命名 --> <feature name="HnbhyoaVpn"> <!-- android-package是此功能模块对应的android实现 value的值是对应的插件中HnbhyoaVpn.java存放的路径 这里安装到了app/java/cordova.hnbhyoa.vpn文件夹下 ,如果这里的value改为HnbhyVpn,那么android下的.java文件也要改名为HnbhyVpn.java --> <param name="android-package" value="cordova.hnbhyoa.vpn.HnbhyoaVpn"/> </feature> </config-file> <!-- target 指定文件被复制到相对于 cordova 项目根路径的路径。如果指定的文件不存在,就会忽略配置变化,并继续安装 --> <config-file parent="/*" target="AndroidManifest.xml"></config-file> <!-- src 相对于 plugin.xml 文件的位置, target-dir:复制文件到该指定的相对于 cordova 项目根目录的目录中,要和上面的value路径对应 --> <!-- 这里安装到了 platforms/android/app/src/main/java/cordova/hnbhyoa/vpn/HnbhyoaVpn.java--> <source-file src="src/android/HnbhyoaVpn.java" target-dir="src/cordova/hnbhyoa/vpn"/> </platform>
<platform name="ios">
<config-file parent="/*" target="config.xml">
<feature name="HnbhyoaVpnIOS">
<param name="ios-package" value="cordova.hnbhyoa.vpn.HnbhyoaVpnIOS"/>
</feature>
</config-file>
<header-file src="src/ios/hnbhyoa-vpn.h"/>
<source-file src="src/ios/hnbhyoa-vpn.m"/>
</platform>
</plugin>
修改www/文件夹下的 js文件
var exec = require('cordova/exec'); // hnbhyoa-vpn: 是plugin.xml文件中的feature标签 name属性 // show:是js中调用的方法名 // [arg0]: 表示一个参数,[arg0,arg1]:表示两个参数 // Android
exports.show = function (arg0, success, error) { exec(success, error, 'HnbhyoaVpn', 'show', [arg0]); };
// IOS
exports.doTest = function (args, success, error) {
exex(success, error, 'HnbhyoaVpnIOS', 'doTest', [args]);
};
修改src/android/下的 java文件
package cordova.hnbhyoa.vpn; import android.widget.Toast; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; import org.json.JSONException; /** * This class echoes a string called from JavaScript. */ public class HnbhyoaVpn extends CordovaPlugin { @Override public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { if ("show".equals(action)){ // 获取activity和context --> cordova.getActivity()和cordova.getContext() Toast.makeText(cordova.getContext(),args.getString(0),Toast.LENGTH_SHORT).show(); return true; } return false; } }
修改src/ios/下的 .m和.h文件
HnbhyoaVpnIOS.m
#import "HnbhyoaVpnIOS.h" @implementation HnbhyoaVpnIOS - (void)doTest:(CDVInvokedUrlCommand *)command { NSLog(@"插件被调起了"); } @end
HnbhyoaVpnIOS.h
#import <Cordova/CDVPlugin.h> @interface HnbhyoaVpnIOS : CDVPlugin - (void)doTest:(CDVInvokedUrlCommand *)command; @end
6.完成plugin.xml修改后,我们进入插件目录,执行 npm init -y
7.添加插件到cordova项目中, cordova plugin add [插件路径]
` 移除是插件的包名` cordova plugin remove [包名]
8.使用js调用show和doTest方法方法
document.getElementById('toast').onclick = function(){ cordova.plugins.vpn.show('777') }
document.getElementById('test').onclick = function(){
cordova.plugins.vpn.doTest();
}
9.注意事项:
index.html中可能会出现button事件无法生效,把<head>中第一行替换成下面
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *;script-src * 'unsafe-inline'"
由于您无法修改cordovas .gradle文件,因此必须添加自己的文件并在plugin.xml中引用它,您可以像这样进行操作:
<framework src="src/android/*.gradle" custom="true" type="gradleReference" />
这将允许您执行诸如编译外部模块之类的事情.为了使它真正起作用,您将不得不在要集成的项目之外创建一个.aar库.
产生的gradle-extension看起来像这样:
repositories { jcenter() flatDir { dirs 'libs' } } dependencies { compile(name:'KTplay', ext:'aar') } android { packagingOptions { exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' } }
这假定您已将.aar库放在名为libs的插件的子目录中.剩下要做的就是确保在构建过程中确实复制了库,这就是为什么我们必须将其作为资源文件添加到plugin.xml:
中的原因
<resource-file src="libs/KTplay.aar" target="libs/KTplay.aar" />
其他:
hook 自定义脚本
<hook type="after_plugin_install" src="scripts/afterPluginInstall.js" /> <hook type="before_plugin_uninstall" src="scripts/beforePluginUninstall.js" />
安装后文件 afterPluginInstall.js
const path = require('path');
const fs = require('fs-extra');
const figlet = require('figlet');
const outputChar = (str) => {
return new Promise((resolve) => {
figlet(str, function (err, data) {
if (err) {
return resolve(str);
}
return resolve(data);
});
});
};
module.exports = async function (ctx) {
const sourceLibs = path.join(ctx.opts.plugin.dir, './src/libs');
const targetLibs = path.join(ctx.opts.projectRoot, './platforms/android/app/libs');
await fs.copy(sourceLibs, targetLibs);
// await fs.copy(sourceJNI, targetJNI);
const chars = await outputChar('ADD SUCCESS');
console.log(chars);
console.log('^_^ vpn plugin add success!');
};
卸载前文件 beforePluginUninstall.js
const path = require('path');
const fs = require('fs-extra');
const figlet = require('figlet');
const outputChar = (str) => {
return new Promise((resolve) => {
figlet(str, function(err, data) {
if (err) {
return resolve(str);
}
return resolve(data);
});
});
};
module.exports = async function(ctx) {
const targetLibs = path.join(ctx.opts.projectRoot, './platforms/android/app/libs');
await fs.remove(targetLibs);
const chars = await outputChar('REMOVE SUCCESS');
console.log(chars);
console.log('^_^ remove londen plugin SDK');
};
现在,开始正题
这是第三方给的SDK文件夹,可以直接用android studio启动
第一步、framework引用aar包
1.写入plugin.xml文件中
<framework src="src/vpn.gradle" custom="true" type="gradleReference" />
2.包vpn.gradle文件放到src文件下
repositories { jcenter() flatDir { dirs 'libs' } } dependencies { // implementation files('libs/TopSecVPN.aar') compile(name:'TopSecVPN', ext:'aar') } android { packagingOptions { exclude 'META-INF/NOTICE' exclude 'META-INF/LICENSE' } }
3.把第三方提供的aar文件放入文件夹中
第二步、把第三方的SDK demo.java文件放入自定义插件文件夹中
放到这里来(记得要修改包名为插件id cordova.hnbhyoa.vpn)
第三步、找到主页面(MainActivity.java)文件
分析文件内容
第四步、构建新文件
可以参考show方法的java文件
public class HnbhyoaVpn extends CordovaPlugin { @Override public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { if ("show".equals(action)){ // 获取activity和context --> cordova.getActivity()和cordova.getContext() Toast.makeText(cordova.getContext(),args.getString(0),Toast.LENGTH_SHORT).show(); return true; } return false; } }
需要了解的知识:
1.cordova 调用原生上下文
原生: getApplicationContext()
cordova: cordova.getActivity().getApplicationContext()
2. e.printStackTrace() 方法 在命令行打印异常信息在程序中出错的位置及原因。
3. 报错提示 callbackContext.error(e.toString());
4.@Override 重写方法 类似注释代码
开始修改
添加常用API
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaArgs;
import org.json.JSONArray;
import org.json.JSONException;
修改他们的Class为Cordova的类
public class MainActivity extends Activity implements OnAcceptSysLogListener,OnAcceptResultListener,OnClickListener {
}
改为
public class HnbhyVpn extends CordovaPlugin implements OnAcceptSysLogListener,OnAcceptResultListener{ // 这里移除了OnClickListener抽象方法,因为没有用到
}
先添加Cordova的方法
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { switch (action) { case "init": this.init(callbackContext); return true; } return false; }
对应js文件调用
var exec = require('cordova/exec'); exports.init = function (success, error) { exec(success, error, 'HnbhyVpn', 'init'); };
找到他们的初始化方法
private void Initialize() { try{ /* * 在Android6.0+请在初始化SDK前申请好电话、存储等权限,确保SDK能够正常初始化 */ TopVPNSdkDemo.m_ihVPNService=VPNService.getVPNInstance(this.getApplicationContext()); } catch(Exception ex) { Log.i("TopVPNSdkDemo", ex.toString()); } if(null==TopVPNSdkDemo.m_ihVPNService){ if (Build.VERSION.SDK_INT>22){ Toast.makeText(this,"VPN实例初始化失败!可能是权限问题导致。如果是,请先到'设置-》应用管理'页面找到本程序,并在其‘应用权限’中授权电话、存储和网络等权限然后再试(本示例程序未加入动态权限申请的功能);如果不是,请看logcat日志输出。",Toast.LENGTH_LONG).show(); } else{ Toast.makeText(getApplicationContext(), "VPN实例初始化失败!具体请看logcat日志输出。", Toast.LENGTH_LONG).show(); } return ; } TopVPNSdkDemo.m_ihVPNService.setOnAcceptResultListener(this); TopVPNSdkDemo.m_ihVPNService.setOnAcceptSysLogListener(this); ((EditText)findViewById(R.id.edtVPNAddr)).setOnFocusChangeListener(new OnFocusChangeListener(){ @SuppressLint("ShowToast") @Override public void onFocusChange(View arg0, boolean arg1) { if(!arg1) { String strAddr = ((EditText)arg0).getText().toString().trim(); DoConfigurationVPN(strAddr); } } }); }
改为
private void init(CallbackContext callbackContext) { try { /* * 在Android6.0+请在初始化SDK前申请好电话、存储等权限,确保SDK能够正常初始化 */ TopVPNSdkDemo.m_ihVPNService = VPNService.getVPNInstance(cordova.getActivity().getApplicationContext()); } catch (Exception e) { e.printStackTrace(); } if (null == TopVPNSdkDemo.m_ihVPNService) { if (Build.VERSION.SDK_INT > 22) { callbackContext.error("VPN实例初始化失败2"); Toast.makeText(cordova.getActivity().getApplicationContext(), "VPN实例初始化失败2222。", Toast.LENGTH_LONG).show(); } else { Toast.makeText(cordova.getActivity().getApplicationContext(), "VPN实例初始化失败!具体请看logcat日志输出。", Toast.LENGTH_LONG).show(); } return; } TopVPNSdkDemo.m_ihVPNService.setOnAcceptResultListener(this); TopVPNSdkDemo.m_ihVPNService.setOnAcceptSysLogListener(this); }
尝试启动
这里直接使用会出现报错 说明需要重写方法
根据错误提示,找到抽象类新增的方法,在报错的类中进行重写,满足语法要求即可;
如果当前类未用到,返回值可以为nulll。
因为用的是第三方SDK, 所以我们直接把对方的抽象方法拿过来修改即可
注释掉一些报错的地方(看命名是用不到的地方)
最终java文件
第五步、修改www/下的js文件(完整代码 示例只用了init方法)
var exec = require('cordova/exec'); exports.init = function (success, error) { exec(success, error, 'HnbhyoaVpn', 'init'); }; exports.login = function (args, success, error) { exec(success, error, 'HnbhyoaVpn', 'login', [args]); }; exports.logout = function (success, error) { exec(success, error, 'HnbhyoaVpn', 'logout'); }; exports.getResource = function (success, error) { exec(success, error, 'HnbhyoaVpn', 'getResource'); }; exports.startService = function (success, error) { exec(success, error, 'HnbhyoaVpn', 'startService'); }; exports.closeService = function (success, error) { exec(success, error, 'HnbhyoaVpn', 'closeService'); }; exports.doConfigurationVPN = function (args, success, error) { exec(success, error, 'HnbhyoaVpn', 'doConfigurationVPN', [args]); };
最后、js调用
document.getElementById('toast').onclick = function(){ cordova.plugins.vpn.init(function (data) { alert(data) }, function (message) { alert(message); }); }
集成成功
遇到的坑:
千万不要嵌套Promise, 会导致获取不到失败回调, 获取的全是成功的, 应该要拆开来写
2. 监听用户选择的监听事件没有生效
.