从零创建Cordova插件-进阶

从零创建Cordova插件-进阶

l cordova 开发进阶

l 开发进阶之插件配置

活动

1
2
3
4
5
<platform name="android">
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity android:name="cn.com.ths.thstoast.MyActivity"/>
</config-file>
</platform>

广播

静态注册

1
2
3
4
5
6
7
8
9
10
<platform name="android">
<!-- 自定义的广播接收器 -->
<receiver android:name="cn.com.ths.thstoast.MyBroadcastReceiver" android:enabled="true">
<intent-filter>
<!-- 飞行模式开/关广播 -->
<action android:name="android.intent.action.AIRPLANE_MODE" />
<category android:name="$PACKAGE_NAME" />
</intent-filter>
</receiver>
</platform>

服务

1
2
3
4
5
6
<platform name="android">
<service
android:name="cn.com.ths.thstoast.MyService"
android:enabled="true"
android:exported="true" />
</platform>

内容提供者

1
2
3
4
5
6
7
8
9
10
11
<platform name="android">
<!-- 访问其他应用的内容需要配置应用读写权限 -->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>

<!-- 如果不是上面Calendar这种系统provider(android.provider.*),则需要注册 -->
<provider
android:name="org.apache.cordova.provider.TestContentProvider"
android:authorities="org.apache.cordova.provider.testprovider"
android:exported="false" />
</platform>

jar+aar+so 配置

jar 配置

1
2
3
<platform name="android">
<lib-file src="src/android/libs/jpush-android-3.0.1.jar" />
</platform>

so 配置

so 路径

so 是在 NDK 平台开发的,NDK 是用来给安卓手机开发软件用的,但是和 SDK 不同的是它用的是 C 语言,而 SDK 用的是 Java 语言。NDK 开发的软件在安卓的环境里是直接运行的,一般只能在特定的 CPU 指令集的机器上运行。

so 配置通常配置在 libs 目录下

so plugin.xml 配置,注意输出路径子目录也要改
1
2
3
4
5
6
<source-file src="src/android/libs/armeabi/libjcore110.so" target-dir="libs/armeabi" />
<source-file src="src/android/libs/armeabi-v7a/libjcore110.so" target-dir="libs/armeabi-v7a" />
<source-file src="src/android/libs/arm64-v8a/libjcore110.so" target-dir="libs/arm64-v8a" />
<source-file src="src/android/libs/x86/libjcore110.so" target-dir="libs/x86" />
<source-file src="src/android/libs/x86_64/libjcore110.so" target-dir="libs/x86_64" />
</source-file>

aar 配置

aar 文件配置

aar 和 jar 类似,但是他包含了所有资源,class 以及 res 资源文件,aar 和 gradle 通常放在 libs 下

gradle 配置

gradle 中要在 repositories 中配置 flatDir,dependencies 中配置 compile 的 aar 包

1
2
3
4
5
6
7
8
9
10
11
12
repositories{
jcenter()
flatDir{
dirs 'libs'
}
}

dependencies {
compile 'com.android.support:appcompat-v7:23.1.0'
compile(name:'ijkplayer-java-debug', ext:'aar')
compile(name:'giraffeplayer-debug', ext:'aar')
}
aar plugin.xml
1
2
3
4
5
 <platform name="android">
<framework src="src/android/libs/giraffeplayer-build.gradle" custom="true" type="gradleReference" />
<resource-file src="src/android/libs/giraffeplayer-debug.aar" target="libs/giraffeplayer-debug.aar" />
<resource-file src="src/android/libs/ijkplayer-java-debug.aar" target="libs/ijkplayer-java-debug.aar" />
</platform>

静态资源和 java 文件

静态资源路径

静态资源规定放在 src/android/res 目录下

静态资源输出目录配置 通过 resource-file 和 source-file 配置当前路径和输出路径,静态资源默认路径是 src/android/res/xxx, 输出路径是 res/xxx

