版权声明:本博客主要记录学习笔记和遇到的一些问题解决方案,转载请注明出处! https://blog.csdn.net/u010982507/article/details/81144478
需求描述
最近项目上有个需求,要实现外部App跳转到自身App,并在自己App中实现免密登录功能。功能不算难,做个笔记,方便以后使用。功能分析
1、FirstApp提供SDK的jar包,SecondApp负责调用jar包中的toActivity方法
2、在toActivity方法中实现FirstApp提供的后台服务,验证SecondApp的合法性
3、接口请求成功后,实现SecondApp到FirstApp之间的跳转功能,并传值
4、FirstApp接收到SecondApp传来的值,进行登录校验代码实现
1、在SecondApp中引入AppBridge的jar包,并调用AppBridge中的toActivity方法
String env = EnvUtil.UAT; // 测试环境
AppBridge appBridge = new AppBridge(env);
appBridge.toActivity(MainActivity.this, "参数1","参数2", new BridgeCallback() {
@Override
public boolean success(String result) {
tv_response.setText(result);
return super.success(result);
}
@Override
public void fail(int code, String message) {
tv_response.setText(message);
}
});
2、进入AppBridge核心实现类,在此类中会请求一个服务接口,这个接口的作用是验证SecondApp的合法性,首先要提前将SecondApp的应用名、包名和证书的sha1值存储到FirstApp服务的数据库中,然后请求接口的时候将Second客户端的包名和证书的sha1值传到FirstApp的服务器验证,这里如果不是使用https请求,参数需要加密处理。
public class AppBridge {
private static final String TAG = AppBridge.class.getSimpleName();
private String env;
// 构造函数,将测试环境初始化
public AppBridge(String env) {
this.env = env;
}
/**
* 跳转到app,并传入相关参数
*
* @param context 上下文
* @param userName 用户名
* @param role 用户角色
* @param callback
*/
public void toActivity(final Context context, final String userName, final String role, final BridgeCallback callback) {
if (callback == null) {
return;
}
// 验证要跳转的app是否已经安装
if (!EnvUtil.isAPKExists(context, EnvUtil.getPackageName(env))) {
callback.fail(BridgeResponse.APP_NOT_EXISTS, "应用不存在.");
return;
}
// 验证SecondApp的证书是否能正常读取
String sign = EnvUtil.getSign(context);
if (sign == null) {
callback.fail(BridgeResponse.APP_SIGN_ERROR, "证书异常.");
return;
}
String params = "{\"InArgs\":{},\"XM\":{\"Sign\":\"" + sign
+ "\",\"PackageName\":\"" + context.getPackageName()
+ "\",\"userName\":\"" + userName
+ "\",\"role\":\"" + role
+ "\"}}";
// 调用OkHttp的post请求
OkhttpUtil.post(EnvUtil.getUrl(this.env), params, new StringCallback() {
@Override
public void onSuccess(String response) {
super.onSuccess(response);
try {
if (!TextUtils.isEmpty(response)) {
JSONObject result = new JSONObject(response);
JSONObject OutArgs = result.getJSONObject("OutArgs");
String errorCode = OutArgs.getString("errorcode");
if (BridgeResponse.RES_SUCCESS_CODE.equals(errorCode)) {
// 服务请求成功
AppInfo appInfo = new AppInfo();
appInfo.getJSONObject(result);
// 实现app之间的跳转
Intent intent = new Intent();
intent.setComponent(new ComponentName(EnvUtil.getPackageName(env), EnvUtil.Entry));
// 拿到唯一的token,作为FirstApp登录使用
intent.putExtra("Token", appInfo.getToken());
intent.putExtra("userName", appInfo.getUserName());
intent.putExtra("Role", appInfo.getRole());
context.startActivity(intent);
callback.success(response);
} else {
callback.fail(Integer.parseInt(errorCode), OutArgs.getString("msg"));
}
} else {
callback.fail(BridgeResponse.PARAMS_ERROR, "服务器返回值异常." + response);
}
} catch (Exception e) {
e.printStackTrace();
callback.fail(BridgeResponse.APP_NOT_EXISTS, e.getMessage());
}
}
@Override
public void onError(String result, int code) {
super.onError(result, code);
callback.fail(code, result.toString());
}
@Override
public void onFailure(Request request, IOException e) {
super.onFailure(request, e);
callback.fail(BridgeResponse.PARAMS_ERROR, e.getMessage());
}
});
}
}
3、EnvUtil环境配置工具类
public class EnvUtil {
public static final String DEV = "dev"; // 开发环境
public static final String TEST = "test"; // 本地测试环境
public static final String UAT = "uat"; // 测试环境
public static final String PRO = "product"; // 生产环境
public static final String URL_PATH = "/servlets/AppService/user/...";
public static final String Entry = "com.appbridge.first.app.MainActivity";
/**
* 获取当前app的签名信息
*
* @param context
* @return
*/
public static String getSign(Context context) {
try {
PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
Signature sign = signs[0];
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(sign.toByteArray());
char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
byte[] bs = md.digest();
int j = bs.length;
char[] buf = new char[j * 3 - 1];
int k = 0;
for (int i = 0; i < j; ++i) {
byte byte0 = bs[i];
buf[k++] = hexDigits[byte0 >>> 4 & 15];
buf[k++] = hexDigits[byte0 & 15];
if (i != j - 1) {
buf[k++] = 58;
}
}
return new String(buf);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取包名
*
* @param env
* @return
*/
public static String getPackageName(String env) {
String packageName = "com.appbridge.first";
switch (env) {
case DEV:
packageName = "com.appbridge.first.dev";
break;
case TEST:
packageName = "com.appbridge.first.devs";
break;
case UAT:
packageName = "com.appbridge.first.uat";
break;
case PRO:
packageName = "com.appbridge.first";
break;
}
return packageName;
}
/**
* 获取环境的url请求地址
*
* @param env
* @return
*/
public static String getUrl(String env) {
String url = "";
switch (env) {
case DEV:
url = "https://127.0.0.1:8080" + URL_PATH;
break;
case TEST:
url = "https://127.0.0.1:8080" + URL_PATH;
break;
case UAT:
url = "https://127.0.0.1:8080" + URL_PATH;
break;
case REL:
url = "https://127.0.0.1:8080" + URL_PATH;
break;
case PRO:
url = "https://127.0.0.1:8080" + URL_PATH;
break;
}
return url;
}
/**
* 判断是否安装某个应用
*
* @param context
* @param packageName
* @return
* @throws Exception
*/
public static boolean isAPKExists(Context context, String packageName) {
boolean isExists = false;
try {
PackageManager packageManager = context.getPackageManager();
List<PackageInfo> info = packageManager.getInstalledPackages(0);// 获取所有已安装程序的包信息
if (info != null) {
for (int i = 0; i < info.size(); i++) {
String pn = info.get(i).packageName;
if (packageName.equals(pn)) {
isExists = true;
}
}
}
} catch (Exception e) {
e.printStackTrace();
isExists = false;
}
return isExists;
}
}
4、AppInfo实体类,这个类只是映射后台json报文
public class AppInfo {
private String Token;
private String UserName;
private String Role;
public void getJSONObject(JSONObject response) {
try {
JSONObject result = response.getJSONObject("result");
JSONObject xm = result.getJSONObject("xm");
this.Token = xm.getString("Token");
this.UserName= xm.getString("UserName");
this.Role = xm.getString("Role");
} catch (JSONException e) {
e.printStackTrace();
}
}
public String getToken() {
return Token;
}
public void setToken(String token) {
Token = token;
}
public String getUserName() {
return UserName;
}
public void setUserName(String UserName) {
UserName= UserName;
}
public String getRole() {
return Role;
}
public void setRole(String role) {
Role = role;
}
}
5、toActivity的回调接口类
public abstract class BridgeCallback {
public boolean success(String uuid) {
return true;
}
public abstract void fail(int code, String message);
}
6、错误码处理
public class BridgeResponse {
public static final String RES_SUCCESS_CODE = "0000";
public static final int APP_NOT_EXISTS = 1001;
public static final int APP_SIGN_ERROR = 1002;
public static final int PARAMS_ERROR = 1003;
}
备注:代码中有个OkHttpUtil类代码没有贴出,这个使用自己项目中的工具类即可。