UNIAPP----Android端原生插件开发实战(一)

1.前言

最近一个项目要求我们的产品必须走网络隧道,并且提供了对应的SDK,很明显只能通过原生开发的方式才能实现这个流程,笔者没有做过原生开发,也没有学过java,所以也踩了不少坑啊,花了两天时间总算完成任务,今天系统的总结下步骤,由于是根据笔者的业务进行的开发,所以步骤上与细节之处与其他原生插件开发相关的文章可能会有些出入。还请带着辩证的心态阅读本文。

2.工具材料清单

工具/材料 版本/版本名
HBuilder X 3.1.4
Android Studio 4.1.3
UNI-SDK Android-SDK@3.1.4.80686_20210305
Android Gradle Plugin Version 4.1.1
Gradle Version 6.5

3.SDK集成文档

这里是原生SDK指的是甲方供的第三方SDK

3.1介绍

1、安全隧道的aar使用的jar包 guava-18.0.jar
2、安全隧道的aar引用的第三方工程

    implementation 'org.bouncycastle:bcprov-jdk15on:1.55'
    implementation 'org.apache.commons:commons-lang3:3.4'
    implementation 'org.slf4j:slf4j-api:1.7.21'

3、安全隧道服务器的地址的配置,在资源文件中配置好安全隧道服务器的地址

<string name="client_vpn_server_host">xxx.xxx.xxx.xx:xxxx</string>

4、安全隧道在初始化后会自动获取白名单(异步动作),获取成功后会持久化到本地,因此,首次初始化安全隧道的时候可能相对会比较慢(因为有网络请求)

3.2 集成

安全隧道提供的是aar,必要的文档都已经打包在了aar中,仅需要在主工程中的build.gradle文件的

dependencies{
    ...

    implementation(name: 'MXSocksCore-x.x.x.xxxxxxxx', ext: 'aar')
    ...
}

x.x.x.xxxxxxxx 为版本号,也可以自己修改,但要跟文件名对应
如果aar不在主工程的build.gradle文件中引用,需要在主文件的build.gradle文件的

repositories {
        flatDir {
            dirs 'libs','子工程的libs的相对路径'
        }
    }

中修改对应的相对路径

最后将aar放到工程的libs文件夹中

3.3 API

1、初始化隧道

MXAppTunnel.getInstance().initAppTunnel(context, new AppTunnelInitComplete() {
                            @Override
                            public void appTunnelInitComplete() {
                                //初始化完成
                            }

                            @Override
                            public void appTunnelInitError(String msg) {
                                //初始化失败
                            }
                        });

隧道初始化必须在使用之前初始化完成,此动作是一个异步的动作,首次启动会获取白名单,获取成功后会存储到本地,之后的启动不会因为获取白名单而阻塞完成

2、安全隧道日志输出

MXAppTunnel.getInstance().setLogPrintListener(new ILogPrint() {
                @Override
                public void log(String tag, String format, Object... objects) {
                    
                }

                @Override
                public void log(String tag, String msg) {
                    
                }

                @Override
                public void diagnosisLog(String msg) {
                    
                }
            });

3、安全隧道信息输出

MXAppTunnel.getInstance().setProxyInfoCallBack(new IProxyInfoCallBack() {
                @Override
                public void sendProxyPort(int httpPort, int socksPort) {
                    //安全隧道两个服务器端口
                    //1、httpPort http本地代理服务器的端口
                    //2、socksPort socks本地代理服务器的端口
                }

                @Override
                public void sendProxyWhiteList(List<String> list) {
                    //list  安全隧道白名单
                }
            });

3.4 安全隧道使用

安全隧道使用需要手动设置http和https请求的代理,仅提供两种网络请求的设置代理的方式,代理地址为xxx.x.x.x 端口在API第三条中有输出

1、HttpClient

HttpHost httpHost = new HttpHost("xxx.x.x.x", xxxx);
httpClient.getParams().setParameter(ConnRouteParams.DEFAULT_PROXY, httpHost);


2、HttpURLConnection

SocketAddress sa = new InetSocketAddress("xxx.x.x.x", xxxx);
//定义代理,此处的Proxy是源自java.net
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP,sa);
(HttpURLConnection) url.openConnection(proxy);

