# Android Native Plugin Development Guidelines

# I. Development Environment Preparation

  1. according toNative Plug-In Tool Operating GuidelinesGenerate a multi-terminal plug-in project

  2. Refer to the documentation below and install Weixin DevToolsJDK as well as Android Studio(Android SDK)

  3. Environmental Dependence:

    • JAVA version:JAVA8 <= JAVA version <= JAVA15
    • Gradle Version:6.7.1
    • Weixin DevToolsPlease use 1.06.2311282 And above version

# II. Introduction to Plug-in Project

  • Here's an example of a multi-terminal plug-in project generated by the tool:

  • Under the root directory Android The directory is the project for native plug-ins

# Engineering Directory

.
 app // Sample application, basically no changes, and do not recommend changes
 build.gradle // Gradle Build Script
 compile // Certificate stored
 Gradle  // storage Gradle Wrapper Directory of
 gradle.properties  // Gradle Property file
 gradlew // Gradle Wrapper The startup script for
 gradlew.bat // Gradle Wrapper The startup script for
 miniapp.json  // Some configuration files when running the application, the basic can not be moved
 miniapp.plugin.json  // Plug-related configuration, described later
 plugin // Plug-in directory, plug-in development mainly need to modify the directory code, the following details
 settings.gradle // Gradle Setup file

# miniapp.plugin.json

{
  "pluginId": "wx45c6d53ff86fd405", // plug-in ID, automatically generated
  "pluginVersion": "0.0.1", // plug-in Version, which needs to be used when building
  "debugSaaAVersion": "1.0.0", // Dependent get  sdk Version (the sdk Only basic rendering functions are included for plugin development. Do not call any of the jsapi Outside the method, i.e. wx.request, etc. wx api Not supported)
  "pluginPackageName": "com.donut.wx45c6d53ff86fd405" // Automatically generated plug-in package names
}

# plugin catalog

.
 build.gradle // Gradle Build Script
 consumer-rules.pro
 gradle.properties 
 libs // Store dependent sdk,sdk Will automatically download
 proguard-rules.pro 
 src
     Main
         AndroidManifest.xml
         java
            com
                donut
                    wx45c6d53ff86fd405
                        PluginManager.kt // Plugin entry file, described in more detail later
         resources
             META-INF
                 services
                     com.tencent.luggage.wxa.SaaA.plugin.NativePluginInterface // Declare the registration plug-in, if the above PluginManager class has changed, you need to synchronize the modification here

# III. Running a Multi-Plug Project

# 1. Switch on the tool Android Mode after the point to run, will directly pull up Android Stuido

Local Environmental Requirements:

  • windows Need to be installed in the C disc (Installed in the C Please open Android manually for a directory other than the disk Stuido Import Project)
  • mac You may need to create a command line script first

If the point runs pull up Android Stuido Failure, you can also manually create your own in the Android Stuido Import to open the project without affecting the subsequent process

Note: It is possible to report when importingunsupported Java Please confirm your Android Studio Used in. java Version:

# 2. Android Studio Click here.runButton to see the sample project in action.

# 3. Click firstLoad multiterminal plugins, Load successfully and then clickCalling the Multiterminal Plugin, can be obtained by vconsole See the return of the plug-in method.

Note:1.06.2311282 And above versions of the Developer Tools do not require manual building of the Android resource pack, the Developer Tools remain running when in Android Studio Click here.runThe latest Mini Program code package resources are built automatically when

Build the log as shown:

If it shows here url Is empty. Make sure inSet up->Security settingsThe multiterminal plug-in service port is open

# IV. Plugin Development

# Call timing of multiterminal applications and multiterminal plug-ins

In the beginning Android Before the development of multi-terminal plug-ins, understanding the timing of multi-terminal applications and multi-terminal plug-in calls will help developers better.

sequenceDiagram
    participant app as Multiterminal application
    participant plugin as Multiterminal plugin

    app->>app:wx.miniapp.loadNativePlugin Load plugin
    app->>plugin: Load plugin
    plugin->>plugin: Instantiate the plug-in object on load
    app->>plugin: Load the plug-in, perform a series of initialization operations (including id Registration, plug-in method registration, plug-ins initPlugin Method call)
    app->>app: Hold Plugin Instance pluginInstance
    Note over app,plugin: Plugin Load Complete
    app->>plugin: pluginInstance.func() Make a call to the plug-in method

Please note that Android An instance of the plug-in runs on the Mini ProgramChild processIn. Acquired in this context. Activity Is when the Mini Program is running. Activity。

Registration Load Process sdk Will be implemented, the developer only needs to inherit the NativePluginInterface, the implementation needs to be exposed to js Side method

