Prepare before you start

1. Required development environment

  1. NodeJs : nodejs.cn/
  2. Ionic : ionicframework.com/
  3. Cordova : cordova.apache.org/
  4. Android : developer.android.google.cn/studio/
  5. The Plugman: cordova.apache.org/docs/en/lat…
  6. Ionic Native: github.com/ionic-team/…

2. Pre-requisite skills

  1. JavaScript Syntax Basics
  2. TypeScript syntax basics
  3. Java Syntax Basics
  4. Basic Android SDK usage

Plug-in writing and development practices

Take calling Android native Toast to implement message prompt as an example

Case operation Effect:

1. Create a plug-in package

Create an empty plug-in using the Plugman CLI

A. Go to the directory where the plug-in is stored

B. Run the following commands to create plug-ins and add platforms supported by plug-ins

Create the plug-in

Plugman create --name ToastPlugin --plugin_id com.zijin. ToastPlugin --plugin_version 0.0.1Copy the code

Add the platform you want to support for the plug-in

plugman platform add --platform_name android
Copy the code

Executing the two commands produces the following structure:

Generate the function of each file in the plug-in package:

a. plugin.xml

Plugin.xml defines the file structure when the plug-in finally generates platform code and some Settings for the Android platform.

An example:

<? The XML version = '1.0' encoding = "utf-8"? > <! - id: PluginID --> <plugin id="com.zijin. Toastplugin "version="1.0.0" XMLNS = "http://apache.org/cordova/ns/plugins/1.0" XMLNS: android = "http://schemas.android.com/apk/res/android" > <! -- pluginName: pluginName --> <name>ToastPlugin</name> <! The ⚠️ js-module tag is used to publish how the JS file that defines the plug-in's functionality is invoked on the Web side. The clobbers tag is used to publish the interface defined in the WWW/toastplugin.js file to the window object, So web side can directly through the cordova. Plugins. ToastPlugin object to invoke the exposure method to access the plugin functionality. ⚠ ️ when performing the plugin install command cli will WWW/ToastPlugin. When installing a plug-in copy js to platforms/android/platform_ww/plugins/cordova - toast - the plugin Directory. --> <js-module name="ToastPlugin" src="www/ToastPlugin.js"> <clobbers target="cordova.plugins.ToastPlugin"/> </js-module> <! <platform name=" Android "> <! - the config file - tag will be child tags inside appended to the platforms/android/app/SRC/main/res/XML/config. Under the XML -- -- > < config file parent = "/ *" target="res/xml/config.xml"> <! Exec (success, error, 'ToastPlugin', 'coolMethod', [arg0]); ⚠️ Note: The third parameter must have the same name for this label, otherwise the plug-in will not be found at run time. --> <feature name="ToastPlugin"> <! The param tag defines the full path of the ToastPlugin class in toastplugin.java. The Cordova Framework instantiates ToastPlugin objects based on the class's full path through reflection. ⚠️ Note: The path of the package must be consistent with the target-dir attribute value in source-file. Otherwise, when instantiating an object through reflection, an exception will occur that the class cannot find. ⚠️ <param name="onload" value="true" /> <param name="onload" value="true" /> --> <param name="android-package" value="com.zijin.toastplugin.ToastPlugin"/> </feature> </config-file> <! -- ⚠️ The config-file tag adds its internal child tags to the root path of the androidmanifest.xml file, the manifest tag. This file is mainly used to configure permissions for the Android platform, declare the created services, declare the created broadcasts, declare the created activities, and set the Android system version compatible with the application, etc. Androidmanifest.xml  https://developer.android.com/guide/topics/manifest/manifest-intro?hl=zh-cn --> <config-file parent="/*" target="AndroidManifest.xml"> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> </config-file> <! -- ⚠️ The source-file tag is used to specify where the files in the plugin package are copied to the Android platform project after the plugin install command is executed. ⚠️ The path copied here is relative to Platforms /android/app, i.e. platforms/android/app is the root directory. --> <source-file src="src/android/ToastPlugin.java" target-dir="src/com/zijin/toastplugin/ToastPlugin"/> </platform> </plugin>Copy the code

b. src/android/ToastPlugin.java

The ToastPlugin class inherits from CordovaPlugin and needs to override the execute method, which responds to method calls in front of the action to implement calls to platform layer functionality.

c. www/ToastPlugin.js

Expose the methods owned by plug-ins to the Web platform, and provide the front end with a unified method to shield native platform (Android/iOS) plug-in function calls.

Command Parameter Meaning

