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

2023-11-17

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'
复制代码
  1. 安全隧道服务器的地址的配置,在资源文件中配置好安全隧道服务器的地址
<string name="client_vpn_server_host">xxx.xxx.xxx.xx:xxxx</string>
复制代码
  1. 安全隧道在初始化后会自动获取白名单(异步动作),获取成功后会持久化到本地,因此,首次初始化安全隧道的时候可能相对会比较慢(因为有网络请求)

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 Demo

文件还提供了一个MainActivity.javaDemo

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后点击确定,整个目录结构就出来了

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

3.2 插件开发

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

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

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

选择library

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

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

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

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文件。

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

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引用的第三方工程结束**/
}
复制代码

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

按照官方的步骤这个类需要继承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

这里就涉及到了 "当前的上下文环境",按照传统的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节点下 添加你要注册的Module或 Component

{
  "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项目,编写调用原生插件的代码

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

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

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

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

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

3.4 插件打包

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

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

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

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

 本文引自 稀土掘金网的扶不起的蝌蚪。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

UNI APP---Android端原生插件开发实战(一) 的相关文章

  • EditText 中的验证允许 IP 或 Web Url 主机

    我需要对我的 EditText 进行验证 以便它允许我输入有效的 IP 地址格式 即示例 132 0 25 225 or 网址格式 www 例如 www example com 逻辑是 如果用户首先输入任何数值 则验证 IP 将执行操作 否
  • 已发布的 Flutter 应用程序在启动时崩溃

    编辑 此问题的解决方案是将您的 flutter 版本升级到较新的 dev 版本 then 1 7 0 您还可以上传单独的 APK 版本 但我个人不喜欢这个选项 请确保您没有从 flutter github 开发存储库下载 错误的构建 因为那
  • 如何检测和管理来电(Android)?

    我想创建一个应用程序 可以检测来电并在一定数量的蜂鸣声 响铃 后启动我的自定义活动 我的意思是在 2 或 3 或 5 声蜂鸣声 响铃 后我的activity被触发 我该怎么做 Thanks 我认为您无法计算自来电开始以来电话响了多少次 无法
  • Android中如何使用洪水填充算法?

    我是Android编程新手 最近尝试编写一个简单的应用程序 仅供练习 在这个中 我想在用户点击时为图像着色 但我不知道如何开始 我读过不同的主题 其中提到使用 洪水填充 算法 我在网上找到了它 但我不知道如何将它放入我的简单应用程序中 我找
  • 如何在出现“无法解析放置符号”错误时向哈希图添加键和值

    我正在与安卓工作室 https en wikipedia org wiki Android Studio1 4 1 我刚刚创建了一个 Hashmap 并正在遵循有关如何填充和操作它的教程 Java 语言 但是 我收到 无法解析符号放置 错误
  • 应用程序实例是否始终在任何活动之前创建?

    在 Android 中 您可以通过扩展 Application 类并在 Manifest 中声明名称来提供您自己的 Application 类实现 我的问题是 这个实现是否总是在初始活动之前创建 或者活动可以在应用程序实例有时间创建之前启动
  • Android Studio:lambda 不起作用[重复]

    这个问题在这里已经有答案了 当尝试使用 lambda 表达式时 我遇到了一些 Gradle 构建错误 错误 41 100 错误 source 1 7 不支持 lambda 表达式 使用 source 8 或更高版本来启用 lambda 表达
  • Android Studio - 错误:未捕获翻译错误:com.android.dx.cf.code.SimException:本地 0001:无效

    我刚刚使用 Android Studio 设置了一台新计算机 并从 bitbucket 导入了我的项目 问题是我现在在尝试构建项目时遇到此错误 信息 Gradle 任务 app clean app generateDebugSources
  • React Native Android 发布 apk 是调试,而不是发布

    我有一个现有的 Android 应用程序 我已根据以下内容将 React Native v0 30 活动添加到项目中docs http facebook github io react native releases next docs i
  • 访问角落里的存储

    我能找到的与文件存储有关的最接近文档的是这个帖子 http nookdeveloper zendesk com entries 20257971 updated what are the size constraints on my app
  • Android - 当不在栏顶部时推送通知空白

    我在使用 Android 推送通知时遇到一个小问题 如果有 3 个通知 并且只有其中一个显示标题和消息 位于酒吧顶部的那个 如果有人知道可能是什么问题 请告诉我 请参阅此链接上的图像 这就是我接收通知的方式http postimg org
  • 如何在 Firebase 远程配置中从 JSON 获取值

    我是 Android 应用开发和 Firebase 的新手 我想知道如何获取存储在 Firebase 远程配置中的 JSONArray 文件中的值 String 和 Int 我使用 Firebase Remote Config 的最终目标是
  • window.onbeforeunload 在 Android Chrome 上不会触发 [alt.解决方案?]

    我开发了一个简单的聊天应用程序 我正在使用 window onbeforeunload当有人关闭选项卡 浏览器时 基本上是当用户离开房间时 通知其他用户 这是我的代码 scope onExit function scope chatstat
  • android 中camera.setParameters 失败

    我已将相机功能包含在我的应用程序中 我还在市场上推出了该应用程序 我从一位用户那里收到一条错误消息 称他在打开相机时遇到错误 我已经在 2 1 的设备上测试了该应用程序 我从用户那里得到的错误是使用 Nexus One 它主要运行 2 2
  • 使用嵌套的 hashmap 参数发送 volley 请求

    我正在使用 android volley 框架向我的服务器发送 jsonobject 请求 get 请求工作正常 现在我想发送一个带有请求参数的 post 请求 该请求参数是嵌套的 hashmap 我重写 getparams 方法 但它期望
  • 不显示 WRITE_EXTERNAL_STORAGE 的权限对话框

    I want to download a file using DownloadManager And DownloadManager wants to WRITE EXTERNAL STORAGE permission I have in
  • Android:打开和关闭SQLite数据库

    我正在开发Android应用程序 我经常在其中访问本地数据库 该数据库可以从不同的主题访问 因此我遇到了数据库的协调问题 我使用以下open and close method public void open mDb mDbHelper g
  • 内部存储的安全性如何?

    我需要的 对于 Android 我需要永久保存数据 但也能够编辑 并且显然是读取 它 用户不应访问此数据 它可以包含诸如高分之类的内容 用户不得对其进行编辑 我的问题 我会 并且已经 使用过Internal Storage 但我不确定它实际
  • 检测 ListView(或 ScrollView)内的滚动位置

    我正在构建一个聊天室应用程序 其中每 X 秒就会轮询一次新事件 每次发生这种情况时 此代码都会使用新数据更新 RoomAdapter ArrayAdapter 的自定义子类 并将其滚动到底部 RoomAdapter adapter Room
  • Android - iphone 风格 tabhost [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi

随机推荐

  • Python3快速入门(六)——Python3面向对象

    Python3快速入门 六 Python3面向对象 一 面向对象技术简介 1 面向对象简介 面向对象编程 Object Oriented Programing OOP 是一种编程思想 OOP把对象当成程序的一个基本单元 一个对象包含数据和操
  • springboot配置RabbitMQ时,本地正常,远程连接RabbitMQ却无法生成Queue,Exchange

    RabbitMQ 在springboot中 无法正确操作远程服务器的问题 问题描述 如果你已经确定你的IP正确 端口号也是5672 账号没有使用默认的guest账号 但是还是没有连接上远程的RabbitMQ 可能是你的springboot配
  • int8,FLOPS,FLOPs,TOPS 等具体含义

    1 定义 算力的计量单位FLOPS Floating point operations per second FLOPS表示每秒浮点的运算次数 具体使用时 FLOPS前面还会有一个字母常量 例如TFLOPS PFLOPS 这个字母T P代表
  • 翻页特效原理

    http www open open com lib view 1326265166952 实现真实的翻页效果 为了能在翻页的过程中看到下一页的内容 在翻页之前必须准备两张页面 一张是当前页 另一张是下一页 翻页的过程就是对这两张页面的剪切
  • SAGE(SAGEMATH)密码学基本使用方法

    求逆元 inv inverse mod 30 1373 print 30 inv 1373 1 扩展欧几里得算法 d u v xgcd 20 30 print d 0 u 1 v 2 format d u v d 10 u 1 v 1 孙子
  • win10系统显示打印机未连接到服务器,解决win10提示“Windows无法连接到打印机”的方法...

    打印机是我们办公室中必备的设备 如今各种打印方式也是层出不穷 最近有用户在使用win10系统进行打印的时候遇到了这样的提示信息 windows 无法连接到打印机 在更详细的信息提示界面中已经告诉我们是由于本地打印店额后台服务程序没有启用导致
  • void、void 的使用

    void的使用 1 对函数返回值的限定 函数无返回值 void test int a int num a 2 void 限定函数的返回值为任意类型的指针 void test int a 5 int p a return p 3 对函数参数的
  • ASCII unicode utf8 编码、解码的那些事

    ASCII unicode gbk utf8 编码 解码的那些事 对应编码这块一直处于一种懵懵懂懂的状态 有的时候去查了资料 当下理解了 过一段时间又遗忘了 今天又重新查阅了一番资料 记录一下所感所悟 阮一峰老师 关于编码的总结 1 ASC
  • 【学一点儿前端】box-sizing以及flex:1的解释

    box sizing box sizing 是一种用于控制CSS盒子模型行为的CSS属性 它的作用是指定元素的宽度和高度的计算方式 以确定元素的总尺寸 具体来说 box sizing 可以有两个可能的取值 1 content box 默认值
  • hive加载数据权限报错

    前提 上传数据至hdfs 的 user root 下 创建了hive的orc表 准备load数据 创建了临时的ordertmp的textfile格式表 后面用insert overwrite进目标表 执行load data 从 user r
  • 2020-06-07

    Arcgis engine实现栅格运算功能 有大佬能帮助一下吗 arcgis是10 2版本的 环境是vs2015
  • linux的-Mtime 命令

    我在写shell脚本的时候 定时删除一些文件的时候 也经常用得到 mtime这个参数 所以打算好好看看 把它弄明白一下 man find里的解释 mtime n File s data was last modified n 24 hour
  • java ee 运行环境_EE质量检查:为我们的网站开发和运行自动测试

    java ee 运行环境 Introduction 介绍 This article is the last of three articles that explain why and how the Experts Exchange QA
  • 【Python_requests学习笔记(九)】基于requests和threading模块实现多线程爬虫

    基于requests和threading模块实现多线程爬虫 前言 此篇文章中介绍基于 requests 和 threading 模块实现多线程爬虫 并以 抓取Cocos中文社区中 热门主题下的帖子名称及id数据 为例进行讲解 因主要介绍如何
  • 华大单片机HC32L130 / HC32L136 / HC32F030 系列硬件开发指南

    适用对象 系列 产品型号 HC32L130 HC32L130E8PA HC32L130F8UA HC32L130J8TA HC32L130J8UA HC32L136 HC32L136J8TA HC32L136K8TA HC32L130 HC
  • 三角函数常见基本公式

    定义式 图形 正弦 sin 余弦 cos 正切 tan或tg 余切 cot或ctg 正割 sec 余割 csc 函数关系 商数关系 倒数关系 平方关系 和差角公式 二角和差公式 三角和公式 积化和差公式 倍角公式 二倍角公式 三倍角公式 四
  • centos7初始化操作-时间同步/网络防火墙/本地源/ssh/等

    一 chrony安装及配置 验证 说明 协议 NTP协议 时间同步必要场景 集群 日志 加密协议等 相关文章 https blog csdn net weixin 44515412 article details 106875753 1 安
  • 使用Lodop控件打印表单和二维码

    文章目录 1 了解Lodop 1 1Lodop的定义 1 2Lodop主要函数 1 3Lodop的下载 2 在页面中引入Lodop 3 支持的浏览器 4 Lodop的应用 4 1使用Lodop打印表单 4 2打印二维码 1 了解Lodop
  • Python学习笔记(十二)————判断语句相关

    目录 1 布尔类型的定义 2 比较运算符 3 if语句 4 if else语句 5 if elif else语句 1 布尔类型的定义 布尔类型的字面量 True 表示真 是 肯定 False 表示假 否 否定 布尔类型的数据 不仅可以通过定
  • UNI APP---Android端原生插件开发实战(一)

    1 前言 最近一个项目要求我们的产品必须走网络隧道 并且提供了对应的SDK 很明显只能通过原生开发的方式才能实现这个流程 笔者没有做过原生开发 也没有学过java 所以也踩了不少坑啊 花了两天时间总算完成任务 今天系统的总结下步骤 由于是根