3、HttpsURLConnection

SocketAddress sa = new InetSocketAddress("xxx.x.x.x", xxxx);
//定义代理,此处的Proxy是源自java.net
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP,sa);
(HttpsURLConnection) url.openConnection(proxy);

注:实例代码中的端口xxxx都是假的,应该使用API第三条中输出的对应的端口

3.5 文件还提供了一个MainActivity.java Demo

package com.example.administrator.networkdemo.ui;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.example.administrator.networkdemo.R;
import com.minxing.vpn.MXAppTunnel;
import com.minxing.vpn.callback.AppTunnelInitComplete;
import com.minxing.vpn.callback.ILogPrint;
import com.minxing.vpn.callback.IProxyInfoCallBack;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "Test";

    private boolean isInit = false;
    private int httpPort1;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void initialize(View view) {

        MXAppTunnel.getInstance().setProxyInfoCallBack(new IProxyInfoCallBack() {
            @Override
            public void sendProxyPort(int httpPort, int socksPort) {
                //安全隧道两个服务器端⼝
                //1、httpPort http本地代理服务器的端⼝
                //2、socksPort socks本地代理服务器的端⼝
                httpPort1 = httpPort;
                Log.i(TAG, "httpPort: " + httpPort + "  socksPort: " + socksPort);
                Toast.makeText(MainActivity.this, "httpPort: " + httpPort + "  socksPort: " + socksPort, Toast.LENGTH_LONG).show();


            }

            @Override
            public void sendProxyWhiteList(List<String> list) {
                //list 安全隧道⽩名单
                StringBuilder stringBuilder = new StringBuilder();
                for (String s : list) {
                    stringBuilder.append(s).append("\n");
                }

                Log.i(TAG, stringBuilder.toString());
                Toast.makeText(MainActivity.this, stringBuilder.toString(), Toast.LENGTH_LONG).show();
            }
        });


        MXAppTunnel.getInstance().initAppTunnel(MainActivity.this, new AppTunnelInitComplete() {
            @Override
            public void appTunnelInitComplete() {
                //初始化完成
                Log.i(TAG, "安全隧道初始化完成");
                Toast.makeText(MainActivity.this, "安全隧道初始化完成", Toast.LENGTH_SHORT).show();

                isInit = true;
            }

            @Override
            public void appTunnelInitError(String msg) {
                //初始化失败
                Log.i(TAG, "安全隧道初始化失败: " + msg);
                Toast.makeText(MainActivity.this, "安全隧道初始化失败: " + msg, Toast.LENGTH_SHORT).show();
            }
        });

        MXAppTunnel.getInstance().setLogPrintListener(new ILogPrint() {
            @Override
            public void log(String tag, String format, Object... objects) {
                Log.i(TAG, tag + " -1- format: " + format + "  objects: " + objects);
            }

            @Override
            public void log(String tag, String msg) {
                Log.i(TAG, tag + " -2- msg: " + msg);
            }

            @Override
            public void diagnosisLog(String msg) {
                Log.i(TAG, " -3- msg: " + msg);
            }
        });
    }

    public void outputMXLog(View view) {
        if (!isInit) {
            Toast.makeText(MainActivity.this, "安全隧道未初始化", Toast.LENGTH_SHORT).show();
            return;
        }


    }

    public void outputMXInfo(View view) {
        if (!isInit) {
            Toast.makeText(MainActivity.this, "安全隧道未初始化", Toast.LENGTH_SHORT).show();
            return;
        }

        request(httpPort1);
    }

    private void request(final int port) {
//        OkHttpClient.Builder builder = new OkHttpClient.Builder();
//        builder.connectTimeout(1, TimeUnit.MINUTES);
//        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", port));
//        builder.proxy(proxy);

        new Thread(new Runnable() {
            @Override
            public void run() {
                String url = "http://xx.xx.xx.xxxx:xxxx/xxxxx/xxxxxx?params1=value1&params2=value2&params3=value3";
                URL url1 = null;
                try {
                    url1 = new URL(url);
                    SocketAddress sa = new InetSocketAddress("127.0.0.1", port);
                    //定义代理,此处的Proxy是源⾃java.net
                    Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP,sa);
                    HttpURLConnection httpURLConnection =(HttpURLConnection) url1.openConnection(proxy);
                    httpURLConnection.setRequestMethod("POST");
                    //得到响应码
                    int responseCode = httpURLConnection.getResponseCode();
                    if(responseCode == HttpURLConnection.HTTP_OK){
                        //得到响应流
                        InputStream inputStream = httpURLConnection.getInputStream();
                        //将响应流转换成字符串
                        //String result = is2String(inputStream);//将流转换为字符串。
                        //Log.d("kwwl","result============="+result);
                    }
                    InputStream inputStream = httpURLConnection.getInputStream();
                    InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                    BufferedReader reader = new BufferedReader(inputStreamReader);

                    String  tempLine;
                    StringBuilder resultBuffer = new StringBuilder();
                    while ((tempLine = reader.readLine()) != null) {
                        resultBuffer.append(tempLine);
                    }
                    Log.i(TAG, " -5- " + resultBuffer.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.i(TAG, " -4- " + e.getMessage());
                }
            }
        }).start();




//        Request request = new Request.Builder().url(url).get().build();
//        Call call = builder.build().newCall(request);
//        call.enqueue(new Callback() {
//            @Override
//            public void onFailure(Call call, IOException e) {
//                final String s = e.getMessage();
//                Log.e(TAG, s);
//                runOnUiThread(new Runnable() {
//                    @Override
//                    public void run() {
//                        Toast.makeText(MainActivity.this, s, Toast.LENGTH_LONG).show();
//                    }
//                });
//            }
//
//            @Override
//            public void onResponse(Call call, Response response) throws IOException {
//                final String s = response.body().string();
//                Log.e(TAG, s);
//                runOnUiThread(new Runnable() {
//                    @Override
//                    public void run() {
//                        Toast.makeText(MainActivity.this, s, Toast.LENGTH_LONG).show();
//                    }
//                });
//            }
//        });
    }
}

