【问题标题】:Custom cordova plugin creation for ionic2 projectionic2项目的自定义cordova插件创建
【发布时间】:2017-04-19 21:17:33
【问题描述】:

我们中的许多人都会遇到类似的问题,但即使在关注最相关的链接 reference link1reference link2 之后,我也无法解决。

问题:

创建一个自定义插件 (Cordova) 以便在 ionic2 中使用它 项目。

期待:这个插件将能够与IOS和Android的原生功能交互。准确地说,我正在尝试使用 cordova 将原生 SDK(Aruba 内部定位 SDK)的功能访问到 Ionic 项目中。

第一步最初根据reference link 1创建插件

第 2 步 创建 Ionic 2 项目(使用 this 基本步骤创建)

第 3 步插件中的 JavaScript 文件无法在 Ionic2 中引用和访问。

谷歌搜索后,我找到了this discussion,它被告知在插件本身中创建接口,原因如下。

从 '../../../plugins/xxx/*.js' 导入 {myPluginName}

无法工作,因为该插件不是 ionic 本机捆绑包的一部分。

如果你有一个自定义插件,你可以做一些事情。

1) 做一个 PR 把它添加到 ionic-native 中

2) 使用原始插件 API。 您可以使用原始插件 API,而无需将其作为 Ionic Native 的一部分。 该插件位于窗口对象上,因此您可以正常定位 api

window.plugin.myPlugin.myMethod()

根据GITHUB Example 项目这种方式应该实现接口

interface CordovaPlugins {
  ZPLPrinter: ZPLPrinter;
}

interface ZPLPrinter {

  print(
    ipaddress: string,
    bclabels: any,
    printSuccess: (ip: string, labels: string[]) => void,
    printError: (message: string) => void): void;

}

现在我在我的插件中创建了一个类似的界面,在插件的 www 文件夹中如下所示

interface CordovaPlugins {
  Communicator: Communicator;
}

interface Communicator {

  getInfo(successCallback: any, errorCallback: any);

}

这个接口最好在 JS 文件中定位这个方法

Device.prototype.getInfo = function(successCallback, errorCallback) {
    console.log("device.js: getInfo function called");
    argscheck.checkArgs('fF', 'Device.getInfo', arguments);
    exec(successCallback, errorCallback, "Device", "getDeviceInfo", []);
};

现在我被卡住了,因为我的 Ionic 项目本身没有 typings 文件夹。

在示例Github Project 中,使用 typings 文件夹来引用cordova 包。 TypeScript File in project 使用 index.t.js 引用 Cordova

用于引用的导入应该是这样的

declare var cordova: Cordova;

疑问:

  1. 我是否正朝着正确的方向前进
  2. 这是创建Cordova插件并在离子中使用的方式吗
  3. 为什么我无法在 Ionic2 中获取 typings 文件夹

编辑 1:

在 Ionic 项目中添加插件后,我尝试在 Android 设备上运行。但它给了我以下错误。

主要错误是这样的

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ionicframework.cutepuppypics234138/com.ionicframework.cutepuppypics234138.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void org.apache.cordova.CordovaPlugin.privateInitialize(java.lang.String, org.apache.cordova.CordovaInterface, org.apache.cordova.CordovaWebView, org.apache.cordova.CordovaPreferences)' on a null object reference

为什么会导致此错误?详细的日志在下面给出了