plugman create --name <pluginName> --plugin_id <pluginID> --plugin_version <version> [--path <directory>] 
Copy the code

👆 The meanings of the preceding parameters are as follows:

  1. PluginName: indicates the name of the plug-in
  2. PluginID: plug-in ID (the package name that generated toastplugin.java)
  3. Version: indicates the version description of the plug-in
  4. Directory: An absolute or relative path in which plug-ins will be stored. By default, plug-ins will be placed in the directory where commands will be executed

2. Install the empty plugin package into the Ionic project

A. Write WWW/toastplugin.js to define plug-in function methods

var exec = require('cordova/exec'); /** ** @param msgInfo js object, including MSG and showLength properties * @param success js function object, when the plug-in function is called successfully * @param error JS function object, */ exports. ShowToast = function (msgInfo, success, error) {exec(success, error, 'ToastPlugin', 'showToast', msgInfo); };Copy the code

Installing an empty plugin package into an existing Ionic project allows you to quickly access the context of the Cordova Framework, support IDE code hints, and compile and run plugin features for quick debugging.

B. Go to the directory where the plug-in package resides and run the NPM init command to initialize the plug-in package (⚠️ otherwise, subsequent steps cannot be performed).

C. Install the plugin into the Ionic project

ionic cordova plugin add .. /ToastPluginCopy the code

Add is followed by the path address of the plug-in package, which can be a relative or absolute path.

If the Ionic project does not already integrate the Cordova platform when installing the plugin, the Ionic CLI will automatically integrate the Cordova environment for you when executing the plug-in installation command.

The operation diagram of the command is as follows:

Or enable the Cordova environment before installing the plug-in by using the Ionic Integrations enable Cordova command.

3. Compile and generate platform code

Run the Ionic Cordova platform add Android command to generate android platform code.

⚠️ If the permission is insufficient, use the super administrator permission or sudo to run the preceding command.

Details about the command: ionicframework.com/docs/cli/co…

4. Import the generated platform code using AndroidStudio

A. Open AndroidStuido and select Open an Existing Android Studio Project

B. Select the directory where Platforms/Android resides to perform the import.

⚠️ On the MAC platform, click open, if the following error message is displayed, please use sudo chmod -r 777 platforms/android to add permission to all files on Android.

⚠️ after the import, AndroidStudio will automatically execute Gradle scripts to download the third-party libraries relied on in the project and build the project. Due to the domestic network environment, it is likely that there will be an error in the download due to the network connection. If there is an error, please open the scientific Online tool and then build the project.

C. Gradle is successfully built

When Gradle has finished building your project, AS is displayed AS the directory structure of your Android project, and you can see the run button.

After opening the debugging mode of Android system in the developer option, connect the COMPUTER AS with USB cable to identify your device. If you do not have a real Android phone, you can use the simulator provided by AS to develop plug-ins.

About the use of the simulator, please reference: developer.android.com/studio/run/…

D. Run the project to the device

Click the green Run button to package Ionic projects into APK and run them on your connected device.

4. Start plug-in function development

Open the Ionic project. The directory structure of the sample project is as follows:

In home.page.ts to call the function method exposed on the front end of the plug-in (toastplugin.js), the code is as follows:

import {Component} from '@angular/core'; // 1. Declare the type of window. Since window is an object in JavaScript, it does not exist in TypeScript and must be declared in order to compile. declare const window: any; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], }) export class HomePage { constructor() { } showToast() { // 2. Through the window object to invoke methods on the plug-in window. The cordova. Plugins. ToastPlugin. ShowToast ({MSG: 'hello, cordova plugin! ', showLength: 'short'}, success => { console.log(success); }, error => { console.log(error); }); }}Copy the code

At this point, the execute method of ToastPlugin in the Android layer will be triggered when the button of printing messages is clicked in the front end. Next, you’ll code the functionality of the plug-in in the Android layer.

Cordova uses JSON as a message format from the front end to the Android plug-in layer. In plug-in development, we often need to parse JSON data in complex formats. Android SDK provides us with native JSON parsing classes, but parsing requires manual retrieval of each piece of content. In order to quickly and conveniently achieve the parsing of JSON data, Here we introduce Gson to help us automate the parsing of the data.

Taking the introduction of Gson library as an example, two ways of introducing tripartite library are introduced:

  1. The three-party library is introduced by importing the downloaded JAR or AAR package into the SRC /main/libs directory.
  2. Gradle completes the introduction of tripartite libraries.