import com.tencent.luggage.wxa.SaaA.plugin.NativePluginInterface
import com.tencent.luggage.wxa.SaaA.plugin.SyncJsApi
import com.tencent.luggage.wxa.SaaA.plugin.AsyncJsApi
import org.json.JSONObject


class TestNativePlugin: NativePluginInterface {
    private val TAG = "TestNativePlugin"

    override fun getPluginID(): String {
        android.util.Log.i(TAG, "getPluginID")
        return BuildConfig.PLUGIN _ID
    }

    // Synchronization method
    // js Side-call plugin.mySyncFunc Will trigger test function
    @SyncJsApi(methodName = "mySyncFunc")
    fun test(data: JSONObject?): String {
        android.util.Log.i(TAG, data.toString())
        return "test"
    }

    // Asynchronous method
    // js Side-call plugin.myAsyncFuncwithCallback  Will trigger testAsync function
    @AsyncJsApi(methodName = "myAsyncFuncwithCallback")
    fun testAsync(data: JSONObject?, callback: (data: Any) -> Unit) {
        android.util.Log.i(TAG, data.toString())

        callback("async testAsync")
    }

}

# Register synchronization method

  • use SyncJsApi annotation
parameter type Required Introductions
methodName String yes in js Layer available for acquisition pluginInstance.methodName Make a method call
  • Input and Output Parameters of Synchronization Method
parameter type Required Introductions
Entry 1 JSONObject? no in js Layer call pluginInstance.methodName When the incoming Object Type parameter
Entry 2 Activity no The Mini Program is currently running Activity, to get the parameter normallydebugSaaAVersion To update to the 1.0.1And above version
Ginseng Serializable type no in js Layer call pluginInstance.methodName The return of
  • Code Examples

Android Implementation of plug-in synchronization method

    @SyncJsApi(methodName = "mySyncFunc")
    fun test(data: JSONObject?): String {
        android.util.Log.i(TAG, data.toString())
        return "test"
    }

If you want to get activity:

    @SyncJsApi(methodName = "mySyncFunc")
    fun test(data: JSONObject?, activity: Activity): String {
        android.util.Log.i(TAG, data.toString())

        val componentName = activity.componentName.toString()
        android.util.Log.i(TAG, componentName)
        
        return "test"
    }

JS Side of the method call


wx.miniapp.loadNativePlugin({
    pluginId: 'YOUR_PLUGIN_ID',
    success: (plugin) => {
        console.log('load plugin success')
        const ret = plugin.mySyncFunc({ a: 'hello', b: [1,2] })
        console.log('mySyncFunc ret:',  ret)
    },
    fail: (e) => {
        console.log('load plugin fail', e)
    }
})

# Register Asynchronous Methods

  • use AsyncJsApi annotation
parameter type Required Introductions
methodName String yes in js Layer available for acquisition pluginInstance.methodName Make a method call
  • Input and output parameters of asynchronous methods
parameter type Required Introductions
Entry 1 JSONObject? yes in js Layer call pluginInstance.methodName When the incoming No. 1 individual Object Type parameter
Entry 2 function yes The callback method supports a parameter of a serializable type. The method callback is passed back to the js layer
Entry 3 Activity no The Mini Program is currently running Activity, to get the parameter normallydebugSaaAVersion To update to the 1.0.1And above version
  • Code Examples Android Plug-in asynchronous method implementation
@AsyncJsApi(methodName = "myAsyncFuncwithCallback")
fun testAsync(data: JSONObject?, callback: (data: Any) -> Unit) {
    android.util.Log.i(TAG, data.toString())

    callback("async testAsync")
}

If you want to get activity:

@AsyncJsApi(methodName = "myAsyncFuncwithCallback")
fun testAsync(data: JSONObject?, callback: (data: Any) -> Unit, activity: Activity) {
    android.util.Log.i(TAG, data.toString())


    val componentName = activity.componentName.toString()
    android.util.Log.i(TAG, componentName)

    callback("async testAsync")
}

JS Side of the method call


wx.miniapp.loadNativePlugin({
    pluginId: 'YOUR_PLUGIN_ID',
    success: (plugin) => {
        console.log('load plugin success')
        plugin.myAsyncFuncwithCallback ({ a: 'hello', b: [1,2] }, (ret) => {
            console.log('myAsyncFuncwithCallback ret:',  ret)
        })
    },
    fail: (e) => {
        console.log('load plugin fail', e)
    }
})

# Plugin Event Listening

  • in Native Side is inherited by calling the NativePluginBase The method of sendMiniPluginEvent May be directed to JS Side to send events.
parameter type Required Introductions
param HashMap yes in js The input obtained by the listening method of the side
  1. debugSaaAVersion To update to the 1.0.2And above version
  2. inheritance NativePluginBase
  3. call sendMiniPluginEvent, the entry type is HashMap
