AOne Android SDK接入文档
1、在工程中导入aar包
下载好aar,并在libs下导入aar包,如下图所示:
2、修改build.gradle
- 联系零信任对接人员获取到 aOne 授权网页的域名、aOne授权网页的poolId、aOne授权网页的appId,aOne授权网页的scheme,并分别填入build.gradle中
- 在build.gradle的 dependencies 中加入 implementation fileTree(include: ['*.aar'], dir: 'libs')
- 执行android studio的rebuild,生成BuildConfig
3、修改AndroidManifest
在登录页增加AOneId授权登录页面的
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Secmobileandroidsdk">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="${aOneScheme}" />
</intent-filter>
</activity>
4、继承VPNApplication
app的Application类继承VPNApplication,如下所示:
/**
* 需要将自己的Application 继承Sdk的VPNApplication
*/
public class AppApplication extends VPNApplication {
}
PS: 这部完成之后,可以运行APP,过滤SDK的tag 如果打印出来当前的版本号,则表示SDK集成成功。
5、AOne 账号登录
此部分参考com.ctcdn.sdkdemo.login包底下的代码,代码介绍如下:
代码路径 | 详细功能介绍 |
---|---|
com.ctcdn.sdkdemo.login.activity | 登录页,包含了登录的所有流程 |
com.ctcdn.sdkdemo.login.exception | 自定义的接口异常类 |
com.ctcdn.sdkdemo.login.model | 接口的请求和解析的一些模型类 |
com.ctcdn.sdkdemo.login.utils | 工具类 |
com.ctcdn.sdkdemo.login.AOneLoginService | 使用了retrofit的依赖,这里定义了登录的一些接口 |
com.ctcdn.sdkdemo.login.ApiResultCallAdapter | Aone接口解析适配器 |
com.ctcdn.sdkdemo.login.RetrofitManager | http管理类 |
-
账号密码登录流程
-
短信登陆流程
6、AOne 第三方浏览器身份验证和授权
在用户同意隐私协议后,进入登录页面,在Sdk 初始化之前需要去第三方浏览器进行进行AOne 身份验证,这里提供拼接授权地址和身份验证调用的方法,方法如下:
/**
* 这里需要填上对接方提供的 aOne 授权网页的域名
*/
public String aOneUrl = BuildConfig.aOneUrl;
/**
* 这里需要填上对接方提供的 aOne 授权网页的poolId
*/
public String aOnePoolId = BuildConfig.aOnePoolId;
/**
* 这里需要填上对接方提供的 aOne 授权网页的appId
*/
public String aOneAppId = BuildConfig.aOneAppId;
/**
* 获取AOne授权链接
*
* @return AOne 授权链接
*/
public String GetAOneIdUrl(String aOneUrl, String aOnePoolId, String aOneAppId) {
if (TextUtils.isEmpty(aOnePoolId)) {
return "";
}
return (aOneUrl + "/" + aOnePoolId + "/app/" + aOneAppId).trim();
}
/**
* aOne网页授权示例
* 描述: 这里会跳转到第三方浏览器, 授权成功后会弹出
*/
public void aOneLogin(View v) {
String aOneIdUrl = GetAOneIdUrl(BuildConfig.AOnePoolId);
if (TextUtils.isEmpty(aOneIdUrl)) {
Toast.makeText(MainActivity.this, getText(R.string.aone_id_url), Toast.LENGTH_SHORT).show();
return;
}
Uri parse;
try {
parse = Uri.parse(aOneIdUrl);
Intent intent = new Intent(Intent.ACTION_VIEW, parse);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} catch (Exception e) {
Toast.makeText(MainActivity.this, getResources().getString(R.string.aone_id_url_invalid, e.getMessage()), Toast.LENGTH_SHORT).show();
}
}
7、增加Sdk的初始化方法
在用户同意隐私协议后,进入登录页面,需要在Activity的OnCreate方法中获取Intent,并通过intent的getQueryParameter的方法,获取到授权回调回来的id_token字段的值,这个值就是AoneId授权成功回来的token,传入SdkInit的入参,完成Sdk的初始化方法,步骤如下:
- 在登录页的onCreate方法中增加获取AOne网页授权回来的token的方法,如下所示:
/** * 这个方法是Aone网页授权回调后,获取到SdkInit的所需要的token的方法 * 调用线程:主线程 */ public void aoneAuthBack() { // 获取 Intent 中的数据 Intent intent = getIntent(); if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { Uri data = intent.getData(); if (data != null) { String idToken = data.getQueryParameter("id_token"); Log.i(TAG, "[AOne] id_token:" + idToken); Map<String, String> params = new HashMap<>(); params.put(Const.INIT_PARAMS.AONE_ID_TOKEN, idToken); SdkInit(this,params); } else { Toast.makeText(MainActivity.this, getText(R.string.u_should_auth_by_web_first), Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(MainActivity.this, getText(R.string.u_should_auth_by_web_first), Toast.LENGTH_SHORT).show(); } }
- 把这个token传入SdkInit方法中,注意这里有获取ip的行为,需要在用户同意隐私协议后才能调用。
- 初始化接口会回调你们一个 token , 需要拿着这个token 下次来初始化,如果在有效期内, 初始化成功, 会在返回给你们一个新的 token ,如果有效期过期, 会通过回调告诉你们有效期过期了, 重新走登录流程。
/** * 方法名:SdkInit * 描述:这个方法用于Sdk初始化,入参只需要AOneId网页授权回调回来的token * 使用前置条件:AoneId网页授权成功后,获取到token,然后传入这个方法,注意这里有获取ip的行为,需要在用户同意隐私协议后才能调用 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * Map<String, String> params = new HashMap<>(); * params.put(Const.INIT_PARAMS.AONE_ID_TOKEN, idToken); * SdkInit(this,params); * 参数说明: * * @param context 当前的Activity * @param params map类型的入参,这里只需要往map里面传入一个参数,key是Const.INIT_PARAMS.AONE_ID_TOKEN,value是AoneId授权成功后的token * 接口回调数据: 回调后的数据类型是map类型, 成功回调示例: {code="0x00000", data={"aoneSdkToken":"aoneSdk的token"}, message=成功} 失败回调示例:{code="0x00999", data={}, message=未知错误} * 接口回调数据字段说明: code: 状态码, data: 数据, message: 消息 */ private void SdkInit(MainActivity context,Map<String, String> params) { AOneSdkClient.Companion.SdkInit(context, params, new AOneSdkClient.Companion.SdkCallBack() { @Override public void onResponse(@NonNull Map<String, String> map) { Log.i(TAG, "[init] response: " + map); String code = map.get(Const.RESPONSE.CODE); String message = map.get(Const.RESPONSE.MESSAGE); if (ResultCode.SUCCESS.getValue().equals(code)) { String data = map.get(Const.RESPONSE.DATA); if (!TextUtils.isEmpty(data)) { Map<String, String> dataMaps = GsonUtils.Companion.GsonToMaps(data); if (dataMaps != null) { String aoneSdkToken = dataMaps.get(Const.DATA.AoneSdkToken); if (TextUtils.isEmpty(aoneSdkToken)){ return; } Log.i(TAG, "[init] 这里需要保存aoneSdkToken 下次自动登录的时候回传给sdk: " + aoneSdkToken); Toast.makeText(MainActivity.this, getText(R.string.init_success), Toast.LENGTH_SHORT).show(); } } } else { findViewById(R.id.aOneAccountLogin).setEnabled(true); findViewById(R.id.aOneWebViewLogin).setEnabled(true); Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } } }); }
8、在需要启动Vpn的界面增加Vpn启动的方法,并监听Vpn启动的状态
-
在合适的时机(建议在调用启动Vpn的方法之前),调用RegistGlobalNotification的方法,监听Vpn的一些状态,根据这些状态来同步界面的样式
VPN状态说明:- UNINITED : VPN 未初始化
- INITED : VPN 已初始化
- READY : VPN 配置完成
- CONNECTED : VPN 已上线
- DISCONNECTED : VPN 已下线
/** * 方法名: RegistGlobalNotification * 描述:这个方法用于Sdk VPN状态的回调,用于同步改变VPN界面的一些上下线的状态 * VPN状态说明: * UNINITED : VPN 未初始化 * INITED : VPN 已初始化 * READY : VPN 配置完成 * CONNECTED : VPN 已上线 * DISCONNECTED : VPN 已下线 * 使用前置条件:无 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.RegistGlobalNotification(new AOneSdkClient.Companion.SdkCallBack() { * @Override * public void onResponse(@NonNull Map<String, String> map) { * } * }); * 参数说明: * 接口回调数据: 回调后的数据类型是map类型, 成功回调示例: {code="0x00000", data={"tunnelState":"INITED"}, message=成功} 失败回调示例:{code="0x00999", data={}, message=未知错误} * 接口回调数据字段说明: code: 状态码, data: vpn状态,里面包含有tunnelState状态的字段,value的含义见上面的VPN状态说明, message: 消息 */ private void sdkCallBack() { AOneSdkClient.Companion.RegistGlobalNotification(new AOneSdkClient.Companion.SdkCallBack() { @Override public void onResponse(@NonNull Map<String, String> map) { Log.i(TAG, "[sdkCallBack] response: " + map); Log.i(TAG, "threadid:" + Thread.currentThread().getName()); String code = map.get(Const.RESPONSE.CODE); String message = map.get(Const.RESPONSE.MESSAGE); if (ResultCode.SUCCESS.getValue().equals(code)) { String data = map.get(Const.RESPONSE.DATA); if (!TextUtils.isEmpty(data)) { Map<String, String> dataMaps = GsonUtils.Companion.GsonToMaps(data); if (dataMaps != null) { ((TextView) findViewById(R.id.status)).setText(dataMaps.get(Const.DATA.TUNNEL_STATE)); } } } else { Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } } }); }
9、启动VPN
- 调用SdkStartVPN方法启动Vpn
/** * 方法名: SdkStartVPN * 描述:这个方法用于Sdk VPN的启动 * 使用前置条件:需要先调用SdkInit * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.SdkStartVPN(new AOneSdkClient.Companion.SdkCallBack() { * @Override * public void onResponse(@NonNull Map<String, String> map) { * } * }); * 参数说明: * 接口回调数据: 回调后的数据类型是map类型, 成功回调示例: {code="0x00000", data={}, message=成功} 失败回调示例:{code="0x00999", data={}, message=未知错误} * 接口回调数据字段说明: code: 状态码, data: 数据, message: 消息 */ public void startVpn(View view) { HashMap<String, String> params = new HashMap<>(); AOneSdkClient.Companion.SdkStartVPN(this, params, new AOneSdkClient.Companion.SdkCallBack() { @Override public void onResponse(@NonNull Map<String, String> map) { Log.i(TAG, "[startVpn] response: " + map); Log.i(TAG, "threadid:" + Thread.currentThread().getName()); String code = map.get(Const.RESPONSE.CODE); String message = map.get(Const.RESPONSE.MESSAGE); if (ResultCode.SUCCESS.getValue().equals(code)) { Toast.makeText(MainActivity.this, getText(R.string.init_success), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } } }); }
10、停止Vpn
- 在用户点击Vpn停止的事件上,调用SdkStopVPN方法停止Vpn,注意这里停止Vpn的条件是先调用SdkStartVPN且Vpn状态要在Connect状态
/** * 方法名: SdkStopVPN * 描述:这个方法用于Sdk VPN的停止 * 使用前置条件:需要先调用SdkStartVPN且Vpn状态要在Connect状态 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.SdkStopVPN(new AOneSdkClient.Companion.SdkCallBack() { * @Override * public void onResponse(@NonNull Map<String, String> map) { * } * }); * 参数说明: * 接口回调数据: 回调后的数据类型是map类型, 成功回调示例: {code="0x00000", data={}, message=成功} 失败回调示例:{code="0x00999", data={}, message=未知错误} * 接口回调数据字段说明: code: 状态码, data: 数据, message: 消息 */ public void stopVpn(View view) { HashMap<String, String> params = new HashMap<>(); AOneSdkClient.Companion.SdkStopVPN(this, params, new AOneSdkClient.Companion.SdkCallBack() { @Override public void onResponse(@NonNull Map<String, String> map) { Log.i(TAG, "[startVpn] response: " + map); Log.i(TAG, "threadid:" + Thread.currentThread().getName()); String code = map.get(Const.RESPONSE.CODE); String message = map.get(Const.RESPONSE.MESSAGE); if (ResultCode.SUCCESS.getValue().equals(code)) { Toast.makeText(MainActivity.this, getText(R.string.init_success), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } } }); }
11、SDK 清理
- 在确定用户不使用VPN服务的时候,通过调用SdkUnInit释放 Vpn服务所需要的资源,调用此方法前需要先调用SdkStopVPN且Vpn状态要在DisConnect状态,否则会提示状态错误的错误码
/** * 方法名: SdkUnInit * 描述:这个方法用于Sdk 资源释放, 调用此方法后Vpn的状态会变成UnInit,要重新启动VPN需要调用SdkInit方法,否则会提示状态错误的错误码 * 使用前置条件:需要先调用SdkStopVPN且Vpn状态要在DisConnect状态,否则会提示状态错误的错误码 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.SdkUnit(new AOneSdkClient.Companion.SdkCallBack() { * * @Override public void onResponse(@NonNull Map<String, String> map) { * } * }); */ public void SdkUnInit(){ HashMap<String, String> params = new HashMap<>(); AOneSdkClient.Companion.SdkUnInit(this, params, new AOneSdkClient.Companion.SdkCallBack() { @Override public void onResponse(@NonNull Map<String, String> map) { Log.i(TAG, "[stopVpn] response: " + map); String code = map.get(Const.RESPONSE.CODE); String message = map.get(Const.RESPONSE.MESSAGE); if (ResultCode.SUCCESS.getValue().equals(code)) { Toast.makeText(MainActivity.this, getText(R.string.disconnect_success), Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show(); } } }); }
12、SDK状态查询
- 调用GetSdkStatus 可以主动查询Sdk的状态
/** * 方法名: GetSdkStatus * 描述:这个方法用于Sdk VPN的状态 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: AOneSdkClient.Companion.GetSdkStatus() * VPN状态说明: * UNINITED : VPN 未初始化 * INITED : VPN 已初始化 * READY : VPN 配置完成 * CONNECTED : VPN 已上线 * DISCONNECTED : VPN 已下线 */ public void GetSdkStatus(View view) { AOneManager.AOneTunnelState aOneTunnelState = AOneSdkClient.Companion.GetSdkStatus(); Toast.makeText(MainActivity.this, "当前Sdk Vpn状态为:" + aOneTunnelState.name(), Toast.LENGTH_SHORT).show(); }
13、调用方法传入Sdk用户对隐私协议的操作情况
-
调用updatePrivacyAgree 传入用户是否同意隐私协议,如果同意隐私协议就传入true,不同意或者撤回隐私协议传入false
/** * 方法名:updatePrivacyAgree * 描述:这个方法用于客户Sdk隐私协议传入到sdk * 使用前置条件:弹出隐私协议窗口后 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.updatePrivacyAgree(isAgree) * 参数说明:Boolean类型的数据,用户同意隐私协议则传入true 拒绝或者撤回则传入false */ private static void updatePrivacyAgree(boolean isAgree) { AOneSdkClient.Companion.updatePrivacyAgree(isAgree); }
14、SDK使用权限说明
权限名称 | 使用目的 | 功能场景 / 申请时机 | 必选/可选 |
---|---|---|---|
android.permission.INTERNET | 查看网络状态,用于数据上报,实现开发者查看崩溃信息的目的 | app启动阶段, 用于VPN网络连接 | 必选 |
android.permission.ACCESS_WIFI_STATE | 查看WiFi网络状态信息,用于查看WiFi网络状态信息 | app启动阶段,用于VPN网络检测 | 必选 |
android.permission.ACCESS_NETWORK_STATE | 允许访问网络状态, 用于区分移动网络或WiFi网络 | app启动阶段,用于VPN网络检测 | 必选 |
android.permission.BIND_VPN_SERVICE | 绑定VPN服务,用于连接内网 | app启动阶段,用于VPN网络连接 | 必选 |
15、SDK 产品功能所需收集的个人信息说明
个人信息类型 | 可选/必选 | 处理目的/功能场景 | 处理方式 |
---|---|---|---|
获取IP | 必选 | 用于网络变化的时候重新解析DNS获取ip | 仅读取,不保存到本地,也不上传服务器 |
常见问题
-
引入aar包后,出现Duplicate class 的错误,如下图所示
原因:因为sdk有依赖一些jar包,如果app工程也依赖相同的jar包会导致冲突,这里需要exclude app 工程的相同的类来解决这类问题,目前sdk层依赖的jar包如下表所示:
库名字 版本 okhttp 4.9.3 okio 2.8.0 gson 2.9.0 datastore-preferences-core 1.0.0 datastore-core 1.0.0 converter-gson 2.9.0 logging-interceptor 4.9.3 retrofit 2.9.0 commons-validator:commons-validator 1.7 org.conscrypt:conscrypt-android 2.5.2 androidx.security:security-crypto 1.1.0-alpha06 androidx.lifecycle:lifecycle-runtime-ktx 2.5.1 com.elvishew:xlog 1.11.1 解决方式:使用
exclude
排除重复的类如果你知道具体哪些类是重复的(通常可以在错误消息中看到),可以使用
exclude
来排除特定的类。例如,如果common-lib
引入了重复的类com.okhttp3
,你可以排除该类:dependencies { implementation('com.example:common-lib:1.0.0') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } }
-
获取设备id
如果需要获取当前sdk指定的设备指纹(唯一id),则可以调用 DeviceIdUtil.Companion.getUniqueId()获取当前系统的版本,则可以调用 DeviceIdUtil.Companion.getDeviceOs()
-
日志路径
如果遇到问题需要协助,开发人员可能会寻求日志定位问题,这里日志的路径的获取有三个方式-
方式一、使用如下方法获取日志
VPNApplication.get().getFileLogPath()
-
方式二、在log中获取,启动后会在tag为Sdk中获取到
在用如上的方法获取到日志路径后,到路径下取出今天的日志发给开发者即可
-
方式三、日志分享
调用AOneSdkClient.Companion.ShareLog(this) 方法,日志会打包成一个zip文件,并弹出分享面板,可以通过微信等方式分享/** * 方法名: ShareLog * 描述:这个方法用于Sdk日志的分享,方便快速定位问题 * 使用线程: 主线程 * 回调线程: 主线程 * 使用方法: * AOneSdkClient.Companion.ShareLog(this); */ public void ShareLog(View view) { AOneSdkClient.Companion.ShareLog(this); }
-
-
错误码
错误码 错误提示 建议处置方式 0x10301 隧道资源接口请求失败 联系管理员 0x10302 隧道资源不存在 联系管理员 0x10311 aoneid隧道资源接口请求失败 联系管理员 0x10312 aoneid隧道资源不存在 联系管理员 0x10321 获取解析组接口错误 联系管理员 0x10322 获取解析组域名为空 联系管理员 0x10323 获取解析组ip为空 联系管理员 0x10331--0x10334 隧道配置接口请求失败 联系管理员 0x10335 隧道配置不存在 联系管理员 0x10336 控制中心无法分配足够客户端ip 联系管理员 0x10337 多镜像中心隧道接口内部错误 联系管理员 0x10341 token接口刷新错误 联系管理员 0x10351 添加设备的接口报错 联系管理员 0x10352 设备未激活 联系管理员,增加设备的激活数量 0x10353 未支持的AuthStatus字段 联系管理员 0x11001 配置转换异常 联系管理员 0x11002 数据转换异常 联系管理员 0x12001 开始认证无Token,一般是Token过期了 1、排查是否登录成功 2、是否正确传递token到sdk,或者token超过了有效期 3、如果还是不行,联系管理员 0x12002 开始认证无用户名 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0x12003 开始认证无随机数 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0x12004 无权限 app申请VPN权限,用户是否同意了 0x12005 无网络 查看网络是否正常 0X12006 无refreshToken 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0X12007 aoneId 用户池id是空的 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0X12008 租户id是空的 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0X12009 token 错误 1、排查是否正确传递token到sdk 2、正确传递还是报错则联系管理员 0X12010 启动VPN一些配置是错误的 联系管理员 0X12011 初始化token已过期 排除token 是否已经过期了 0X13001 状态错误 联系管理员 0X13002 下线失败 联系管理员 0X13003 初始化错误 联系管理员 0X13004 已经在线 VPN已经是在线状态,又调用了SdkStartVPN导致 建议检查下VPN的状态 0X13005 json 序列化错误 联系管理员 0X13006 json 解析失败 联系管理员 0X13007 未知错误 联系管理员 0X13008 服务端响应错误,一般是502等错误状态码 联系管理员 0X13009 服务端无响应 联系管理员 0X13010 请先传入用户是否同意隐私协议再使用sdk 需要弹出隐私协议确认对话框,并调用的方法AOneSdkClient.Companion.updatePrivacyAgree(true)告知sdk 0X13011 需等待用户同意隐私协议后再使用sdk 用户同意隐私协议后需要调用AOneSdkClient.Companion.updatePrivacyAgree(true);