12-08 16:10:49.079 20555-20555/? E/ApkAssets: Error while loading asset assets/natives_blob_64.bin: java.io.FileNotFoundException: assets/natives_blob_64.bin
12-08 16:10:49.079 20555-20555/? E/ApkAssets: Error while loading asset assets/snapshot_blob_64.bin: java.io.FileNotFoundException: assets/snapshot_blob_64.bin
12-08 16:10:49.682 20555-20555/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.ionicframework.cutepuppypics234138, PID: 20555
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.ionicframework.cutepuppypics234138/com.ionicframework.cutepuppypics234138.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void org.apache.cordova.CordovaPlugin.privateInitialize(java.lang.String, org.apache.cordova.CordovaInterface, org.apache.cordova.CordovaWebView, org.apache.cordova.CordovaPreferences)' on a null object reference
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2339)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413)
   at android.app.ActivityThread.access$800(ActivityThread.java:155)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5343)
   at java.lang.reflect.Method.invoke(Native Method)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:700)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void org.apache.cordova.CordovaPlugin.privateInitialize(java.lang.String, org.apache.cordova.CordovaInterface, org.apache.cordova.CordovaWebView, org.apache.cordova.CordovaPreferences)' on a null object reference
   at org.apache.cordova.PluginManager.getPlugin(PluginManager.java:171)
   at org.apache.cordova.PluginManager.startupPlugins(PluginManager.java:97)
   at org.apache.cordova.PluginManager.init(PluginManager.java:86)
   at org.apache.cordova.CordovaWebViewImpl.init(CordovaWebViewImpl.java:115)
   at org.apache.cordova.CordovaActivity.init(CordovaActivity.java:149)
   at org.apache.cordova.CordovaActivity.loadUrl(CordovaActivity.java:224)
   at com.ionicframework.cutepuppypics234138.MainActivity.onCreate(MainActivity.java:39)
   at android.app.Activity.performCreate(Activity.java:6010)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1129)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2292)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2413) 
   at android.app.ActivityThread.access$800(ActivityThread.java:155) 
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1317) 
   at android.os.Handler.dispatchMessage(Handler.java:102) 
   at android.os.Looper.loop(Looper.java:135) 
   at android.app.ActivityThread.main(ActivityThread.java:5343) 
   at java.lang.reflect.Method.invoke(Native Method) 
   at java.lang.reflect.Method.invoke(Method.java:372) 
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:905) 
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:700) 
12-08 16:10:49.879 20656-20656/? E/SubDex: SubDex Config : .dex 2
12-08 16:10:50.285 20656-20656/? E/project: extsdcard==/storage/emulated/0/Android/data/com.cleanmaster.mguard/files
12-08 16:10:50.303 20656-20656/? E/project: extsdcard==/storage/emulated/0/Android/data/com.cleanmaster.mguard/files