import com.tencent.luggage.wxa.SaaA.plugin.NativePluginBase

class TestNativePlugin: NativePluginBase(), NativePluginInterface {
    private val TAG = "TestNativePlugin"

    override fun getPluginID(): String {
        android.util.Log.e(TAG, "getPluginID")
        return BuildConfig.PLUGIN _ID
    }

    @AsyncJsApi(methodName = "myAsyncFuncwithCallback")
    fun testAsync(data: JSONObject?, callback: (data: Any) -> Unit) {
        android.util.Log.i(TAG, data.toString())

        // When necessary, to js Send Message
        val values1 = HashMap<String, Any>()
        values1["status"] = "testAsync start"
        this.sendMiniPluginEvent(values1)

        callback("async testAsync")

        // When necessary, to js Send Message
        val values2 = HashMap<String, Any>()
        values2["status"] = "testAsync end"
        this.sendMiniPluginEvent(values2)
    }
}
  • in JS Side that can use the plug-in instance onMiniPluginEvent Method Register Listening
parameter type Required Introductions
callback Function yes Native Lateral JS The callback triggered when an event is sent on the side, and multiple callbacks can be registered
  • in JS Side that can use the plug-in instance offMiniPluginEvent Cancel the wire.
parameter type Required Introductions
callback Function no Cancel the wire.Cancels all listening callbacks when no callback is specified to cancel

JS Side of the method call


const listener1 = (param) => {
  console.log('onMiniPluginEvent listener1', param)
}

const listener2 = (param) => {
  console.log('onMiniPluginEvent listener2', param)
}

wx.miniapp.loadNativePlugin({
    pluginId: 'YOUR_PLUGIN_ID',
    success: (plugin) => {
        plugin.onMiniPluginEvent(listener1)
        plugin.onMiniPluginEvent(listener2)
    }
})

# Executing code in the main process

debugSaaAVersion To update to the 1.0.6And above version

part sdk Requires that code must be initialized in the main process, encapsulating theNativePluginMainProcessTaskUsed to facilitate execution in the main process, detailed parameters see the following code example

  1. First you need to inherit. NativePluginMainProcessTask

Please note that in the following examplevalToSync1andvalToSync2Just data fields for cross-process transfer. If you don't need to transfer, you can just delete

import com.tencent.luggage.wxa.SaaA.plugin.NativePluginMainProcessTask
import kotlinx.android.parcel .Parcelize 


    @Parcelize
    class TestTask(private var valToSync1: String, private var valToSync2: String) :
        NativePluginMainProcessTask() {

        private var clientCallback:  ((Any) -> Unit)? = null

        fun setClientCallback (callback: (data: Any) -> Unit) {
            this.clientCallback = callback
        }

        /**
         * Logic that runs in the main process. It is not recommended to perform operations that take too long in the main process
         */
        override fun runInMainProcess() {
            android.util.Log.e("MainProcess", " runInMainProcess, valToSync1:${valToSync1}, valToSync2:${valToSync2}")
            // If you need to call back data from the main process to the Mini Program process, assign the value and call the callback function
            valToSync1 = "runInMainProcess"
            this.callback() // The callback function synchronizes the task data of the main process and calls runInClientProcess in the child process
        }

        /**
         * Logic that runs in the Mini Program process
         */
        override fun runInClientProcess() {
            android.util.Log.e("ClientProcess", "valToSync1: ${valToSync1}, valToSync2:${valToSync2}")
            this.clientCallback?.let { callback ->
                callback(valToSync1)
            }
        }

        override fun parseFromParcel(mainProcessData: Parcel?) {
            // If you need to get the master process data, you need to rewrite parseFromParcel to manually parse the Parcel
            this.valToSync1 = mainProcessData?.readString() ?: ""
            this.valToSync2 = mainProcessData?.readString() ?: ""
        }

    }
  1. Actually execute runInMainProcess when needed
@AsyncJsApi(methodName = "registerPush")
fun registerPush(data: JSONObject?, callback: (data: Any) -> Unit, activity: Activity) {
    val testTask = TestTask("test1", "test2")
    testTask.setClientCallback (callback)
    testTask.execAsync() // Real execution of runInMainProcess
}

# requestPermission

debugSaaAVersion To update to the 1.0.6And above version

To reduce the hassle of requesting permissions on the plug-in side, wrap arequestPermissionmethod

    @AsyncJsApi(methodName = "registerPush")
    fun registerPush(data: JSONObject?, callback: (data: Any) -> Unit, activity: Activity) {
        android.util.Log.i(TAG, data.toString())

        this.requestPermission(
                activity,
                arrayOf(Manifest.permission.POST_NOTIFICATIONS)
            ) { permissions, grantResults ->
                if (grantResults != null && grantResults.size > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED
                ) {
                    android.util.Log.i(TAG, "PERMISSION_GRANTED, do invoke again")
                    callback("PERMISSION_GRANTED")
                    
                } else {
                    android.util.Log.e(TAG, "reloadQRCode fail, SYS_PERM_DENIED")
                    callback("fail!")
                }

            }



    }

