本文档主要介绍加加移动服务平台Android SDK的接入和使用。
* 支持版本说明
> * Android SDK >= 15
> * Android Studio 3.0+
> * JDK 1.8
## Android SDK集成
### 情景一 AAR Maven依赖
* 创建library,此处略
* 上传AAR到github
1,在library下的build.gradle加入
```java
apply plugin: 'maven'
ext {
GITHUB_REPO_PATH = "XXX"//XXX:打包aar成功后所在目录,比如我的是 D:/Projects/AARMaven
PUBLISH_GROUP_ID = 'anji.sdk.appsp'//aar的组名
PUBLISH_ARTIFACT_ID = 'aar'//包名
PUBLISH_VERSION = '0.0.2'//版本名,通过上述配置,生成的aar目录是 anji/sdk/appsp/aar/0.0.2
}
//打包
uploadArchives {
repositories {
mavenDeployer {
//本地maven仓库地址,也可以使用远程maven仓库
def deployPath = file(project.GITHUB_REPO_PATH)
repository(url: "file://${deployPath.absolutePath}")
pom.project {
groupId project.PUBLISH_GROUP_ID
artifactId project.PUBLISH_ARTIFACT_ID
version project.PUBLISH_VERSION
}
}
}
}
// 源代码一起打包
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.sourceFiles
}
artifacts {
archives androidSourcesJar
}
2,生成aar并上传至github
执行gradlew uploadArchives,即可本地生成aar资源,从目录anji开始,全部传到github所在的仓库
3,集成aar
3.1 host配置
针对Windows环境,C:\Windows\System32\drivers\etc 的host文件加入
185.199.108.133 raw.githubusercontent.com
针对Mac环境,在/etc/hosts下加入
3.2 在项目的build.gradle中加入
buildscript {
repositories {
google()
jcenter()
//关键代码
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
//关键代码,XX表示github的组织或账号,我们是anji-plus,YY是branch,比如我们是main
//我们完整的路径是 https://raw.githubusercontent.com/anji-plus/aj_android_appsp_aar/main
maven { url "https://raw.githubusercontent.com/XX/aj_android_appsp_aar/YY" }
}
}
3.3 在app/build.gradle中引入
dependencies {
//引用sdk
implementation 'anji.sdk.appsp:aar:0.0.2'
}
到此,AAR上传和集成就结束了。
dependencies {
//XXX是library的名称
implementation project(':XXX')
}
AppKey的获取见 操作手册
public class AppContext extends Application {
private static AppContext mInstance;
//appkey是在Appsp创建应用生成的
public static final String appKey = "860dc3bd5faa489bb7ab6d087f8ee6e5";
public static AppContext getInstance() {
return mInstance;
}
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
//关键代码
AppSpConfig.getInstance()
.init(this, appKey)
//可修改基础请求地址
.setHost("https://openappsp.anji-plus.com")
//正式环境可以禁止日志输出,通过Tag APP-SP过滤看日志
.setDebuggable(BuildConfig.DEBUG ? true : false)
//务必要初始化,否则后面请求会报错
.deviceInit();
}
}
<!-- apk存储 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 网络请求 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- apk升级 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- 7.0+文件读取 com.anji.appsp.sdktest是Demo的包名-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="包名.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="files_root"
path="Android/data/包名/" />
<external-path
name="external_storage_root"
path="." />
<root-path
name="root_path"
path="" />
</paths>
private void checkVersion() {
AppSpConfig.getInstance().getVersion(new IAppSpVersionCallback() {
@Override
public void update(AppSpModel<AppSpVersion> spModel) {
//因为是异步,注意当前窗口是否活跃
if (!isActive()) {
return;
}
//当前在子线程,注意
}
@Override
public void error(String code, String msg) {
//当前在子线程,注意
}
});
}
请求返回数据结构如下
spModel 数据详情
{
"repCode": "0000", //业务返回码,0000表示成功
"repMsg": "成功", //业务日志
"repData": {
"downloadUrl": "app下载地址",
"mustUpdate": false, //是否强制更新,true为强制更新
"showUpdate": true, //是否允许弹出更新
"updateLog": "更新日志"
}
}
| 字段| 类型| 说明 | | :-----| :---- | :---- | | repCode |String | 业务返回码,0000表示成功 | | repMsg |String | 业务日志、异常信息 | | repData | Object | 请求业务数据包、详情见下 |
repData 数据详情
| 字段| 类型| 说明 | | :-----| :---- | :---- | | downloadUrl |String | app下载地址 | | mustUpdate | boolean | 是否强制更新,true为强制更新; false为非强制更新 | | showUpdate | boolean | 是否提示更新:允许弹出更新 | | updateLog |String | 更新日志 |
errorInfo 数据详情
repCode: 抛出异常Code
repMsg: 异常信息
| 字段| 类型| 说明 | | :-----| :---- | :---- | | repCode |String | 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空 | | repMsg |String | 异常信息 |
### 公告服务
private void checkNotice() {
AppSpConfig.getInstance().getNotice(new IAppSpNoticeCallback() {
@Override
public void notice(AppSpModel<List<AppSpNoticeModelItem>> spModel) {
//因为是异步,注意当前窗口是否活跃
if (!isActive()) {
return;
}
//当前在子线程,注意
}
@Override
public void error(String code, String msg) {
//当前在子线程,注意
}
});
请求返回数据结构如下
spModel 数据详情
{
"repCode": "0000", //业务返回码,0000表示成功
"repMsg": "成功", //业务日志
"repData": [ // 返回数据为 List
{
"title": "公告标题",
"details": "公告内容",
"templateType": "dialog", //公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll)
"templateTypeName": "公告"//公告模板名称
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
| repCode | String | 业务返回码,0000表示成功 |
| repMsg | String | 业务日志、异常信息 |
| repData | Object | 请求业务数据包、详情见下 |
repData 数据详情
| 字段 | 类型 | 说明 |
|---|---|---|
| title | String | 公告标题 |
| details | String | 公告内容 |
| templateType | String | 公告类型( 弹窗:dialog; 水平滚动:horizontal_scroll) |
| templateTypeName | String | 公告模板名称 |
errorInfo 数据详情
repCode: 抛出异常Code
repMsg: 异常信息
| 字段| 类型| 说明 | | :-----| :---- | :---- | | repCode |string | 抛出异常Code ; 1001: 请求异常;1202: appKey为空;1203: appKey校验失败;1207: 系统版本号不能为空;1208: 应用版本号不能为空 | | repMsg |string | 异常信息 |
/**
* @param host 请求的基本地址
*/
public AppSpConfig setHost(String host) {
AppSpRequestUrl.Host = host;
return appSpConfig;
}
/**
* 设置是否打开debug开关
*
* @param debug true表示打开debug
*/
public AppSpConfig setDebuggable(boolean debug) {
AppSpLog.setDebugged(debug);
return appSpConfig;
}
/**
* 初始化
*
* @param context 可以是 activity/fragment/view
* @param appKey 用于标识哪个APP,唯一标识
*/
public AppSpConfig init(Context context, String appKey) {
this.context = context;
this.appKey = appKey;
return appSpConfig;
}
/**
* 获取设备信息
*/
public void deviceInit() {
AppSpVersionController appSpVersionController = new AppSpVersionController(context, appKey);
appSpVersionController.initDevice();
}
/**
* 版本更新回调
*
* @param iAppSpVersionCallback
*/
public void getVersion(IAppSpVersionCallback iAppSpVersionCallback) {
AppSpVersionController appSpVersionController = new AppSpVersionController(context, appKey);
appSpVersionController.getVersion(iAppSpVersionCallback);
}
/**
* 公告回调
*
* @param iAppSpNoticeCallback
*/
public void getNotice(IAppSpNoticeCallback iAppSpNoticeCallback) {
AppSpNoticeController appSpNoticeController = new AppSpNoticeController(context, appKey);
appSpNoticeController.getNotice(iAppSpNoticeCallback);
}
/**
* RSA加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return Base64Util.encode(encryptedData);
}
接口路径:https://openappsp.anji-plus.com/sp/phone/deviceInit
public interface IAppSpVersionService {
//获取版本信息
void getVersion();
}
@Override
public void initDevice() {
//appKey不能为空
if(TextUtils.isEmpty(appKey)){
AppSpLog.e("initDevice Appkey is null or empty");
return;
}
//请求基础数据
AppSpPostData appSpPostData = getPostEncryptData();
AppSpHttpClient client = new AppSpHttpClient();
//请求
client.request(AppSpRequestUrl.Host + AppSpRequestUrl.initDevice, appSpPostData, new AppSpCallBack() {
@Override
public void onSuccess(String data) {
AppSpLog.d("initDevice success");
}
@Override
public void onError(String code, String msg) {
AppSpLog.d("initDevice error");
}
});
}
接口路径:https://openappsp.anji-plus.com/sp/phone/appVersion
public interface IAppSpVersionService {
//获取版本信息
void getVersion();
}
@Override
public void getVersion() {
//appKey不能为空
if(TextUtils.isEmpty(appKey)){
AppSpLog.e("getVersion Appkey is null or empty");
return;
}
//请求基础数据
AppSpPostData appSpPostData = getPostEncryptData();
AppSpHttpClient client = new AppSpHttpClient();
//请求
client.request(AppSpRequestUrl.Host + AppSpRequestUrl.getAppVersion, appSpPostData, new AppSpCallBack() {
@Override
public void onSuccess(String data) {
//数据解析
if (appSpVersionHandler != null) {
appSpVersionHandler.handleVersionSuccess(data);
}
}
@Override
public void onError(String code, String msg) {
//数据解析
if (appSpVersionHandler != null) {
appSpVersionHandler.handleUpdateException(code, msg);
}
}
});
}
/**
* 版本更新获取数据成功
* json解析
*/
public void handleVersionSuccess(String data) {
if (appSpVersionCallback != null) {
synchronized (appSpVersionCallback) {
//Update
try {
AppSpModel<AppSpVersion> spVersionModel = new AppSpModel<>();
JSONObject jsonObject = new JSONObject(data);
spVersionModel.setRepCode(getStringElement(jsonObject.opt("repCode")));
spVersionModel.setRepMsg(getStringElement(jsonObject.opt("repMsg")));
Object repDtaObj = jsonObject.opt("repData");
if (repDtaObj != null && !TextUtils.isEmpty(repDtaObj.toString())
&& !"null".equalsIgnoreCase(repDtaObj.toString())) {
JSONObject repData = new JSONObject(repDtaObj.toString());
if (repData != null) {
AppSpVersion appSpVersion = new AppSpVersion();
appSpVersion.setDownloadUrl(getStringElement(repData.opt("downloadUrl")));
appSpVersion.setUpdateLog(getStringElement(repData.opt("updateLog")));
appSpVersion.setShowUpdate(getBooleanElement(repData.opt("showUpdate")));
appSpVersion.setMustUpdate(getBooleanElement(repData.opt("mustUpdate")));
spVersionModel.setRepData(appSpVersion);
}
}
AppSpLog.d("版本更新返回客户端数据 " + spVersionModel);
//结果返回给调用者
appSpVersionCallback.update(spVersionModel);
} catch (Exception e) {
AppSpLog.d("版本更新返回客户端数据 Exception e " + e.toString());
}
}
}
}
/**
* 版本更新异常处理
*
* @param code
* @param msg
*/
public void handleUpdateException(String code, String msg) {
if (appSpVersionCallback != null) {
synchronized (appSpVersionCallback) {
//Update error
//结果返回给调用者
appSpVersionCallback.error(code, msg);
}
}
}
接口路径:https://openappsp.anji-plus.com/sp/phone/appNotice
public interface IAppSpNoticeService {
void getNotice();
}
@Override
public void getNotice() {
//appKey不为空
if (TextUtils.isEmpty(appKey)) {
AppSpLog.e("getNotice Appkey is null or empty");
return;
}
//请求基础数据
AppSpPostData appSpPostData = getPostEncryptData();
AppSpHttpClient client = new AppSpHttpClient();
client.request(AppSpRequestUrl.Host + AppSpRequestUrl.getAppNotice, appSpPostData, new AppSpCallBack() {
@Override
public void onSuccess(String data) {
//数据处理
if (appSpNoticeHandler != null) {
appSpNoticeHandler.handleNoticeSuccess(data);
}
}
@Override
public void onError(String code, String msg) {
//数据处理
if (appSpNoticeHandler != null) {
appSpNoticeHandler.handleNoticeExcption(code, msg);
}
}
});
}
数据解析,详见AppSpNoticeHandler.java ```java /**
公告获取数据成功 */ public void handleNoticeSuccess(String data) { if (appSpNoticeCallback != null) {
synchronized (appSpNoticeCallback) {
//Notice
try {
AppSpModel<List<AppSpNoticeModelItem>> spVersionModel = new AppSpModel<>();
JSONObject jsonObject = new JSONObject(data);
spVersionModel.setRepCode(getStringElement(jsonObject.opt("repCode")));
spVersionModel.setRepMsg(getStringElement(jsonObject.opt("repMsg")));
Object repDtaObj = jsonObject.opt("repData");
if (repDtaObj != null && !TextUtils.isEmpty(repDtaObj.toString())
&& !"null".equalsIgnoreCase(repDtaObj.toString())) {
JSONArray repData = new JSONArray(repDtaObj.toString());
List<AppSpNoticeModelItem> items = new ArrayList<>();
if (repData != null) {
for (int i = 0; i < repData.length(); i++) {
AppSpNoticeModelItem item = new AppSpNoticeModelItem();
JSONObject value = repData.getJSONObject(i);
item.setTitle(getStringElement(value.opt("title")));
item.setDetails(getStringElement(value.opt("details")));
item.setTemplateType(getStringElement(value.opt("templateType")));
item.setTemplateTypeName(getStringElement(value.opt("templateTypeName")));
items.add(item);
}
spVersionModel.setRepData(items);
appSpNoticeCallback.notice(spVersionModel);
}
}
AppSpLog.d("通知返回客户端数据 " + spVersionModel);
} catch (Exception e) {
AppSpLog.d("通知返回客户端数据 Exception e " + e.toString());
}
}
} }
/**
主工程下proguard-rules.pro文件加入:
-dontwarn com.anji.appsp.sdk.**
-keep class com.anji.appsp.sdk.**{*;}
Android SDK地址: https://gitee.com/anji-plus/appsp/tree/master/demo/android/aj_android_appsp
Android SDK地址: https://gitee.com/anji-plus/aj-appsp/sdk/android