🍭 recommended approach: Use the second approach whenever possible to complete the introduction of tripartite libraries. Reason: Because our project will also introduce other tripartite plug-ins, sometimes it is inevitable that other plug-ins will reference the same tripartite library as you. If you manually download jar packages to introduce, jar package conflicts will occur.

Add the following to the dependencies closure of build.gradle in your app:

Implementation 'com. Google. Code. Gson: gson: 2.6.1'Copy the code

To modify the build.gradle file, click sync Now in the upper right corner to automatically download the new dependencies. With the dependencies complete, we started writing the following code in toastplugin.java:

public class ToastPlugin extends CordovaPlugin { @Override public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { if (action.equals("showToast")) { Gson gson = new Gson(); List<MsgInfo> msgInfoList = gson.fromJson(args.toString(), new TypeToken<List<MsgInfo>>() { }.getType()); this.showToast(msgInfoList, callbackContext); return true; } return false; } private void showToast(List<MsgInfo> msgInfoList, CallbackContext callbackContext) { MsgInfo msgInfo = null; if (msgInfoList ! = null && msgInfoList.size() > 0) { msgInfo = msgInfoList.get(0); } else {callBackContext. error(" parameter exception "); return; } if ("short".equals(msgInfo.getShowLength())) { Toast.makeText(cordova.getContext(), msgInfo.getMsg(), Toast.LENGTH_SHORT).show(); Callbackcontext. success(" message indicating success "); } else if ("long".equals(msgInfo.getShowLength())) { Toast.makeText(cordova.getContext(), msgInfo.getMsg(), Toast.LENGTH_LONG).show(); Callbackcontext. success(" message indicating success "); } else {callBackContext. error(" parameter exception "); }}}Copy the code

A few things to note about the code above:

A. For classes that want to implement plug-in functions, CordovaPlugin needs to be inherited and execute method should be rewritten to handle front-end calls to plug-in functions.

B. Execute method parameters and return values.

The meanings of the three parameters of the execute method:
  1. String Action: Action is the name of the method used by the front-end to call the plug-in. In plug-in development, we will implement different functions according to the value of the action.

  2. JSONArray ARgs: Args is a JSONArray object passed by the front end when the plug-in method is called.

  3. CallbackContext CallbackContext: the CallbackContext object is the CallbackContext object when the plug-in method is invoked. It is responsible for processing the message passing from the plug-in to the front end.

The execute method returns the following meaning:

The value of action is the name of the method used by the front end to invoke a plug-in function, that is, a specific function of the plug-in layer. We need to return true if we have a matching implementation, false otherwise to tell the front-end caller that this is an invalid action. When false is returned, the Cordova Framework returns an INVALID_ACTION failure message to the front end. When we return true we need to return the message ourselves via callbackContext.

C. Use of the CallbackContext class

Whether to return a single message or multiple messages depends on what the plug-in implements. If we need to write a plug-in function that gets device information, we simply return a message containing the device information. If functions such as e-label inventory need to be realized, the plug-in needs to continuously transmit multiple messages containing e-label content to the front end. We use the CallbackContext class to deliver either a single message or multiple messages.

1. Send a single message
Callbackcontext. error("error message."); // Calling the SUCCESS method triggers the execution of the front-end success method callback callBackContext.success ("success message.");Copy the code

By default, the Cordova Framework only calls CallBackContext. error or SUCCESS once and does not pass messages to the front end.

2. Send multiple messages
PluginResult pr = new PluginResult(PluginResult.Status.OK, jsonArray.toString()); // Be sure to set pr.setkeepCallback (true); callbackContext.sendPluginResult(pr);Copy the code

Using the code above, we can send multiple messages to the front end in succession. When creating PluginResult objects, we use the first parameter to set the success or failure status of the current message, and use the second parameter to send the specific message content.

If the value is not immediately available after the plug-in function is called, we can pass an empty content to the front end first. Call the above method to return the actual value when the actual value is obtained.

 PluginResult pr = new PluginResult(PluginResult.Status.NO_RESULT);
 pr.setKeepCallback(true);
 callbackContext.sendPluginResult(pr);
Copy the code

4. Dealing with time-consuming tasks

The code we write in the plug-in runs in the main thread of Android. If we need to do some time-consuming operations in the plug-in, such as file reading and writing, network request, audio and video format conversion, we need to put the processing logic of these time-consuming tasks into the sub-thread to execute. Otherwise, ANR may occur when a time-consuming task blocks the main thread for too long. If a task takes too long to execute, the Cordova Framework prompts you to execute the task in a child thread.

