Android原生同步登陸狀態到H5網頁避免二次登陸

本文解決的問題是目前流行的 Android/IOS 原生應用內嵌 WebView 網頁時,原生與H5頁面登陸狀態的同步。

大多數混合開發應用的登陸都是在原生頁面中,這就牽扯到一個問題,如何把登陸狀態傳給H5頁面呢?總不能打開網頁時再從網頁中登陸一次系統吧… 兩邊登陸狀態的同步是必須的。

100 多位經驗豐富的開發者參與,在 Github 上得到了近 1000 個 star 的全棧全平臺開源項目想了解或參與嗎?
項目地址:https://github.com/cachecats/coderivercss

1、同步原理

其實同步登陸狀態就是把登陸後服務器返回的 token 、userId 等登陸信息傳給H5網頁,在發送請求時將必要的校驗信息帶上。只不過純H5開發是本身有一個登陸頁,登陸以後保存在 Cookie 或其餘地方;混合開發中H5網頁本身不維護登陸頁,而是由原生維護,打開 webview 時將登陸信息傳給網頁。html

實現的方法有不少,能夠用原生與 JS 的通訊機制把登陸信息發送給H5,關於原生與 JS 雙向通訊,我以前寫了一篇詳解文章,不熟悉的同窗能夠看看:前端

Android webview 與 js(Vue) 交互vue

這裏咱們用另外一種更簡單的方法,經過安卓的 CookieManager 把 cookie 直接寫入 webview 中。java

2、安卓端代碼

這是安卓開發須要作的。android

先說一下步驟:git

  1. 準備一個對象 UserInfo ,用來接收服務端返回的數據。
  2. 登陸成功後把 UserInfo 格式化爲 json 字符串存入 SharedPreferences 中。
  3. 打開 webview 時從 SharedPreferences 取出上一步保存的 UserInfo 。
  4. 新建一個 Map 將 UserInfo 以鍵值對的格式保存起來,便於下一步保存爲 cookie。
  5. 將 UserInfo 中的信息經過 CookieManager 保存到 cookie 中。

看似步驟不少,其實就是獲得服務端返回的數據,再經過 CookieManager 保存到 cookie 中這麼簡單,只不過中間須要作幾回數據轉換。程序員

咱們按照上面的步驟一步步看代碼。UserInfo 對象就不貼了,都是些基本的信息。github

將 UserInfo 保存到 SharedPreferences

登陸接口請求成功後,會拿到 UserInfo 對象。在成功回調裏經過下面一行代碼保存 UserInfo 到 SharedPreferencesweb

