2016-12-05 32 views
4

我們中的許多人會經歷類似的問題,但即使經過以下最相關的鏈接reference link1reference link2,我無法解決。爲ionic2項目創建自定義cordova插件

問題:

Create a custom plugin (Cordova) in-order to use this in ionic2 project.

期望:這個插件將可以與IOS和Android原生功能進行交互。準確地說,我試圖使用cordova將原生SDK(Aruba內部定位SDK)的功能應用到Ionic項目中。

步驟1最初插件創建爲每reference link 1

步驟2創建離子2項目(與this基本步驟中創建)

步驟3的JavaScript在插件文件無法指並訪問Ionic2。

谷歌搜索後,我發現this discussion,它被告知在插件本身創建接口,因爲以下原因。

import {myPluginName} from '../../../plugins/xxx/*.js'

Will not work because the plugin is not part of the ionic native bundle.

If you have a custom plugin, you can do a few things.

1) Make a PR to add it to ionic-native proper

2) Use the raw plugin API. You can use the raw plugin API without having it be part of Ionic Native. The plugin is on the window object, so you would target the api normally

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", []); 
}; 

現在我卡住了,因爲我的離子項目沒有打字文件夾本身。

在示例Github Project中,cordova包使用類型文件夾引用。TypeScript File in project使用用來指index.t.js

進口應該像

declare var cordova: Cordova; 

疑惑指Cordova

  1. 我是在這個過程中
  2. 的賴特方向這是方法創建Cordova插件並在離子中使用
  3. 爲什麼我不能夠得到typings文件夾中Ionic2

編輯1:

只是添加,甚至沒有在離子項目指的插件後,我試圖在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 

回答

9

經過多次試驗和錯誤,我找到了解決方案。

我將下面的細節放在未來的任何人嘗試類似的東西的參考!

與代碼的問題如下(我的插件名稱爲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. 如果需要
  6. 現在驗證Inject.js其缺少必要的方法調用中plugin.xml包括權限面臨編譯/運行時間的問題。目前它只有以下代碼。

var exec = require('cordova/exec'); 
exports.coolMethod = function(arg0, success, error) { 
exec(success, error, "Inject", "coolMethod", [arg0]); 
}; 
  • 但是,我們需要包括以下也使其安裝和工作

  • function Inject(){ 
    } 
    Inject.install = function() { 
        if (!window.plugins) { 
        window.plugins = {}; 
        } 
    
        window.plugins.Inject = new Inject(); 
        return window.plugins.Inject; 
    }; 
    
    cordova.addConstructor(Inject.install); 
    
  • 例如,我的目標是顯示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); 
    
  • 現在讓我們實現我們的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."); 
         } 
        } 
    } 
    
  • 只是爲了讓這清楚我把我的最後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> 
    
  • 現在創建一個樣品Ionic2項目,並添加的Android/IOS平臺
  • 使用cordova plugin add命令
  • 導航所創建的插件添加到在的NodeJS離子項目命令提示符併發出此命令(請參閱add後的插件文件夾庫)cordova plugin add D:\PluginTrial\Inject
  • 添加的插件應填充在Ionic2Project\plugins文件夾下
  • 使用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); 
         }); 
        } 
    } 
    
  • 現在參閱在home.html文件此showToast方法

  • <button ion-button (click)="showToast('Yo Man! Its working ', 'center')">Default</button> 
    
  • 就是這樣。你應該能夠成功測試插件!快樂編碼
  • 參考: Reference OneReference TwoReference Three

    +0

    尼斯的解釋..我嘗試了同樣的,但得到這個錯誤錯誤:未捕獲(承諾):TypeError:callback.fail.apply不是一個函數TypeError:callback.fail.apply不是在Object的函數。回調從本地(cordova.js:296) –

    +0

    我不太確定這個具體的錯誤。但可能是由於其他一些依賴。我確定如果你按照我解釋的完全相同的步驟,你將能夠實現相同的 – Stallion

    +0

    我已經遵循了確切的步驟..一切都按照解釋添加,但點擊按鈕後只有這個錯誤投擲..我使用離子3 –

    1

    就類型而言,它已不再使用。所有或大部分的打字稿聲明都被轉移到npm本身,並將其安裝爲npm install @types/package_namehttps://www.npmjs.com/~types https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/README.md 如果您需要分型文件夾,你可以嘗試

    npm install typings 
    

    您還可以通過

    // <reference path="" /> 
    

    全球化志願服務青年類型declararions在打字稿

    +0

    雖然試圖用插件運行項目,在推出的應用程序崩潰本身。嘗試在模擬器和設備..任何線索?我刪除了插件並啓動,該應用程序運行良好。 – Stallion

    +0

    是否有任何日誌? –

    +0

    是的,我應該發佈嗎? – Stallion

    3

    你的插件需要看起來像這樣:

    In:/ [自定義插件名稱]/js/custom_plugi n.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/[機器人] || [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

    來回答EDIT 1的問題,Android的部分這裏一些細節: 在Android插件項目(一旦Android平臺已添加並建造了至少一次,離子CLI),在機器人工作室( 2.2.2),查看「[我的項目] \ platforms \ android」下的構建項目時:

    在層次結構中,MainActivity文件自動生成在: 「android \ java \ com \ ionicframework。[my項目名稱+大量] \ 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.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> 
    
    +0

    根據你的建議添加插件後,如果不提及Ionic項目中的任何方法並嘗試在設備中部署導致崩潰。我已經添加了日誌,你可以請檢查 – Stallion

    +0

    我已經提交了更多有關如何在Android端看起來像插件的輸入。我不知道爲什麼MainActivity啓動時會出現nullpointerexception,正如我的答案更新中所述,MainActivity是自動生成的。 – nyluje

    +0

    感謝您的支持。讓我試一試您提供的所有輸入內容。儘管Android部分在某方面很有幫助,但我希望在Ionic2中使用它。 – Stallion

    相關問題