THREAD WARNING: exec() call to ToastPlugin.showToast blocked the main thread for 26ms. Plugin should use CordovaInterface.getThreadPool().
Copy the code

Threads can be started directly using the thread pool wrapped by the Cordova Framework.

       cordova.getThreadPool().execute(new Runnable() {
            @Override
            public void run() {
                // Perform time-consuming operations
            }
        });
Copy the code

👹 Note: Do not do any UI-related operations in child threads, otherwise the program will crash. If you need to update the UI, use Handler or getActivity().runonuithRead () to switch to the main thread before performing uI-related operations.

5. Handle plug-in lifecycle events

What is the life cycle?

When a user browses, exits, and returns to your app, instances of an Activity in the app transition between different states in its lifecycle. The Activity class provides a number of callback methods that let the Activity know that a state has changed. The system is creating, stopping, or resuming an Activity, or destroying the process in which the Activity resides. The Cordova Framework synchronizes the Activity lifecycle state of the WebView to the CordovaPlugin. Therefore, we can rewrite the lifecycle method in CordovaPlugin to make our plug-in respond to different lifecycle events to improve the stability and performance of the application.

The following is an Android Activity lifecycle event:

The lifecycle callback method in the CordovaPlugin

When writing a plug-in, we generally only need to deal with the following three lifecycle methods:

  1. OnPause: This callback is executed when the application changes from visible to invisible, such as when we press the home button to return to the home page. Generally we need to pause the execution of the plug-in in this method, such as the plug-in to the RFID tag scanning function.
  2. OnResume: This callback is performed when the app changes from invisible to visible, such as when we click on the app icon to return to the app. Normally we need to resume suspended operations in this method, such as restoring the plug-in’s rfid tag scanning function.
  3. OnDestory: callback when the user exits the application. At this point, we need to perform some resource release operations in this callback method.

5. Extract developed features into separate plug-in packages

This is an important step, and one that is prone to error. When developing plug-in functions, we need to pay special attention to the source code files added in the process of plug-in development, resource files and three-party libraries introduced, because when extracting into a separate plug-in package, we need to copy all the content added during the development of this function into the plug-in package created in the first step. Plugin.xml is configured according to the directory structure in Platforms/Android.

Before extracting, let’s check what was added to implement the plugin functionality:

  1. MsgInfo.java
  2. ToastPlugin.java
  3. Gson was introduced through the Gradle version build tool

Step 1: Copy the new files to the plugin package. We can organize the file location according to the function of the source code. The source code is stored under SRC/Android.

Step 2: Write the plugin.xml file.

<? The XML version = '1.0' encoding = "utf-8"? > < plugin id = "com. Zijin. Toastplugin" version = "1.0.0" XMLNS = "http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android"> <name>ToastPlugin</name> <js-module name="ToastPlugin" src="www/ToastPlugin.js"> <clobbers target="cordova.plugins.ToastPlugin"/> </js-module> <platform name="android"> <config-file parent="/*" target="res/xml/config.xml"> <feature name="ToastPlugin"> <param name="android-package" value="com.zijin.toastplugin.ToastPlugin"/> </feature> </config-file> <config-file parent="/*" target="AndroidManifest.xml"> </config-file> <source-file src="src/android/main/ToastPlugin.java" target-dir="src/com/zijin/toastplugin"/> <source-file src="src/android/model/MsgInfo.java" Target - dir = "SRC/com/zijin toastplugin" / > < framework SRC = "com. Google. Code. Gson: gson: 2.6.1" / > < / platform > < / plugin >Copy the code

The role of tags in plugin.xml was introduced at the beginning of this article. It is important to note that the SRC attribute in the source-file tag is the path of the file in the plugin package, but target-dir must be added exactly as we implemented in the Android layer.

The introduction of the Gson library is accomplished by using the Framework tag to fill in the SRC attribute with the corresponding dependency address of Gson.

About the plugin. The XML configuration instructions in detail please refer to: cordova.apache.org/docs/en/lat…

After completing the above steps, reinstall the plug-in package and then run the APP to test whether the extraction of the plug-in package is successful.

6. Create an Ionic Native wrapper for the plugin we wrote

A Cordova plug-in is written in the following five steps, but the code exposed to the front-end call is written in JavaScript language (WWW/toastplugin.js). Ionic is based on TypeScript. In order to achieve easy and uniform function invocation on the Ionic side, we need to use Ionic Native to wrap Cordova plug-ins already written. Ionic Native wraps successful or failed callbacks to methods in plug-ins in promises or Observables, providing a common interface for all plug-ins.