//將UserData存儲到SP
SPUtils.putUserData(context, result.getData());
  • 1
  • 2

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(); for (Map.Entry<String, String> entry : cookieMap.entrySet()) { String cookieStr = makeCookie(entry.getKey(), entry.getValue()); cookieManager.setCookie(url, cookieStr); } String newCookie = cookieManager.getCookie(url); return TextUtils.isEmpty(newCookie) ? false : true; } /** * 組裝 Cookie 裏須要的值 * * @return */ public static Map<String, String> getCookieMap() { UserData userData = SPUtils.getUserData(MyApplication.getAppContext()); String accessToken = userData.getAccessToken(); Map<String, String> headerMap = new HashMap<>(); headerMap.put("access_token", accessToken); headerMap.put("login_name", userData.getLoginName()); headerMap.put("refresh_token", userData.getRefreshToken()); headerMap.put("remove_token", userData.getRemoveToken()); headerMap.put("unitId", userData.getUnitId()); headerMap.put("unitType", userData.getUnitType() + ""); headerMap.put("userId", userData.getUserId()); return headerMap; } /** * 拼接 Cookie 字符串 * * @param key * @param value * @return */ private static String makeCookie(String key, String value) { Date date = new Date(); date.setTime(date.getTime() + 3 * 24 * 60 * 60 * 1000); //3天過時 return key + "=" + value + ";expires=" + date + ";path=/"; } }

syncCookie() 方法最後兩行是驗證存入 cookie 成功了沒。

到這裏 Android 這邊的工做就作完了,H5能夠直接從 Cookie 中取出 Android 存入的數據。

ProgressWebviewActivity封裝

下面是封裝的帶進度條的 ProgressWebviewActivity

/** * 帶進度條的 WebView。採用原生的 WebView */ public class ProgressWebviewActivity extends Activity { private WebView mWebView; private ProgressBar web_bar; private String url; private WebSettings webSettings; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web); url = getIntent().getStringExtra("url"); init(); } private void init() { //Webview mWebView = findViewById(R.id.web_view); //進度條 web_bar = findViewById(R.id.web_bar); //設置進度條顏色 web_bar.getProgressDrawable().setColorFilter(Color.RED, android.graphics.PorterDuff.Mode.SRC_IN); //對WebView進行必要配置 settingWebView(); settingWebViewClient(); //加載url地址 mWebView.loadUrl(url); } /** * 對 webview 進行必要的配置 */ private void settingWebView() { webSettings = mWebView.getSettings(); //若是訪問的頁面中要與Javascript交互,則webview必須設置支持Javascript // 若加載的 html 裏有JS 在執行動畫等操做,會形成資源浪費(CPU、電量) // 在 onStop 和 onResume 裏分別把 setJavaScriptEnabled() 給設置成 false 和 true 便可 webSettings.setJavaScriptEnabled(true); //設置自適應屏幕,二者合用 webSettings.setUseWideViewPort(true); //將圖片調整到適合webview的大小 webSettings.setLoadWithOverviewMode(true); // 縮放至屏幕的大小 //縮放操做 webSettings.setSupportZoom(true); //支持縮放,默認爲true。是下面那個的前提。 webSettings.setBuiltInZoomControls(true); //設置內置的縮放控件。若爲false,則該WebView不可縮放 webSettings.setDisplayZoomControls(false); //隱藏原生的縮放控件 //其餘細節操做 webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); //沒有網絡時加載緩存 //webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); //關閉webview中緩存 webSettings.setAllowFileAccess(true); //設置能夠訪問文件 webSettings.setJavaScriptCanOpenWindowsAutomatically(true); //支持經過JS打開新窗口 webSettings.setLoadsImagesAutomatically(true); //支持自動加載圖片 webSettings.setDefaultTextEncodingName("utf-8");//設置編碼格式 //不加的話有些網頁加載不出來,是空白 webSettings.setDomStorageEnabled(true); //Android 5.0及以上版本使用WebView不能存儲第三方Cookies解決方案 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, true); webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } } /** * 設置 WebViewClient 和 WebChromeClient */ private void settingWebViewClient() { mWebView.setWebViewClient(new WebViewClient() { @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); Logger.d("onPageStarted"); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); Logger.d("onPageFinished"); } // 連接跳轉都會走這個方法 @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Logger.d("url: ", url); view.loadUrl(url);// 強制在當前 WebView 中加載 url return true; } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { handler.proceed(); super.onReceivedSslError(view, handler, error); } }); mWebView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); Logger.d("current progress: " + newProgress); //更新進度條 web_bar.setProgress(newProgress); if (newProgress == 100) { web_bar.setVisibility(View.GONE); } else { web_bar.setVisibility(View.VISIBLE); } } @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); Logger.d("標題:" + title); } }); } /** * 同步 webview 的Cookie */ private void syncCookie(String url) { boolean b = CookieUtils.syncCookie(url); Logger.d("設置 cookie 結果: " + b); } /** * 對安卓返回鍵的處理。若是webview能夠返回,則返回上一頁。若是webview不能返回了,則退出當前webview */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) { mWebView.goBack();// 返回前一個頁面 return true; } return super.onKeyDown(keyCode, event); } @Override protected void onResume() { super.onResume(); Logger.d("onResume " + url); //同步 cookie 到 webview syncCookie(url); webSettings.setJavaScriptEnabled(true); } @Override protected void onStop() { super.onStop(); webSettings.setJavaScriptEnabled(false); } }

Activity 的佈局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <WebView android:id="@+id/web_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <ProgressBar android:id="@+id/web_bar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="-7dp" android:layout_marginTop="-7dp" android:indeterminate="false" /> </RelativeLayout> 

 

 

上面兩個文件複製過去就能用,進度條的顏色能夠任意定製。

3、H5端代碼(Vue實現)

相比之下H5這邊的代碼就比較少了,只需在進入頁面時從 cookie 中取出 token 等登陸信息。

其實若是大家後端的校驗是從 cookie 中取 token 的話,前端能夠不作任何處理就能訪問成功。

由於其餘接口須要用到 userId 等信息,因此在剛進入頁面時從 cookie 取出 UserInfo 並保存到 vuex 中,在任何地方均可以隨時用 UserInfo 啦。

//從Cookie中取出登陸信息並存入 vuex 中 getCookieAndStore() { let userInfo = { "unitType": CookieUtils.getCookie("unitType"), "unitId": CookieUtils.getCookie("unitId"), "refresh_token": CookieUtils.getCookie("refresh_token"), "userId": CookieUtils.getCookie("userId"), "access_token": CookieUtils.getCookie("access_token"), "login_name": CookieUtils.getCookie("login_name"), }; this.$store.commit("setUserInfo", userInfo); } 

 

把這個方法放到儘量早的執行到的頁面的生命週期方法中,好比 created()mounted()、或 activated()。由於個人頁面中用到了 <keep-alive>,因此爲了確保每次進來都能拿到信息,把上面的方法放到了 activated() 中。

上面用到了一個工具類 :CookieUtils,代碼以下:

主要是根據名字取出 cookie 中對應的值。

/** * 操做cookie的工具類 */ export default { /** * 設置Cookie * @param key * @param value */ setCookie(key, value) { let exp = new Date(); exp.setTime(exp.getTime() + 3 * 24 * 60 * 60 * 1000); //3天過時 document.cookie = key + '=' + value + ';expires=' + exp + ";path=/"; }, /** * 移除Cookie * @param key */ removeCookie(key) { setCookie(key, '', -1);//這裏只須要把Cookie保質期退回一天即可以刪除 }, /** * 獲取Cookie * @param key * @returns {*} */ getCookie(key) { let cookieArr = document.cookie.split('; '); for (let i = 0; i < cookieArr.length; i++) { let arr = cookieArr[i].split('='); if (arr[0] === key) { return arr[1]; } } return false; } } 

 

 

以上就是用最簡單的方法同步安卓原生登陸狀態到H5網頁中的方法。若是你有更便捷的方式,歡迎在評論區交流。


全棧全平臺開源項目 CodeRiver

CodeRiver 是一個免費的項目協做平臺,願景是打通 IT 產業上下游,不管你是產品經理、設計師、程序員或是測試,仍是其餘行業人員,只要有好的創意、想法,均可以來 CodeRiver 免費發佈項目,召集志同道合的隊友一塊兒將夢想變爲現實!

CodeRiver 自己仍是一個大型開源項目,致力於打造全棧全平臺企業級精品開源項目。涵蓋了 React、Vue、Angular、小程序、ReactNative、Android、Flutter、Java、Node 等幾乎全部主流技術棧,主打代碼質量。

目前已經有近 100 名優秀開發者參與,github 上的 star 數量將近 1000 個。每一個技術棧都有多位經驗豐富的大佬坐鎮,更有兩位架構師指導項目架構。不管你想學什麼語言處於什麼技術水平,相信都能在這裏學有所獲。

經過 高質量源碼 + 博客 + 視頻,幫助每一位開發者快速成長。

項目地址:https://github.com/cachecats/coderiver


您的鼓勵是咱們前行最大的動力,歡迎點贊,歡迎送小星星✨ ~

在這裏插入圖片描述

相關文章
相關標籤/搜索