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¶ms2=value2¶ms3=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的安装就不写了,这些没啥大的问题,随便百度一个相关文章都能跑得起来
根据官方的注意,总体来说,我们在本地开发的时候注意配置
gradle
和tools.build:gradle
点击 文件-项目结构 查看我们的版本
安装官方的步骤,新建一个
Module
,在此之前我们先把项目结构转换Project
类型的结构,然后点击 文件-新建-New Module
选择library
配置包名以及
Module
名称,点击完成(Finish
)按照官方的布置,新建完成了要去配置刚创建的
Module
的build.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放在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引用的第三方工程结束**/
}
接入完毕,
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.3 插件打包
插件打包第一步还是很简单的,点击IDE右侧的Gradle
图标,找到uniPlugin-Hello-AS-testModule-Tasks-other-assembleRelease
,双击assembleRelease
在
testModule-build-outputs-arr
文件夹找到我们的testModule-release.arr
按照官方文档生成
uni-app
插件打包之前一定要记得去
manifest.json
选择本地的原生插件,你会发现插件名就是之前package.json
中的name
字段。打包的时候选择 运行-运行到手机或模拟器-制作自定义调试基座,等待打包完成点击运行
4 原生插件深入编写
由于笔者的项目是通过网络隧道的方式进行请求,所以UNI.request
这种API
就完全失效。必须在原生插件编写post
、get
、upload
等方法,才能通过第三方SDK的网络隧道进行数据通信。