【问题讨论】:

    标签: javascript android cordova plugins ionic2


    【解决方案1】:

    经过多次尝试和错误,我找到了解决方案。

    我将以下详细信息记录下来,以供任何正在尝试类似东西的人参考!

    代码问题如下(我的插件名称是Inject

    • Inject\www\Inject.js 没有安装插件的基本功能
    • Inject.js 中提到的任何方法名称都应与Inject\src\Inject.java 相同,因为execute 方法中有选项可以根据收到的标签引用不同的方法名称

    步骤:

    1. 使用plugman创建插件骨架Reference Link
    2. 不要使用 cordova-plugin-am-i-late 之类的名称,而是使用 cordova.plugin.Inject 。我使用 - 符号遇到了编译/运行时问题
    3. 添加目标平台plugman platform add --platform_name android
    4. 验证 plugin.xml 比较 the same reference
    5. 如果需要,请在plugin.xml 中包含权限
    6. 现在验证 Inject.js 是否缺少必要的方法调用。目前它只有以下代码。

    var exec = require('cordova/exec');
    exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "Inject", "coolMethod", [arg0]);
    };
    
    1. 但我们还需要在下面包含以使其安装和工作

    function Inject(){
    }
    Inject.install = function () {
      if (!window.plugins) {
        window.plugins = {};
      }
    
      window.plugins.Inject = new Inject();
      return window.plugins.Inject;
    };
    
    cordova.addConstructor(Inject.install);
    
    1. 例如,我的目标是显示 Toast 消息,下面是我的完整 Inject.js 文件

    function Inject(){
    }
    
    Inject.prototype.coolMethod = function (options, successCallback, errorCallback) {
      cordova.exec(successCallback, errorCallback, "Inject", "coolMethod", []);
    };
    
    Inject.install = function () {
      if (!window.plugins) {
        window.plugins = {};
      }
    
      window.plugins.Inject = new Inject();
      return window.plugins.Inject;
    };
    
    cordova.addConstructor(Inject.install);
    
    1. 现在让我们实现我们的Inject.java

    public class Inject extends CordovaPlugin {
    
    private static final int GRAVITY_CENTER = Gravity.CENTER_VERTICAL|Gravity.CENTER_HORIZONTAL;
    private static final String TAG = "InjectCordovaPlugin";
    String messageReceived;
    
        @Override
        public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
            Log.v(TAG, "execute , action =" + action);
            if (action.equals("coolMethod")) {
                String message = args.getString(0);
                Log.v(TAG, "coolMethod called with message =" + message);
                this.coolMethod(message, callbackContext);
                return true;
            }
    
            return false;
        }
        private void coolMethod(String message, CallbackContext callbackContext) {
        Log.v(TAG, "Inject's coolMethod called ,message="+message);
        messageReceived = message;
            if (message != null && message.length() > 0) {
                cordova.getActivity().runOnUiThread(new Runnable() {
                public void run() {
    
                final android.widget.Toast toast = android.widget.Toast.makeText(
                  cordova.getActivity().getWindow().getContext(),
                  messageReceived,
                  android.widget.Toast.LENGTH_LONG 
                    );
                    toast.setGravity(GRAVITY_CENTER, 0, 0);
                    toast.show();
                }
                });
                callbackContext.success(message);
            } else {
                callbackContext.error("Expected one non-empty string argument.");
            }
        }
    }
    
    1. 为了说明这一点,我要发布我的最终结果plugin.xml

    <?xml version='1.0' encoding='utf-8'?>
    <plugin id="cordova.plugin.Inject" 
        version="1" 
        xmlns="http://apache.org/cordova/ns/plugins/1.0" 
        xmlns:android="http://schemas.android.com/apk/res/android">
    <name>Inject</name>
    <js-module name="Inject" src="www/Inject.js">
        <clobbers target="window.plugins.Inject"/>
    </js-module>
    <platform name="android">
    <config-file parent="/*" target="res/xml/config.xml">
    <feature name="Inject">
    <param name="android-package" 
            value="cordova.plugin.Inject.Inject" />
            </feature>
    </config-file>
    <config-file parent="/*" target="AndroidManifest.xml">
    </config-file>
    <source-file src="src/android/Inject.java" 
    target-dir="src/cordova.plugin.Inject/Inject" />
    </platform>
    </plugin>
    
    1. 现在创建一个示例 Ionic2 项目并添加 Android/IOS 平台
    2. 使用cordova plugin add 命令添加创建的插件
    3. 在 Nodejs 命令提示符中导航到 ionic 项目并给出此命令(参考插件文件夹库在 add 之后)cordova plugin add D:\PluginTrial\Inject
    4. 添加的插件应填充在Ionic2Project\plugins 文件夹下
    5. 使用window 对象调用此函数。下面是我的home.ts

    import { Component } from '@angular/core';
    
    import { NavController, Platform } from 'ionic-angular';
    
    declare var window: any;
    @Component({
      selector: 'page-home',
      templateUrl: 'home.html'
    })
    export class HomePage {
    
      constructor(public navCtrl: NavController, private platform: Platform) {
    
      }
        showToast(message, position) {
            this.platform.ready().then(() => {
                window.plugins.Inject.coolMethod(message, "short", position);
            });
        }
    }
    
    1. 现在在home.html 文件中引用这个 showToast 方法

    <button ion-button (click)="showToast('Yo Man! Its working ', 'center')">Default</button>
    
    1. 就是这样。您应该能够成功测试插件!快乐编码

    参考: Reference One , Reference Two , Reference Three

    【讨论】:

    • 很好的解释.. 我尝试了同样的方法但得到了这个错误错误错误:未捕获(在承诺中):TypeError:callback.fail.apply 不是函数Object.callback From Native (cordova.js:296) 处的函数
    • 我不太确定这个特定的错误。但可能是由于其他一些依赖。我敢肯定,如果您按照我解释的完全相同的步骤进行操作,您将能够实现相同的目标
    • 我已按照确切的步骤进行操作。所有内容均按照说明添加,但单击按钮后仅抛出此错误。我使用的是 Ionic 3
    • 我不确定 inonic3,就像我在 ionic2 中所做的那样。不确定该版本更改是否会在过程中进行任何更改。理想情况下,插件创建不应该!只需尝试深入挖掘以确保您正在执行与上述完全相同的步骤。祝你好运!
    【解决方案2】:

    您的插件需要如下所示:

    在:/[自定义插件名称]/js/custom_plugin.js

    var CustomPlugin = function(){};
    
    CustomPlugin.someFunction = function(){
        console.log("someFunction starts");
    
        return new Promise(function(resolve,reject){
        cordova.exec(
            resolve,
            reject,
            [PLUGIN_NAME],
            [ACTION_ON_NATIVE_SIDE],
            []
        );
    
        });
        console.log("someFunction stops");
    }
    
    .... more functions
    
    module.exports = CustomPlugin;
    

    在:/[自定义插件名称]/src/[android]||[ios] 中,带有本机代码的类。

    并且在:/[自定义插件名称]/plugin.xml(这是一个示例,必须根据您的情况调整设置):

    <?xml version="1.0" encoding="UTF-8"?>
    <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
        id="[CustomPlugin]"
        version="1.0.0">
        <name>CustomPlugin</name>
        <description>...</description>
        <license>...</license>
        <author>...</author>
    
        <engines>
            <engine name="cordova" version=">=6.0.0" />
        </engines>
    
    
    
        <js-module src="www/js/custom_plugin.js" name="CustomPlugin">
            <clobbers target="CustomPlugin" />
        </js-module>
    
    
        <platform name="ios">
            <config-file target="config.xml" parent="/*">
                <preference name="orientation" value="portrait"/>
                <feature name="CustomPlugin">
                    <param name="ios-package" value="CustomPlugin" />
                    <param name="onload" value="true"/>
                </feature>
            </config-file>
    
            <header-file src="src/ios/CustomPlugin.h" />
            <source-file src="src/ios/CustomPlugin.m" />
            <!--framework src="QuartzCore.framework" /> 
            <framework src="AssetsLibrary.framework" />
            <framework src="CoreGraphics.framework" />
            <framework src="MobileCoreServices.framework" /-->
        </platform>
    
        <platform name="android">
            <config-file target="res/xml/config.xml" parent="widget">
                <preference name="orientation" value="portrait"/>
                <feature name="CustomPlugin" >
                    <param name="android-package" value="[package name].CustomPlugin"/>
                    <param name="onload" value="true"/>
                </feature>
            </config-file>
    
            <config-file target="AndroidManifest.xml" parent="/*">
                <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
                <uses-permission android:name="..." />
                <uses-feature android:name="..." />
            </config-file>
    
            <source-file src="src/android/CustomPlugin.java" target-dir="[package folder directory organization like: com.android.something]" />
            <source-file ... />
            <source-file src="src/android/custom_plugin.xml" target-dir="res/layout" />
    
        </platform>
    
    </plugin>
    

    然后您使用 CLI 添加插件:ionic plugin add [folder of your plugin]

    在您的 Ionic 项目中,在您要使用插件的类(angular2 指令)中,在 @Component 部分之前写:declare var CustomPlugin: any;。然后在该类中,您可以通过引用使用module.exports = CustomPlugin; 导出的CustomPlugin 来使用您的插件 来自文件:/[custom plugin name]/js/custom_plugin.js

    要回答问题的编辑 1,这里是 Android 部分的一些详细信息: 在 android 插件项目中(一旦平台 android 已添加并至少构建一次,使用 ionic CLI),在 android studio (2.2.2) 中,查看“[my project]\platforms\android”下的构建项目时:

    在层次结构中,MainActivity 文件在以下位置自动生成: "android\java\com\ionicframework.[我的项目名+一个大号]\MainActivity":

      package com.ionicframework.[my project name + a large number];
    
    import android.os.Bundle;
    import org.apache.cordova.*;
    
    public class MainActivity extends CordovaActivity
    {
        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
    
            // enable Cordova apps to be started in the background
            Bundle extras = getIntent().getExtras();
            if (extras != null && extras.getBoolean("cdvStartInBackground", false)) {
                moveTaskToBack(true);
            }
    
            // Set by <content src="index.html" /> in config.xml
            loadUrl(launchUrl);
        }
    }
    

    对于“android\java[自定义插件包]下的我的自定义插件(这里不再赘述):

    package [package of the custom plugin];
    
    import org.apache.cordova.CallbackContext;
    import org.apache.cordova.CordovaPlugin;
    import org.apache.cordova.PluginResult;
    
    // + other imports needed
    
    public class CustomPlugin extends CordovaPlugin  {
    
        private static CallbackContext customDeferredCallback;
    
        public boolean execute(String action, final JSONArray args, final CallbackContext callbackContext) throws JSONException {
    //all the thing corresponding to your case need to end if with either:
    //   if OK: callbackContext.success(); return true;  ;
    //   if not OK: callbackContext.error(error.getMessage()); return false;
    //   if JAVA returns a result to JS do: actionBindListener(callbackContext);
    
    
    
    }
    
        private boolean actionBindListener(final CallbackContext callbackContext){
        cordova.getThreadPool().execute(new Runnable() {
                public void run(){
                    try{
                        customDeferredCallback= callbackContext;
                    }catch(Exception e){e.printStackTrace();}
                }
            });
            return true;
        }
    
    
        //in your program when you get the result you want to send back to javascript call this function
        public static void sendResultToJS(res){
            JSONObject eventData = new JSONObject();
            try {
                eventData.put("CUSTOM_KEY", res);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, eventData);
            pluginResult.setKeepCallback(true);
            try{
                    customDeferredCallback.sendPluginResult(pluginResult);
            } catch(NullPointerException e){
                e.printStackTrace();
            }
        }
    }
    

    最后 android\manifests\manifests.xml 看起来像这样:

    <?xml version='1.0' encoding='utf-8'?>
    <manifest android:hardwareAccelerated="true" android:versionCode="1" android:versionName="0.0.1" package="com.ionicframework.[project name + large number]" xmlns:android="http://schemas.android.com/apk/res/android">
        <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
        <uses-permission android:name="android.permission.INTERNET" />
        <application android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
            <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
                <intent-filter android:label="@string/launcher_name">
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:exported="true" android:name="com.adobe.phonegap.push.PushHandlerActivity" />
            <receiver android:name="com.adobe.phonegap.push.BackgroundActionButtonHandler" />
            <receiver android:exported="true" android:name="com.google.android.gms.gcm.GcmReceiver" android:permission="com.google.android.c2dm.permission.SEND">
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                    <category android:name="${applicationId}" />
                </intent-filter>
            </receiver>
            <service android:exported="false" android:name="com.adobe.phonegap.push.GCMIntentService">
                <intent-filter>
                    <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                </intent-filter>
            </service>
            <service android:exported="false" android:name="com.adobe.phonegap.push.PushInstanceIDListenerService">
                <intent-filter>
                    <action android:name="com.google.android.gms.iid.InstanceID" />
                </intent-filter>
            </service>
            <service android:exported="false" android:name="com.adobe.phonegap.push.RegistrationIntentService" />
        </application>
        <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="24" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.VIBRATE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
        <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
        <permission android:name="${applicationId}.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    </manifest>
    

    【讨论】:

    • 根据您的建议添加插件后,未引用 Ionic 项目中的任何方法并尝试在设备中部署导致崩溃。我已经添加了日志,你可以检查一下
    • 我已经向您提交了有关插件在 android 端的外观的更多信息。我不知道为什么 MainActivity 启动时会出现空指针异常,正如我的答案更新中所述 MainActivity 是自动生成的。
    • 感谢您的支持.. 让我尝试一下您提供的所有输入。尽管 Android 部分在某个方面很有帮助,但我希望在 Ionic2 中使用它。
    • 如果您仍然遇到问题并希望我进一步查看它,请在 github 上发布您项目的子项目(仅关注插件方面)。
    • 你能检查一下这个插件是否配置正确。 github.com/harry7070/CordovaPlugin 。我试图在示例科尔多瓦中添加这个插件,但它也在那里失败了。可能是插件代码本身的问题
    【解决方案3】:

    就打字而言,它不再使用。所有或大部分 typescript 声明都被移动到 npm 本身,您将它们安装为 npm install @types/package_namehttps://www.npmjs.com/~types https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md 如果您需要打字文件夹,您可以尝试

    npm install typings
    

    你也可以通过

    引用类型声明
    // <reference path="" />
    

    在打字稿中

    【讨论】:

    • 在尝试使用插件运行项目时,应用程序在启动时崩溃。在模拟器和设备中试过..有什么线索吗?我删除插件并启动,应用程序运行良好..
    • 是的,我应该发布它吗?
    • 否则我不能说你的问题:)
    猜你喜欢
    • 1970-01-01
    • 2016-12-04
    • 1970-01-01
    • 2017-08-08
    • 2017-08-08
    • 2016-12-24
    • 2015-10-05
    • 2017-03-07
    • 2020-05-20
    相关资源
    最近更新 更多