为ionic2项目创建自定义cordova插件

时间:2016-12-05 13:05:28

标签: javascript android cordova plugins ionic2

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

问题:

  

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

期望:此插件可以与IOS和Android的原生功能进行互动。确切地说,我正在尝试使用cordova访问Ionic项目中的原生SDK(Aruba内部定位SDK)的功能。

第1步根据reference link 1

初始创建的插件

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

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

在谷歌搜索之后,我找到this discussion,由于以下原因,它被告知在插件中创建界面。

  

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

     

无效,因为插件不是离子本机包的一部分。

     

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

     

1)制作PR以将其添加到离子本地

     

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. 中使用的方法
  4. 为什么我无法在Ionic2中获取typings文件夹
  5. 编辑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
    

2 个答案:

答案 0 :(得分:10)

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

我正在详细说明以下细节,以便将来参考尝试类似内容的任何人!

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

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

<强>步骤:

  1. 使用plugman创建插件Reference Link
  2. 的骨架
  3. 请使用cordova-plugin-am-i-late,而不是使用cordova.plugin.Inject等名称。我使用-符号
  4. 面临编译/运行时问题
  5. 添加目标平台plugman platform add --platform_name android
  6. 验证plugin.xml与the same reference
  7. 的比较
  8. 如果需要,在plugin.xml中包含权限
  9. 现在验证Inject.js缺少必要的方法调用。目前它只有以下代码。
  10. var exec = require('cordova/exec');
    exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "Inject", "coolMethod", [arg0]);
    };
    
    1. 但我们还需要包含以下内容以使其安装和工作
    2. 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文件
      2. 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
        2. 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
          2. <?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. 添加创建的插件
            4. 在Nodejs命令提示符中导航到ionic项目并提供此命令(请参阅add之后的插件文件夹基础)cordova plugin add D:\PluginTrial\Inject
            5. 添加的插件应填入Ionic2Project\plugins文件夹
            6. 使用window对象调用此函数。以下是我的home.ts
            7. 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 file
              2. 中引用此showToast方法
                <button ion-button (click)="showToast('Yo Man! Its working ', 'center')">Default</button>
                
                1. 那就是它。您应该能够成功测试插件!快乐编码
                2. 参考: Reference OneReference TwoReference Three

答案 1 :(得分:3)

您的插件需要如下所示:

在:/ [自定义插件名称] /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;。然后在该课程中,您可以参考使用CustomPlugin导出的module.exports = CustomPlugin;来使用您的插件  来自文件:/[custom plugin name]/js/custom_plugin.js

回答编辑问题1,这里有一些ANDROID PART的详细信息: 在Android插件项目中(曾经使用离子CLI添加和构建至少一次平台android),在android studio(2.2.2)中查看&#34; [我的项目] \平台下的构建项目机器人&#34;:

在层次结构中,MainActivity文件在以下位置自动生成: &#34; android \ java \ com \ ionicframework。[我的项目名称+一个大数字] \ MainActivity&#34;:

  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);
    }
}

我的自定义插件(此处不再详述)&#34; 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>