1
2
3
4
5
6
7
8
9
10
11
12
13
<platform name="android">
<source-file src="src/android/res/drawable-hdpi/jpush_richpush_btn_selector.xml" target-dir="res/drawable" />
<source-file src="src/android/res/drawable-hdpi/jpush_richpush_progressbar.xml" target-dir="res/drawable" />

<source-file src="src/android/res/drawable-hdpi/jpush_ic_richpush_actionbar_back.png" target-dir="res/drawable-hdpi" />
<source-file src="src/android/res/drawable-hdpi/jpush_ic_richpush_actionbar_divider.png" target-dir="res/drawable-hdpi" />

<source-file src="src/android/res/layout/jpush_popwin_layout.xml" target-dir="res/layout" />
<source-file src="src/android/res/layout/jpush_webview_layout.xml" target-dir="res/layout" />
<source-file src="src/android/res/layout/test_notification_layout.xml" target-dir="res/layout" />

<source-file src="src/android/res/values/jpush_style.xml" target-dir="res/values" />
</platform>

java 文件路径

java 文件通常放在 src/android 目录下

java 文件输出目录配置 默认路径是 src/android/Xxx.java 输出目录是 src/包/名/字/,注意 target-dir 是目录路径,target 才是文件路径

1
2
3
4
<platform name="android">
<source-file src="src/android/MyReceiver.java" target-dir="src/cn/jpush/phonegap" />
<source-file src="src/android/JPushPlugin.java" target-dir="src/cn/jpush/phonegap" />
</platform>

meta-data

拿一个分享插件举例做示范

plugin.xml:配置 preference,用于接收用户传参 variable 的值,config-file 中配置 meta-data, 保存参数键值信息用于给 java 类调用。

1
2
3
4
5
6
7
8
9
10
11
<preference name="WEIXIN_APP_ID" />

<platform name="android">
<preference name="WEIXIN_APP_ID" value="WEIXIN_APP_ID" />

<!-- 友盟 AK -->
<meta-data
android:name="WEIXIN_APP_ID"
android:value="WEIXIN_APP_ID" >
</meta-data>
</platform>

权限

当需要使用系统的某个功能时,一定要加上权限询问配置,所有的权限在这查看

1
2
3
4
5
6
7
8
9
10
11
<platform name="android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
</platform>

l 开发进阶之插件实现

开启活动

简单来说,活动相当于 angular 的 page,vue 的 vue,是一个可包含组件(fragment)的 ui 页面。

开启普通活动

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
// 应用上下文
Context context = cordova.getActivity().getApplicationContext();
String pkgName = context.getPackageName();

// 打开app应用
Intent intent = context
.getPackageManager()
.getLaunchIntentForPackage(pkgName);

// 打开XxxActivity
// Intent intent=new Intent(cordova.getActivity(), XxxActivity.class);

// 打开应用必须要加 CATEGORY_LAUNCHER
intent.addCategory(Intent.CATEGORY_LAUNCHER);

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);

// 启动应用
context.startActivity(intent);

// 启动活动
// cordova.getActivity().startActivity(intent);

// 启动有返回值的活动
// cordova.startActivityForResult((CordovaPlugin) this, intent, 0);

本插件中开启活动代码

1
2
3
4
5
6
7
8
9
10
11
public class ThsToast extends CordovaPlugin {
private static final String TAG = "ThsToast";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "start execute!");

// 启动活动
Intent intent = new Intent(cordova.getActivity(), MyActivity.class);
cordova.getActivity().startActivity(intent);
}
}

启动活动演示:

打开第三方 Android SDK 活动 如:百度地图 uri

1
2
3
4
5
6
7
8
// 如果有百度地图 uri详情:http://lbsyun.baidu.com/index.php?title=uri/api/android
Intent intent = Intent.parseUri("intent://map/direction?"
+ "origin="+options.getOrigin4Baidu()
+ "&destination="+options.getDestination4Baidu()
+ "&mode="+options.getModel4Baidu()
+ "&coord_type=wgs84&referer=Autohome|GasStation#Intent;scheme=bdapp;package=com.baidu.BaiduMap;end",0);