# V. Plug-in Use

# to construct

Android plugins must be built and uploaded before they can really be used

Click on the toolto construct -> to construct Android Plug-in product, will automatically execute the build command./gradlew :plugin:build, when the build is complete, it will generate a localAar Folder, and eventually put localAar Folder Contents copy To the directory build/android Down, for subsequent uploads

  • The version number readsminiapp.plugin.jsonInpluginVersionfield

# Third-party dependence Sdk processing

  • maven source

If the user has a third-party dependency sdkmaven Source), which can be configured pom file Format Reference:

project.ext.pomDeps = [
    "com.tencent.tpns:tpns" : "1.4.0.1-release",
]

tip: Support Maven Central, and the following warehouses

  • https://repo1.maven.org /maven2 /
  • https://dl.google.com/dl/android/maven2
  • https://developer.huawei.com/Repo/

libs or Not mentioned above maven Within the source list

Please refer to the use of(fat-aar-android)[https://github.com/kezong/fat-aar-android]To rely on embed enter aar package

# apply Gradle plugin

Android manufacturers may need to push the manufacturer's plug-in, for example, Huawei manufacturers may need toapply plugin: 'com.huawei.agconnect', when building remotely, it can be done inproject.miniapp.jsonadoptpluginConfigPathTo configure The profile for that path needs to be a legitimate json File (which is used when the cloud is built), the reference format is

{
	"project": {
        // Dependent configuration, optional, build app Is merged into the project root directory build.gradle (do not copy the comments when using)
	    "dependencies": [
	      "com.huawei.agconnect:agcp:1.6.0.300"
	    ]
	},
	"app": {
        // Gradle that needs to be used Plugin, Optional, Build app When it comes into effect. (Do not copy comments when using)
	   "plugins": [
	      "com.huawei.agconnect"
	   ]
	}
}

When added, the gradle plugin s dependencies are added at the top of the build.gradle in the project s root directory

buildscript {
    dependencies {
        classpath 'com.huawei.agconnect:agcp:1.6.0.300'
    }
}

in app catalog build.gradle Add to bottom of file apply plugin

apply plugin: 'com.huawei.agconnect'

If you need to upload other files, you can refer to the Android Configure native resources

# upload

Android native plug-ins must be uploaded before they can be used in cloud-built projects

  • In order for the cloud build to work with the plugin, please use it before uploadingConstruction productTest it.
  1. ensure localAar There's already a build in the folder
  2. miniapp.plugin.jsonAdd one to the file.debugWithAarField, set to true
  3. Android Studio Click here.run, In build You can see it in the log. Use Aar The output of
  4. Self-test to ensure that the logic is consistent with the expected use of the build product

Please note:

  1. Will be uploaded when it is uploaded build/android and android/plugin Two folders
  2. The version number must be the same as the build number when uploading(miniapp.plugin.jsonInpluginVersion)Consistent, otherwise there will be problems with the use

# Cloud Build Using Plugins

Configure the plug-ins you need to use in your multi-application project ID

  • In a multi-end application project that uses plug-ins, Android Sdk Version required >= 1.1.0
  • If needed activity or Event Notification,Android Sdk Version Please select 1.1.9 And above

# debugSaaAVersion Version Update

Android Sdk Please see all versions.Android SDK Update log

# 1.0.6

Minimum Android Sdk Version:1.3.13

  1. A newly added allow[Executing code in the main process](#Executing code in the main process)
  2. A newly added requestPermission

# Common problem

# Q: It works when debugging, but after uploading it, it fails using the cloud build

  1. Be sure to ensure that you useConstruction productSelf-tested(miniapp.plugin.jsonfiledebugWithAarfortrue)
  2. Check the obfuscation configuration for any problems. You can build a Release Version of the Apk Self-test
    • before clean project(./gradlew clean)
    • ./gradlew :app:assembleRelease And watch build/outpus/Apk /arm64/ReleaseDown Apk
    • If you need to add a new obfuscation configuration, you can add it in theplugin/consumer-rules.proIn-file processing (do not delete the file's original configuration)
  3. adb Logcat or Android Stuido You can look at error messages, you can see if the thrown error messages are useful

# Q: Execution failed for task ':plugin:generatePom'

Please refer to the documentation to check your pom Configuration is configured correctly

# Q: Why does the project change source code invalid and can not break point?

  • Please confirm(miniapp.plugin.jsonfiledebugWithAarWas it set uptrue)
  • Please verify that the breakpoint process has selected:wxa_container0