第一步,先测试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. 监听用户选择的监听事件没有生效
.