3.开发

3.1原生项目运行

为了开发原生插件,那么建立原生的项目工程这是必不可少的条件,为了方便开发这里直接使用了UNI-SDK文件夹中的UniPlugin-Hello-AS这个工程,直接拖入到Android Studio(以下简称AS)点击文件-新建-Import Project

导入项目

选择项目

选中UniPlugin-Hello-AS后点击确定,整个目录结构就出来了
目录

现在点击运行按钮让示例项目跑起来。
运行示例项目

Gradle Build Running

UNI示例

3.2 插件开发

首先跟着Android原生插件开发教程,一步一步往下进行。
JDK安装和AS的安装就不写了,这些没啥大的问题,随便百度一个相关文章都能跑得起来

注意

根据官方的注意,总体来说,我们在本地开发的时候注意配置gradletools.build:gradle
点击 文件-项目结构 查看我们的版本
项目结构

项目结构

安装官方的步骤,新建一个Module,在此之前我们先把项目结构转换Project类型的结构,然后点击 文件-新建-New Module
New Module

选择library
library

配置包名以及Module名称,点击完成(Finish)
Module

Module生成完成

按照官方的布置,新建完成了要去配置刚创建的Modulebuild.gradle信息,注意是Module的而不是app
Module-build.gradle

新建完成可能会出现如下的错误信息
依赖报错

Version 28 (intended for Android Pie and below) is the last version of the legacy support library, so we recommend that you migrate to AndroidX libraries when using Android Q and moving forward. The IDE can help with this: Refactor > Migrate to AndroidX... less... (Ctrl+F1) 
Inspection info:There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion).  Issue id: GradleCompatible

具体的解决办法可以去百度,但是我发现这貌似仅仅是个警告,反正最后没有影响我的编译、运行和使用。

首先按照第三方SDK的配置说明,在资源文件中配置好安全隧道服务器的地址(注意是在main文件夹下)
参考如uniplugin_component等其他模块的配置格式新建res文件。

image.png

RES

image.png

配置value

由于我们的网络隧道是做到Module插件模块中的,所以我们讲MXSDK放在Module的libs中进行引用。