Steps for writing an Ionic Native package:

  1. Clone the ionic-native project from Github to the local environment, which is required for subsequent command execution and plug-in file template generation.

    Address: github.com/ionic-team/…

  2. Go to the root directory of the cloned ionic-native

  3. Creating a plug-in package

    // Call this command and replace 'PluginName' with the name of the plug-in you want to add. // The first letter must be capitalized and name gulp plugin:create -n PluginName with the big humpCopy the code

    After executing this command, a plugin named PluginName is created in the SRC /@ionic-native/plugins directory. The plugin contains an index.ts file, which is used to write ionic plug-ins.

  4. Start writing the index.ts file

    @Plugin({
    pluginName: 'ZijinUtilPlugin',
    plugin: 'cordova-plugin-x-zijinutil', // npm package name, example: cordova-plugin-camera
    pluginRef: 'cordova.plugins.ZijinUtil', // the variable reference to call the plugin, example: navigator.geolocation
    platforms: ['Android'] // Array of platforms supported, example: ['Android', 'iOS']
    })
    @Injectable()
    export class ZijinUtilPlugin extends IonicNativePlugin {
    }
    Copy the code

    The important thing to note here is what each parameter in the @plugin decorator does:

    1. PluginRef: refers to the object called by the front end to Cordova. This value is the same as the attribute value of the plugin.xml node clobbers in Cordova.
    2. Plugin: Value is the name of the NPM package.
  5. Ionc Native is compiled by calling NPM run Build in the ionC Native root directory. The generated ionic Native package is generated in the ionic-native/ Dist /@ionic-native/plugins directory.

Follow these steps to create an Ionic Native wrapper for the ToastPlugin plugin

  1. To create the index.ts file of the ToastPlugin, run the following command: / SRC /@ionic-native/plugins/toast-plugin-wrapper/

     gulp plugin:create -n ToastPluginWrapper 
    Copy the code
  2. Write the index.ts file

    import { Injectable } from '@angular/core';
    import { Plugin, Cordova, CordovaProperty, CordovaInstance, InstanceProperty, IonicNativePlugin } from '@ionic-native/core';
    import { Observable } from 'rxjs';
    @Plugin({
    pluginName: 'ToastPluginWrapper',
    plugin: 'cordova-plugin-toast',
    pluginRef: 'cordova.plugins.ToastPlugin',
    platforms: ['Android']
    })
    @Injectable()
    export class ToastPluginWrapper extends IonicNativePlugin {
    
    @Cordova()
    showToast(msgInfo: MsgInfo): Promise<string> {
        return;
    }
    
    }
    
    export interface MsgInfo {
    msg: string;
    showLength: string;
    }
    Copy the code

    ⚠️ Note that the type of return value declared in the index.ts file for the plug-in method depends on whether the Android layer implements the plug-in function to send the message to the front end once or multiple times.

    1. The front end only sends a message once: use Promise as the return value type of the plug-in method

    2. Send multiple messages to the front end: Use Observable as the return value type of the plug-in method

  3. By using the ionic-native root pathnpm run buildCommand to compile the source code to generate the ionic wrapper file, generate the wrapper file location /dist/@ionic-native/plugins/

👹 may encounter some pits

  1. Cordova is referenced repeatedly in JavaScript files generated in the type declaration package compiled under ionic-native:

    Solution: Delete unnecessary cordova modules

  2. The type declaration file index.d.ts specifies that the return result of a method calling the Native layer function does not take effect as Observable:

    Index.d.ts file internal method declaration:

    @cordova()
    openScanReceiver(): Observable<any> {
    return;
    }
    Copy the code

    Solutions:Add {” Observable “: true} to the two index.js in the screenshot above.

  3. If you need to write an IonicNative package for Cordova, write the parameters passed first.

    // 🙆🏻♂️ correct exports.startService = function (interval, success, error) {exec(success, error, 'BackgroundTask', 'startService', [interval]); }; // 🙅🏻♂️ error, Android layer will not be able to get the passed parameter exports.startService = function (success, error, interval) { exec(success, error, 'BackgroundTask', 'startService', [interval]); };Copy the code

7. Use the Cordova plugin with Ionic Native packaging in the project

  1. First we need to copy the toasts-plugin-Wrapper folder generated in the previous step into the project.

  2. This service is provided in the NgModule metadata in the appModule to allow external instantiation of the plug-in through dependency injection.

  3. Invoke plug-in functions.

🎊 end

Example code address: github.com/Merpyzf/Plu…