安卓cordove插件开发指导(android plugin development guide)

本文翻译自一下链接原文http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html#android-permissions

 

安卓cordove插件开发指导

这个部分介绍了怎样在安卓平台下开发cordova本地插件。在看此篇文章之前,应该先看 PluginDevelopment Guide。来获取一个整体的有关插件结构和JavaScript接口的节本了解。这篇文件接着上篇继续讲解回声示例。从cordova的webview传出字符串并传递回来的这个例子。同时可以参看代码里的注释。CordovaPlugin.java.

安卓插件都是基于cordova-android。是从android webview通过本地桥建立而成的。一个本地安卓插件是由至少一个继承自CordovaPluginjava class。和至少实现累里面一个方法而构成。

插件类的映射

javaScript插件用到的接口使用了cordova.exec方法。如下:

exec(<successFunction>,<failFunction>,<service>, <action>,[<args>

这个函数会从webview发出一个到安卓本地的请求。有效的访问了service类里的action方法。并且附加的参数通过args数组传递。

无论你通过java文件或jar文件来开发插件。这个插件必须在安卓应用程序的res/xml/config.xml文件里说明。有关应用插件怎样使用plugin.xml文件来注入这个feature元素请看应用插件的详细信息。

<featurename="<service_name>">
    <paramname="android-package"value="<full_name_including_namespace>"/>
</feature>

Service的名字对应到javaScript的里exec调用的函数。它的值是java类里面实时在在存在的类名。否则,插件可以编译,但是cordova无法调用。

插件初始化以及生命周期

每个插件的生命周期都伴随着其对应的webview的生命周期。插件不会实例化直到它们被javascript第一次调用。除非config.xml文件里的<param>的onload名字属性为“true”。例如:

<featurename="Echo">
    <paramname="android-package"value="<full_name_including_namespace>"/>
    <paramname="onload"value="true"/>
</feature>

插件应该使用initialize方法启动自己的逻辑部分

@Override
publicvoidinitialize(CordovaInterfacecordova,CordovaWebViewwebView){
    super.initialize(cordova,webView);
    // your init code here
}

插件还应该获取Android应用的生命周期事件并且可以通过扩展重写来获取其事件,包括(onResumeonDestroy,等)。对于要求长时间运行的插件,例如acrivity背后运行的媒体播放功能。监听者,或者是内部状态都应该实现onReset()方法。这个函数会在webview切换一个新页面或者是刷新等重新加载javascript的时候会被调用。

编写一个安卓插件

JavaScript发起对本地插件的调用。并且需要保证在config.xml里映射正确。但是最终安卓的java代码是怎样的呢?无论传递到插件里的执行函数是怎样的,大多数函数实现是像如下的样子:

@Override
publicbooleanexecute(Stringaction,JSONArrayargs,CallbackContextcallbackContext)throwsJSONException{
    if("beep".equals(action)){
        this.beep(args.getLong(0));
        callbackContext.success();
        returntrue;
    }
    returnfalse;  // Returning false results in a "MethodNotFound" error.
}

Javascript通过exec函数的action参数来调用相应的类私有成员函数。并且加上可选的参数。

当发生例外或者错误,为了清除起见把错误的java类的错误名称尽可能的详细返回给JavaScript是很重要的,

线程

JavaScript插件并不是在webview的主线程里面运行。反而是运行在webcore的线程里。就和ececute函数一样。如果你需要和用户界面交互,你应该用 Activity's runOnUiThread来调用:就像如下方法:

@Override
publicbooleanexecute(Stringaction,JSONArrayargs,finalCallbackContextcallbackContext)throwsJSONException{
    if("beep".equals(action)){
        finallongduration=args.getLong(0);
        cordova.getActivity().runOnUiThread(newRunnable(){
            publicvoidrun(){
                ...
                callbackContext.success();// Thread-safe.
            }
        });
        returntrue;
    }
    returnfalse;
}

如果你不需要运行在ui线程里,但是也不希望阻挡webcore的线程。你需要用 ExecutorService 执行你的函数。通过cordova.getThreadPool()来获取这个服务。

@Override
publicbooleanexecute(Stringaction,JSONArrayargs,finalCallbackContextcallbackContext)throwsJSONException{
    if("beep".equals(action)){
        finallongduration=args.getLong(0);
        cordova.getThreadPool().execute(newRunnable(){
            publicvoidrun(){
                ...
                callbackContext.success();// Thread-safe.
            }
        });
        returntrue;
    }
    returnfalse;
}

增加依赖库

如果你的安卓插件有依赖库,这些库必须列在plugin.xml文件里,有两种方法:

推荐的方法是使用<framework/>标签。(从 PluginSpecification了解详细情况)使用这种方法指定库可以允许库通过gradle来管理。参考: DependencyManagement logic。一般使用的普通库有,gson,android-support-v4,google-play-services,这些库可以被多个插件调用而不发生冲突。

第二个选项是使用<lib-file/>标签来指定jar文件的路径。(详细查看: PluginSpecification )。使用这个方法的条件是你使用的这个库没有其它插件使用。(例如这个库是仅给你的插件使用的)。否则你是在冒风险。如果其另一个插件也用了你使用的插件。这样的情况对于只了解cordova调用者会造成很大的困惑和沮丧。

回声安卓插件例子

为了配合在JavaScript的回声接口,我们使用plugin.xml文件来注入一个特定的feature。来定位平台下的config.xml文件

<platformname="android">
    <config-filetarget="config.xml"parent="/*">
        <featurename="Echo">
            <paramname="android-package"value="org.apache.cordova.plugin.Echo"/>
        </feature>
    </config-file>
    <source-filesrc="src/android/Echo.java"target-dir="src/org/apache/cordova/plugin"/>
</platform>

然后增加如下到 src/android/Echo.java文件:

packageorg.apache.cordova.plugin;

importorg.apache.cordova.CordovaPlugin;
importorg.apache.cordova.CallbackContext;

importorg.json.JSONArray;
importorg.json.JSONException;
importorg.json.JSONObject;

/**
* This class echoes a string called from JavaScript.
*/
publicclassEchoextendsCordovaPlugin{

@Override
publicbooleanexecute(Stringaction,JSONArrayargs,CallbackContextcallbackContext)throwsJSONException{
    if(action.equals("echo")){
        Stringmessage=args.getString(0);
        this.echo(message,callbackContext);
        returntrue;
    }
    returnfalse;
}

privatevoidecho(Stringmessage,CallbackContextcallbackContext){
    if(message!=null&&message.length()>0){
        callbackContext.success(message);
    }else{
        callbackContext.error("Expected one non-empty string argument.");
    }
}
}

CordovaPlugin导入所有依赖的库是非常必要的。这些execute()方法 是重写了接收消息的函数。execute()首先测试action的值。对于当前的例子只有一个有效的值echo。其他任何的值都返回false和INVALID_ACTION错误结果。对于javascript方面则是体现为一个错误回调函数。

接下来函数通过对象的getString函数处理args来检索echo字符串。指定第一个参数传递到函数方法。当参数被传递到私有的echo函数。函数检查这个参数是不是为空或者是一个空的字符串。如果是则调用javascript的allbackContext.error()函数,如果多种测试都通过了,那么就调用成功的回调函数callbackContext.success()。Message字符串就会被传递回JavaScript的成功回调函数。

与Android集成

安卓有intent机制来使进程之间互相通信。相对的,插件可以访问CordovaInterface对象。因此就可以访问安卓应用的activity。这是通过调用上下文来启动一个新的android intent。CordovaInterface可以允许插件启动一个activity并接收返回参数。当intent返回到应用的时候,可设定插件的回调函数。

对于cordova2.0来说,插件不可以直接访问context。以及ctx成员,因为ctx成员都是在context之下。因此getContext() 和 getActivity()都可以返回需要的对象。

安卓的权限

安卓的运行程序权限现在是从安装时确定的而不再是在运行是确定。所需要的权限需要在应用程序里声明。这些权限需求需要写在安卓的manifest文件里。可以通过config.xml注入的方式把权限增加到AndroidManifest.xml文件里。以下示例显示了增加通讯录的权限。

<config-filetarget="AndroidManifest.xml"parent="/*">
    <uses-permissionandroid:name="android.permission.READ_CONTACTS"/>
</config-file>

运行时权限(cordove-android5.0.0+)

安卓6.0引入了一个新的权限模型。用户可以根据需要随时打开或关闭权限。这就意味着应用程序必须相应这些权限的改变。这是cordova-android5.0.0主要个更新部分。

需要用户在运行是处理权限的变化部分,可以在安卓开发文档里找到,地址:here

只要一个插件需要一定的权限,就可以调用权限处理函数来申请所需要的权限。格式如下:

cordova.requestPermission(CordovaPluginplugin,intrequestCode,Stringpermission);

为了减少冗长,使用本地静态变量来赋值是一个很好的实践经验。

publicstaticfinalStringREAD=Manifest.permission.READ_CONTACTS;

而且有标准的定义requestcode的方法:

publicstaticfinalintSEARCH_REQ_CODE=0;

 

然后在,exec方法里应该这样检查权限:

if(cordova.hasPermission(READ))
{
    search(executeArgs);
}
else
{
    getReadPermission(SEARCH_REQ_CODE);
}

在这个例子中,我们知识调用requestPermission

protectedvoidgetReadPermission(intrequestCode)
{
    cordova.requestPermission(this,requestCode,READ);
}

这将会触发一个提示对话框问用户是否开启权限。一旦用户给了权限返回结果必须处理onRequestPermissionResult方法。这个方法是每个插件必须重写的。可以看下面这个例子:

publicvoidonRequestPermissionResult(intrequestCode,String[]permissions,
                                         int[]grantResults)throwsJSONException
{
    for(intr:grantResults)
    {
        if(r==PackageManager.PERMISSION_DENIED)
        {
            this.callbackContext.sendPluginResult(newPluginResult(PluginResult.Status.ERROR,PERMISSION_DENIED_ERROR));
            return;
        }
    }
    switch(requestCode)
    {
        caseSEARCH_REQ_CODE:
            search(executeArgs);
            break;
        caseSAVE_REQ_CODE:
            save(executeArgs);
            break;
        caseREMOVE_REQ_CODE:
            remove(executeArgs);
            break;
    }
}

上面这个switch表达式是从提示对话框里返回的结果,其中requestcode是返回的结果。应该调用这个方法。需要注意如果没有正确处理接收onRequestPermissionResult返回结果,将会导致权限提示对话框堆积。这个情况应该避免。

除了可以单独请求全权限,还可以通过定义数组来用组的方式来申请权限。权限组根据插件需要的权限定义。

String[]permissions={Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION};

当需要请求权限的时候可以使用如下方法:

cordova.requestPermissions(this,0,permissions);

这个将会申请权限组里面的所有权限。对外提供一个可以公共访问的权限数组,这样的方式对与把你的插件做为依赖项的时候是很好的想法,当然这也不是必须的。

调试插件

安卓应用程序可以使用eclipse或Android studio来调试。当然更推荐使用Android studio 因为cordova-android作为一个库存在在项目里。而且插件是有源代码组成。因此可以在cordova应用程序里调试java代码,就像在本地安卓里一样的方法调试。

调用其他activity

当你的插件调用了一个acrivity并且使得cordova的activity放在后台,你就需要做特殊的考虑。,因为安卓系统在内存低的时候会会销毁放在后台的程序。在这种情况下,CordovaPlugin的实例也会被销毁。如果你的插件正在等待启动的activity返回结果的状态。当cordova acrivity切换到前面的时候,插件会被重新建立,并且收到Androidactivity返回的结果。插件的状态将不会被保存或重置。插件的CallbackContext 也会丢失。你的CordovaPlugin 实现这里的俩个方法可以处理好这样情况。

/**
 * Called when the Activity is being destroyed (e.g. if a plugin calls out to an
 * external Activity and the OS kills the CordovaActivity in the background).
 * The plugin should save its state in this method only if it is awaiting the
 * result of an external Activity and needs to preserve some information so as
 * to handle that result; onRestoreStateForActivityResult() will only be called
 * if the plugin is the recipient of an Activity result
 *
 * @return  Bundle containing the state of the plugin or null if state does not
 *          need to be saved
 */
publicBundleonSaveInstanceState(){}
 
/**
 * Called when a plugin is the recipient of an Activity result after the
 * CordovaActivity has been destroyed. The Bundle will be the same as the one
 * the plugin returned in onSaveInstanceState()
 *
 * @param state             Bundle containing the state of the plugin
 * @param callbackContext   Replacement Context to return the plugin result to
 */
publicvoidonRestoreStateForActivityResult(Bundlestate,CallbackContextcallbackContext){}

你应该注意以上者两个方法只有你需要启动Android本地actiity的时候才有必要。插件的状态不会被重置。除非你的插件被通过CordovaInterface的 startActivityForResult()方法调用,并且cordova activity放在后台的时候被安卓系统销毁。

作为onRestoreStateForActivityResult()一部分。你的插件将会被传递一个复制的CallbackContext。,你必须要认识到这个CallbackContext不是原来被activity销毁的那个CallbackContext。原来的那个已经不存在了。并且不可能被JavaScript程序继续调用了。相反,当应用程序resume的时候,这个替代的CallbackContext将会返回结果作为部分的resume事件。有效的resume事件包含了一下内容。

{
    action: "resume",
    pendingResult: {
       pluginServiceName: string,
       pluginStatus: string,
       result: any
    }
}

pluginServiceName 将会对应到plugin.xml的name元素

pluginStatus是一个形容pluginresult传递到callbackcontext的状态字符串,。从pluginResult.java来看插件的状态字符串值。

Result plugin传递给callbackContext的任意结果。(例如:字符串,数字,json对象,等)

Resume内容将会被传递到任何在javascript应用程序注册过的resume事件回调函数。这就意味着结果会被直接传递到cordova的应用程序里。你的插件在应用程序接收之前将没有机会来处理结果。因此,你应该尽量让本地代码来尽可能完整的返回结果。而且在启动一个activity的时候尽量不要使用javascript回调函数。

一定要确认好,cordova应用程序怎样处理从resume事件接收到的结果。Cordova应该维持自己的状态和记忆自己发出了什么请求,和自己提供了什么参数。无论如何,作为插件部分API你应该清楚的知道pluginStatus意味着什么。还有resume里面返回了什么样的数据。

对于启动一个activity的完整事件顺序是如下:

1. Cordova应用程序发起一个对你插件的调用。

2. 你的插件发起一个acrivity来获取一个结果。

3. 安卓系统销毁ordova acrivity,并且你的插件实例的onSaveInstanceState()函数被调用。

4. 用户与弹出activity交互,然后acrivity结束。

5.Cordova activity 被重新创建,并且收到返回结果。onRestoreStateForActivityResult()被调用。

6. onActivityResult()被调用,然后你的插件传递结果到新的CallbackContext。

7. Cordova应用程序触发resume事件,并且接收结果。

安卓提供了一个低内存acrivity销毁测试选项。在安卓设备或模拟器的开发者选项里开启“不保留acrivity”选项。来模拟低内存状态。如果你的插件启动内部activity,你就应该开启这个选项来测试你的应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值