cordova.getActivity().startActivity(intent);

使用广播

广播其实就是一个在 app 范围内的事件推送和接受中心,类似于 iframe 的 postMessage。

在 cordova 项目中使用和原生广播没有区别,一般静态广播用在插件比较多,如果是动态广播,则需要在 activity 中通过 registerReceiver 注册

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ThsToast extends CordovaPlugin {
private static final String TAG = "ThsToast";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "start execute!");

// 动态注册广播:
MyBroadcastReceiver myReceiver = new MyBroadcastReceiver();
IntentFilter itFilter = new IntentFilter();
itFilter.addAction("android.intent.action.AIRPLANE_MODE");
cordova.getContext().registerReceiver(myReceiver, itFilter);
}
}

android/MyBroadcastReceiver.java

1
2
3
4
5
6
7
8
9
10
11
public class MyBroadcastReceiver extends BroadcastReceiver {
private String ACTION_BOOT = "android.intent.action.AIRPLANE_MODE";

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_BOOT.equals(action)) {
Toast.makeText(context, "切换飞行模式!", Toast.LENGTH_SHORT).show();
}
}
}

广播效果演示:

使用服务

服务可以同步或执行一些小任务、小进程,甚至对其他进程的 ui 页面做操作,可类比 angular 的 service 服务。

和原生 Service 一样的用法

1
2
3
4
5
6
7
8
9
10
public class ThsToast extends CordovaPlugin {
private static final String TAG = "ThsToast";
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
Log.d(TAG, "start execute!");

// 启动服务
cordova.getContext().startService(new Intent(cordova.getActivity(), MyService.class));
}
}

使用内容提供者

内容提供者其实是一个手机系统范围的 API 调度中心,比如可以读取和修改通讯录和相册的内容。

使用 jar+aar+so

使用 jar

jar 包可以理解为 api 的集合,解压后全部是编译好的 class,但是不包含资源文件,可以直接使用使用其中的类。

jar 包路径通常位于 libs 目录下

查看依赖的 api 源文件

使用 so

通常在 jar 中使用

使用 aar

例如跳转 activity

使用 meta-data

保存用户添加插件时传入的参数键值对象

meta-data 在 java 中获取参数对象

Plugin.java:通过 cordova.getActivity().getPackageManager().getApplicationInfo(cordova.getActivity().getPackageName(), PackageManager.GET_META_DATA)获取参数存储的对象 appInfo,再通过 appInfo.metaData.getType(key)取得参数 value,getType 有 getString、getInt……

meta-data 如何传参

传入插件参数有两种办法,

1.用 cordova 安装时利用–variable key=value 传入

1
cordova plugin add cordova-plugin-share --variable WEIXIN_APP_ID=xxx --variable WEIXIN_APP_SECRET=xxx

2.添加插件完成后,在项目的 config.xml 中手动添加,cordova build 后生效

1
2
3
4
<plugin name="cordova-plugin-share" spec="1.0.0">
<variable name="WEIXIN_APP_ID" value="xxx" />
<!--...-->
</plugin>

使用权限

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
import android.Manifest;

public static final String WRITE = Manifest.permission.WRITE_EXTERNAL_STORAGE;
public static final String READ = Manifest.permission.READ_EXTERNAL_STORAGE;
public static final int REQ_CODE = 0;

@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("show")) {
this.args = args;
this.callbackContext = callbackContext;

if (cordova.hasPermission(READ) && cordova.hasPermission(WRITE)) {
// 有权限则跳转活动
this.launchActivity();
} else {
// 若没有权限则请求
this.getPermission();
}
return true;
}
return false;
}

/**
* 请求权限
**/
protected void getPermission() {
cordova.requestPermissions(this, REQ_CODE, new String[]{WRITE, READ});
}

@Override
public void onRequestPermissionResult(int requestCode, String[] permissions,
int[] grantResults) throws JSONException {
for (int r : grantResults) {
if (r == PackageManager.PERMISSION_DENIED) {
this.callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, PERMISSION_DENIED_ERROR));
return;
}
}

