本文解决的问题是目前流行的 Android/IOS 原生应用内嵌 WebView 网页时,原生与H5页面登录状态的同步。
大多数混合开发应用的登录都是在原生页面中,这就牵扯到一个问题,如何把登录状态传给H5页面呢?总不能打开网页时再从网页中登录一次系统吧… 两边登录状态的同步是必须的。
100
多位经验丰富的开发者参与,在 Github 上获得了近1000
个star
的全栈全平台开源项目想了解或参与吗?
项目地址:https://github.com/cachecats/coderiver
一、同步原理
其实同步登录状态就是把登录后服务器返回的 token
、userId
等登录信息传给H5网页,在发送请求时将必要的校验信息带上。只不过纯H5开发是自己有一个登录页,登录之后保存在 Cookie 或其他地方;混合开发中H5网页自己不维护登录页,而是由原生维护,打开 webview 时将登录信息传给网页。
实现的方法有很多,可以用原生与 JS 的通信机制把登录信息发送给H5,关于原生与 JS 双向通信,我之前写了一篇详解文章,不熟悉的同学可以看看:
这里我们用另一种更简单的方法,通过安卓的 CookieManager
把 cookie
直接写入 webview 中。
二、安卓端代码
这是安卓开发需要做的。
先说一下步骤:
- 准备一个对象
UserInfo
,用来接收服务端返回的数据。 - 登录成功后把
UserInfo
格式化为 json 字符串存入SharedPreferences
中。 - 打开 webview 时从
SharedPreferences
取出上一步保存的UserInfo
。 - 新建一个
Map
将UserInfo
以键值对的格式保存起来,便于下一步保存为 cookie。 - 将
UserInfo
中的信息通过CookieManager
保存到 cookie 中。
看似步骤很多,其实就是得到服务端返回的数据,再通过 CookieManager
保存到 cookie 中这么简单,只不过中间需要做几次数据转换。
我们按照上面的步骤一步步看代码。UserInfo
对象就不贴了,都是些基本的信息。
将 UserInfo 保存到 SharedPreferences
登录接口请求成功后,会拿到 UserInfo
对象。在成功回调里通过下面一行代码保存 UserInfo
到 SharedPreferences
。
//将UserData存储到SP
SPUtils.putUserData(context, result.getData());
SPUtils 是操作 SharedPreferences 的工具类,代码如下。
包含了保存和取出 UserInfo
的方法(代码中对象名是 UserData),保存时通过 Gson 将对象格式化为 json 字符串,取出时通过 Gson 将 json 字符串格式化为对象。
public class SPUtils {
/**
* 保存在手机里面的文件名
*/
public static final String FILE_NAME = "share_data";
/**
* 存储用户信息
*
* @param context
* @param userData
*/
public static void putUserData(Context context, UserData userData) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
Gson gson = new Gson();
String json = gson.toJson(userData, UserData.class);
editor.putString(SPConstants.USER_DATA, json);
SharedPreferencesCompat.apply(editor);
}
/**
* 获取用户数据
*
* @param context
* @return
*/
public static UserData getUserData(Context context) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
String json = sp.getString(SPConstants.USER_DATA, "");
Gson gson = new Gson();
UserData userData = gson.fromJson(json, UserData.class);
return userData;
}
}
取出 UserInfo 并保存到 cookie 中
这里封装了一个带进度条的 ProgressWebviewActivity
,调用时直接打开这个 Activity 并将网页的 url 地址传入即可。在 Activity 的 onResume
生命周期方法中执行同步 cookie 的逻辑。为什么在 onResume
中执行?防止App 从后台切到前台 webview
重新加载没有拿到 cookie,可能放在 onCreate
大多数情况下也没有问题,但放到 onResume
最保险。
@Override
protected void onResume() {
super.onResume();
Logger.d("onResume " + url);
//同步 cookie 到 webview
syncCookie(url);
webSettings.setJavaScriptEnabled(true);
}
/**
* 同步 webview 的Cookie
*/
private void syncCookie(String url) {
boolean b = CookieUtils.syncCookie(url);
Logger.d("设置 cookie 结果: " + b);
}
同步操作封装到了 CookieUtils
工具类中,下面是 CookieUtils
的代码:
这个工具类中一共干了三件事,从 SharedPreferences
中取出 UserInfo
,将 UserInfo
封装到 Map 中,遍历 Map 依次存入 cookie。
public class CookieUtils {
/**
* 将cookie同步到WebView
*
* @param url WebView要加载的url
* @return true 同步cookie成功,false同步cookie失败
* @Author JPH
*/
public static boolean syncCookie(String url) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
CookieSyncManager.createInstance(MyApplication.getAppContext());
}
CookieManager cookieManager = CookieManager.getInstance();
Map<String, String> cookieMap = getCookieMap()