android.md 21 KB

安卓手册

简介

本文档主要介绍加加移动服务平台Android SDK的接入和使用。

  • SDK目录结构 ``` com.anji.appsp.sdk ├── base // 基础请求数据,基础数据处理类,基础controller │
    ├── http // 网络请求,使用HttpURLConnection请求 │
    ├── model // 接口返回的数据,以及需要返回给前台的数据 │ ├── notice // 公告模块 │ └── controller // 公告功能的处理 │ └── impl // 公告接口实现 │ └── service // 公告接口 ├── util // 工具,比如加密工具、获取设备信息工具 │
    ├── version // 版本模块 │ └── controller // 版本功能的处理 │ └── impl // 版本接口实现 │ └── service // 版本接口 AppSpConfig // 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上传和集成就结束了。

情景二 本地library集成

  • 创建library,此处略
  • app/build.gradle 下引用
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>
  • file_paths.xml代码
<?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 | 异常信息 |

Android SDK解析

  • 采取service/impl/controller/handler 的结构,方便模块功能扩展
  • AppSpConfig 主类,对外提供可配置方法
	/**
     * @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加密

  • 见RSAUtil.java
	/**
     * 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);
    }

设备信息获取

  • 因为这个功能就是一次性的,所以放在version模块
  • 接口

接口路径: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);
                }
            }
        });
    }
  • 数据解析,详见AppSpVersionHandler.java
	/**
     * 版本更新获取数据成功
     * 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());
          }
      
      }
      

      } }

    /**

    • 公告异常处理 *
    • @param code
    • @param msg */ public void handleNoticeExcption(String code, String msg) { if (appSpNoticeCallback != null) { synchronized (appSpNoticeCallback) { //结果返回给调用者 appSpNoticeCallback.error(code, msg); } } } ```

Android 混淆

主工程下proguard-rules.pro文件加入:


-dontwarn com.anji.appsp.sdk.**
-keep class com.anji.appsp.sdk.**{*;}

Demo下载

Android SDK地址: https://gitee.com/anji-plus/appsp/tree/master/demo/android/aj_android_appsp

SDK下载

Android SDK地址: https://gitee.com/anji-plus/aj-appsp/sdk/android