switch (requestCode) {
case REQ_CODE:
launchActivity();
break;
}
}

使用生命周期和常用钩子

excute:执行插件方法

调用插件执行方法。第一个参数 action 是调用的方法名,第二个参数 args 是传入的参数数组,第三个参数 CallbackContext 是传入的回调函数上下文,可以通过 callbackContext.success(message)和 callbackContext.error(errorMessage)传入回调参数;

1
2
3
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
}

initialize:初始化

在插件构造函数执行和字段初始化之后调用,此时尚未执行 excute 方法。

1
2
@Override
public void initialize (CordovaInterface cordova, CordovaWebView webView) {}

pluginInitialize:无参初始化

在插件构造函数执行和字段初始化之后调用,没有参数,此时尚未执行 excute 方法,pluginInitialize 不支持 cordova 3.0-3.5 。

1
2
@Override
protected void pluginInitialize() {}

插件初始化到执行的过程执行顺序如下,依次是 initialize、pluginInitialize、execute:

1
2
3
2020-09-03 00:20:34.928 19307-19435/com.ths.exam.lilin D/ThsToast: start initialize!
2020-09-03 00:20:34.928 19307-19435/com.ths.exam.lilin D/ThsToast: start pluginInitialize!
2020-09-03 00:20:34.929 19307-19435/com.ths.exam.lilin D/ThsToast: start execute!

onStart:活动开始周期

活动正在被启动,已经可见,但是还没位于前台。

1
2
@Override
public void onStart() {}

onResume:活动恢复周期

活动位于前台,并且可以与用户交互了。

1
2
3
4
5
6
7
8
9
10
/**
* 当活动将开始与用户互动时调用。
*
* @param multitasking 表示是否为应用程序打开了多任务
*/
@Override
public void onResume(boolean multitasking) {
super.onResume(multitasking);
// deviceready();
}

onPause:活动暂停周期

活动处于正在停止的状态,通常当要离开这活动的时候会被调用。接下去 onStop()马上会被调用,如果是弹出一个对话框,那么 onStop 不会被调用。

1
2
3
4
5
6
7
8
9
/**
* 在系统即将开始恢复上一个活动时调用
*
* @param multitasking 表示是否为应用程序打开了多任务
*/
@Override
public void onPause(boolean multitasking) {
super.onPause(multitasking);
}

onStop:活动停止周期

活动即将停止,活动完全不可见。

1
2
3
4
5
6
7
/**
* 活动停止前调用
*/
@Override
public void onStop() {
super.onStop();
}

onReset:活动重置周期

这个方法表示活动正在重新启动,活动由停止状态恢复为运行状态,通常由上一个活动返回到这个活动时,这个活动会调用此方法。

1
2
3
4
5
/**
* 当视图导航时调用
*/
@Override
public void onReset() {}

onActivityResult:返回活动数据

当从另一个活动返回到当前活动时,当前活动中的 onActivityResult 可接收刚才活动的返回数据。

1
2
3
4
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
}

onDestroy:活动销毁周期

活动完全销毁前调用,可以在这做一些资源释放的操作。

1
2
3
4
5
6
7
/**
* 活动销毁前调用
*/
@Override
public void onDestroy() {
// deviceready = false;
}

l 总结

本文从创建一个简单的 cordova 自定义插件,到四大组件的配置和使用,以及如何导入和使用 jar、aar、so、静态资源方面做了详细讲解,最后归纳了常用的生命周期和回调方法。
看到这,我们应该已经具备独立创建和改写插件的基本能力,剩下的就只有自己多看多用 cordova-plugin,实践出真知,若有总结不到位或者遗漏的地方,还请各位朋友多多指出,共同交流完善!如果这篇文章对前端或其他方向的你有所帮助或者启发,记得点个赞哦亲:)

l 下载文中演示的插件

1
cordova plugin add cordova-plugin-ths-toast
作者

李霖

发布于

2020-10-14

更新于

2023-08-05

许可协议

CC BY-NC-SA 4.0

评论