plugins {
    id 'com.android.library'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

//导入aar需要的配置
repositories {
    flatDir {
        dirs 'libs' //指定arr的导入路径,默认是当前Module的libs目录
    }
}


dependencies {
    /**引入uniSDK必要的依赖开始**/
    //以com.等开头的是第三方的远程依赖库
    compileOnly 'com.android.support:recyclerview-v7:28.0.0'
    compileOnly 'com.android.support:support-v4:28.0.0'
    compileOnly 'com.android.support:appcompat-v7:28.0.0'
    compileOnly 'com.alibaba:fastjson:1.1.46.android'
    compileOnly fileTree(include: ['uniapp-v8-release.aar'], dir: '../app/libs')  //这种引入方式 ../app/libs  指定了app目录下的模块的rarr文件
    /**引入uniSDK必要的依赖结束**/
    /**安全隧道的aar引用的第三方工程开始**/
    implementation 'org.bouncycastle:bcprov-jdk15on:1.55'
    implementation 'org.apache.commons:commons-lang3:3.4'
    implementation 'org.slf4j:slf4j-api:1.7.21'
    //引入MX本地arr文件(根据dirs 'libs'这个路径直接引用当前Module-libs目录)
    implementation(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar') 
    /**安全隧道的aar引用的第三方工程结束**/
}

接入第三方arr

接入完毕,run一下,发现没抱错,下面开始定制化的开发。
新建一个类
image.png

按照官方的步骤这个类需要继承UniModule,按照DEMO里面的写法,具体如下

package com.example.kysin;

import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.minxing.vpn.MXAppTunnel;
import com.minxing.vpn.callback.AppTunnelInitComplete;
import com.minxing.vpn.callback.ILogPrint;
import com.minxing.vpn.callback.IProxyInfoCallBack;

import java.util.List;

import io.dcloud.feature.uniapp.common.UniModule;

public class tunnel extends UniModule {
    private static final String TAG = "Test";
    private boolean isInit = false;
    private int httpPort1;
    
    public void initialize(View view) {
        MXAppTunnel.getInstance().setProxyInfoCallBack(new IProxyInfoCallBack() {
            @Override
            public void sendProxyPort(int httpPort, int socksPort) {
                //安全隧道两个服务器端⼝
                //1、httpPort http本地代理服务器的端⼝
                //2、socksPort socks本地代理服务器的端⼝
                httpPort1 = httpPort;
                Log.i(TAG, "httpPort: " + httpPort + "  socksPort: " + socksPort);
                Toast.makeText(MainActivity.this, "httpPort: " + httpPort + "  socksPort: " + socksPort, Toast.LENGTH_LONG).show();


            }

            @Override
            public void sendProxyWhiteList(List<String> list) {
                //list 安全隧道⽩名单
                StringBuilder stringBuilder = new StringBuilder();
                for (String s : list) {
                    stringBuilder.append(s).append("\n");
                }

                Log.i(TAG, stringBuilder.toString());
                Toast.makeText(MainActivity.this, stringBuilder.toString(), Toast.LENGTH_LONG).show();
            }
        });


        MXAppTunnel.getInstance().initAppTunnel(MainActivity.this, new AppTunnelInitComplete() {
            @Override
            public void appTunnelInitComplete() {
                //初始化完成
                Log.i(TAG, "安全隧道初始化完成");
                Toast.makeText(MainActivity.this, "安全隧道初始化完成", Toast.LENGTH_SHORT).show();

                isInit = true;
            }

            @Override
            public void appTunnelInitError(String msg) {
                //初始化失败
                Log.i(TAG, "安全隧道初始化失败: " + msg);
                Toast.makeText(MainActivity.this, "安全隧道初始化失败: " + msg, Toast.LENGTH_SHORT).show();
            }
        });

        MXAppTunnel.getInstance().setLogPrintListener(new ILogPrint() {
            @Override
            public void log(String tag, String format, Object... objects) {
                Log.i(TAG, tag + " -1- format: " + format + "  objects: " + objects);
            }

            @Override
            public void log(String tag, String msg) {
                Log.i(TAG, tag + " -2- msg: " + msg);
            }

            @Override
            public void diagnosisLog(String msg) {
                Log.i(TAG, " -3- msg: " + msg);
            }
        });
    }
}    

这里IDE会提示“不能解决符号MainActivity

MainActivity

这里就涉及到了 "当前的上下文环境",按照传统的Activity方法,我们可以直接集成Activity然后写Activity.this或者通过getApplicationContext来得到执行的上下文。但是官方文档写到:

  • Activity的获取方式。通过mUniSDKInstance.getContext()强转Activity。建议先instanceof Activity判断一下再强转
    所以这里我们改造下封装的方法,用mUniSDKInstance.getContext()代替Activity.this写法
package com.example.kysin;

import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.minxing.vpn.MXAppTunnel;
import com.minxing.vpn.callback.AppTunnelInitComplete;
import com.minxing.vpn.callback.ILogPrint;
import com.minxing.vpn.callback.IProxyInfoCallBack;

import java.util.List;

import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.common.UniModule;

public class Tunnel extends UniModule {
    private static final String TAG = "Test";
    private boolean isInit = false;
    private int httpPort1;

    @UniJSMethod(uiThread = false)
    public void initialize() {
        Log.i(TAG, "11111");
        MXAppTunnel.getInstance().setProxyInfoCallBack(new IProxyInfoCallBack() {
            @Override
            public void sendProxyPort(int httpPort, int socksPort) {
                //安全隧道两个服务器端⼝
                //1、httpPort http本地代理服务器的端⼝
                //2、socksPort socks本地代理服务器的端⼝
                httpPort1 = httpPort;
                Log.i(TAG, "httpPort: " + httpPort + "  socksPort: " + socksPort);
                Toast.makeText((Activity)mUniSDKInstance.getContext(), "httpPort: " + httpPort + "  socksPort: " + socksPort, Toast.LENGTH_LONG).show();


            }

            @Override
            public void sendProxyWhiteList(List<String> list) {
                //list 安全隧道⽩名单
                StringBuilder stringBuilder = new StringBuilder();
                for (String s : list) {
                    stringBuilder.append(s).append("\n");
                }

                Log.i(TAG, stringBuilder.toString());
                Toast.makeText((Activity)mUniSDKInstance.getContext(), stringBuilder.toString(), Toast.LENGTH_LONG).show();
            }
        });


        MXAppTunnel.getInstance().initAppTunnel((Activity)mUniSDKInstance.getContext(), new AppTunnelInitComplete() {
            @Override
            public void appTunnelInitComplete() {
                //初始化完成
                Log.i(TAG, "安全隧道初始化完成");
                Toast.makeText((Activity)mUniSDKInstance.getContext(), "安全隧道初始化完成", Toast.LENGTH_SHORT).show();

                isInit = true;
            }

            @Override
            public void appTunnelInitError(String msg) {
                //初始化失败
                Log.i(TAG, "安全隧道初始化失败: " + msg);
                Toast.makeText((Activity)mUniSDKInstance.getContext(), "安全隧道初始化失败: " + msg, Toast.LENGTH_SHORT).show();
            }
        });

        MXAppTunnel.getInstance().setLogPrintListener(new ILogPrint() {
            @Override
            public void log(String tag, String format, Object... objects) {
                Log.i(TAG, tag + " -1- format: " + format + "  objects: " + objects);
            }

            @Override
            public void log(String tag, String msg) {
                Log.i(TAG, tag + " -2- msg: " + msg);
            }

            @Override
            public void diagnosisLog(String msg) {
                Log.i(TAG, " -3- msg: " + msg);
            }
        });
    }
}

3.3 在原生APP里进行插件测试

写完之后需要进行隧道初始化的测试
要在原生工程中实现这个Module的调用测试,需要进行下步骤:

  • 将原生插件在通过dcloud_uniplugins.json进行声明和Module引入
  • 新建一个自定义的UNI项目,并编写对应的调用方法

所以我们第一步是先去原生工程中进行插件的声明,按照官方文档描述:
UniPlugin-Hello-AS工程下app-src-main-assets/dcloud_uniplugins.json文件。 在moudles节点下 添加你要注册的ModuleComponent

dcloud_uniplugins.json

{
  "nativePlugins": [
    {
      "plugins": [
        {
          "type": "module",
          "name": "TestModule",
          "class": "io.dcloud.uniplugin.TestModule"
        }
      ]
    },
    {
      "plugins": [
        {
          "type": "component",
          "name": "myText",
          "class": "io.dcloud.uniplugin.TestText"
        }
      ]
    },
    {
      "hooksClass": "",
      "plugins": [
        {
          "type": "module",
          "name": "DCloud-RichAlert",
          "class": "uni.dcloud.io.uniplugin_richalert.RichAlertModule"
        }
      ]
    },
    {
      "plugins": [
        {
          "type": "module",
          "name": "test-Module", //这个名字可以随便取,只要和UNI项目中requireNativePlugin的相同就行
          "class": "com.example.kysin.Tunnel"
        }
      ]
    }
  ]
}

然后还要去app模块的build.gradle去添加新增的Moudle插件

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion '28.0.3'
    defaultConfig {
        applicationId "com.HBuilder.UniPlugin"
        minSdkVersion 21
        targetSdkVersion 26 //建议此属性值设为21 io.dcloud.PandoraEntry 作为apk入口时   必须设置 targetSDKVersion>=21 沉浸式才生效

        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        ndk {
            abiFilters 'x86','armeabi-v7a'
        }
    }
    buildTypes {
        release {
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            zipAlignEnabled true
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    //使用uniapp时,需复制下面代码
    /*代码开始*/
    aaptOptions {
        additionalParameters '--auto-add-overlay'
        //noCompress 'foo', 'bar'
        ignoreAssetsPattern "!.svn:!.git:.*:!CVS:!thumbs.db:!picasa.ini:!*.scc:*~"
    }
    /*代码结束*/
}
repositories {
    flatDir {
        dirs 'libs'
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation fileTree(dir: 'libs', include: ['*.aar'])

    implementation "com.android.support:support-v4:28.0.0"
    implementation "com.android.support:appcompat-v7:28.0.0"

    /*uniapp所需库-----------------------开始*/
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    implementation 'com.facebook.fresco:fresco:1.13.0'
    implementation "com.facebook.fresco:animated-gif:1.13.0"
    /*uniapp所需库-----------------------结束*/
    // 基座需要,必须添加
    implementation 'com.github.bumptech.glide:glide:4.9.0'
    implementation 'com.alibaba:fastjson:1.1.46.android'

    // 添加uni-app插件
    implementation project(':uniplugin_component')
    implementation project(':uniplugin_module')
    implementation project(':uniplugin_richalert')
    // 添加自定义插件
    implementation project(':testModule')  //和你新建Module的文件夹名字保持一致
}

testModule模块Gradle.builde中本地的arr文件引入,我总结了以下几种情况

//app工程libs如没有这个arr文件会报Coulad not resolve:MXSocksCore-release_6.8.0_stable_socks_jar_160:
//app工程libs如没有这个arr文件会报Duplicate class com.google.common.annotations.Beta found in modules MXSocksCore-release_6.8.0_stable_socks_jar_160-runtime(:MXSocksCore-release_6.8.0_stable_socks_jar_160:) adn MXSocksCore-release_6.8.0_stable_socks_jar_160-runtime(MXSocksCore-release_6.8.0_stable_socks_jar_160.arr)
//app无法编译运行
implementation(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar')

//情况同上
api(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar')

//app工程libs如没有这个arr文件会报警告,但是程序会正常启动,但是自定义的Module事件无法触发
//Missing class:com.mingxing.vqn.callback.ApptunnellnitCompelet
//Missing class: com.minxing.vpn.callback.IProxyInfoCallBack
//Missing class: com.minxing.vpn.callback.ILogPrint
//app工程libs有这个arr文件才能不报/Missing class,能正常运行
compileOnly(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar')

//app工程libs如没有这个arr文件也能正常运行
//但是打包arr时报错:Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies would not be packaged in the resulting AAR. Previous versions of the Android Gradle Plugin produce broken AARs in this case too (despite not throwing this error). The following direct local .aar file dependencies of the :testModule project caused this error: C:\Users\jnp\Desktop\jianshu\Android-SDK@3.1.4.80686_20210305\UniPlugin-Hello-AS\testModule\libs\MXSocksCore-release_6.8.0_stable_socks_jar_160.aar
api fileTree(include: ['MXSocksCore-release_6.8.0_stable_socks_jar_160.aar'], dir: './libs')

//app工程libs如没有这个arr文件也能正常运行
//但是打包arr时报错:Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies would not be packaged in the resulting AAR. Previous versions of the Android Gradle Plugin produce broken AARs in this case too (despite not throwing this error). The following direct local .aar file dependencies of the :testModule project caused this error: C:\Users\jnp\Desktop\jianshu\Android-SDK@3.1.4.80686_20210305\UniPlugin-Hello-AS\testModule\libs\MXSocksCore-release_6.8.0_stable_socks_jar_160.aar
implementation fileTree(dir: 'libs', include: ['*.aar'])

基于此本文只能选择compileOnly(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar')方式进行第三方arr插件引用,这里需要把第三方的arr包放1份到app模块的libs文件夹下

plugins {
    id 'com.android.library'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

//导入aar需要的配置
repositories {
    flatDir {
        dirs 'libs' //指定arr的导入路径,默认是当前Module的libs目录
    }
}


dependencies {
    /**引入uniSDK必要的依赖开始**/
    //以com.等开头的是第三方的远程依赖库
    compileOnly 'com.android.support:recyclerview-v7:28.0.0'
    compileOnly 'com.android.support:support-v4:28.0.0'
    compileOnly 'com.android.support:appcompat-v7:28.0.0'
    compileOnly 'com.alibaba:fastjson:1.1.46.android'
    compileOnly fileTree(include: ['uniapp-v8-release.aar'], dir: '../app/libs')  //这种引入方式 ../app/libs  指定了app目录下的模块的arr文件
    /**引入uniSDK必要的依赖结束**/
    /**安全隧道的aar引用的第三方工程开始**/
    implementation 'org.bouncycastle:bcprov-jdk15on:1.55'
    implementation 'org.apache.commons:commons-lang3:3.4'
    implementation 'org.slf4j:slf4j-api:1.7.21'
    //引入MX本地arr文件(根据dirs 'libs'这个路径直接引用当前Module-libs目录)
    compileOnly(name: 'MXSocksCore-release_6.8.0_stable_socks_jar_160', ext: 'aar')
    /**安全隧道的aar引用的第三方工程结束**/
}

然后去新建一个UNI项目,编写调用原生插件的代码

UNI项目

编写完成后,点击 发行-原生APP本地打包-生成本地打包APP资源
本地打包

把原生工程中app-src-main-assets-apps目录下的__UNI__BCEC007这整个文件删除,然后把你打包完成以新的APPID命名的文件粘贴到刚刚删除干净的apps目录下这里以__UNI__911FD69为例子。
__UNI__911FD69

然后去app-src-main-assets-data-dcloud_control.xml中修改appid为你刚刚复制过来的那个appid
修改APPid

点击run,然后点击app首页的图标调用原生的方法
看看logcat的输入日志

logcat

以上可以看到能够正常的进行调用。插件测试成功

3.3 插件打包

插件打包第一步还是很简单的,点击IDE右侧的Gradle图标,找到uniPlugin-Hello-AS-testModule-Tasks-other-assembleRelease,双击assembleRelease

assembleRelease

testModule-build-outputs-arr文件夹找到我们的testModule-release.arr
按照官方文档生成uni-app插件
文件夹格式及package.json

打包之前一定要记得去manifest.json选择本地的原生插件,你会发现插件名就是之前package.json中的name字段。
选择原生插件

打包的时候选择 运行-运行到手机或模拟器-制作自定义调试基座,等待打包完成点击运行
测试成功

4 原生插件深入编写

由于笔者的项目是通过网络隧道的方式进行请求,所以UNI.request这种API就完全失效。必须在原生插件编写postgetupload等方法,才能通过第三方SDK的网络隧道进行数据通信。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容