This article addresses the problem of synchronizing the login status of native and H5 pages when the popular Android/IOS native apps embed WebView pages.

Most hybrid applications login to the native page, which raises the question of how to pass login status to the H5 page. Can’t open the web page and then log in the system from the web page… Synchronization of login status on both sides is required.

More than 100 experienced developers participated in the full stack full platform open source project on Github, nearly 1000 star. Want to know or participate? Project address: github.com/cachecats/c…

1. Synchronization principle

In fact, synchronizing the login status means sending the login information such as the token and userId returned by the server to the H5 web page, and bringing the necessary verification information when sending a request. But pure H5 development has its own login page, which is saved in cookies or other places after login; H5 web page in mixed development does not maintain the login page by itself, but by native maintenance, when opening the WebView will be the login information to the web page.

There are many ways to achieve this, you can use the native and JS communication mechanism to send the login information to H5, about the native and JS two-way communication, I wrote a detailed article before, unfamiliar students can have a look:

Android WebView interacts with JS (Vue)

Here we’re going to use another, simpler method, which is to write cookies directly to the WebView through the Android Cookie manager.

Two, Android code

That’s what android development needs to do.

Here are the steps:

  1. Prepare an objectUserInfoTo receive data returned by the server.
  2. After successful loginUserInfoFormat as JSON string saveSharedPreferencesIn the.
  3. Open webView fromSharedPreferencesRemove what was saved in the previous stepUserInfo
  4. Create a newMapUserInfoSave it as a key-value pair for the next step as a cookie.
  5. willUserInfoThe information inCookieManagerSave to cookie.

It seems that there are many steps, in fact, it is so simple to get the data returned by the server, and then save it into the cookie through CookieManager, but the middle needs to do several data conversion.

Let’s go through the code step by step as described above. I won’t post the UserInfo object, it’s just basic information.

Save UserInfo to SharedPreferences

After the login interface request succeeds, the UserInfo object is retrieved. Save UserInfo to SharedPreferences with the following line in the success callback.

// Store UserData to SP sputils.putUserData (context, result.getData());Copy the code

SPUtils is a utility class for manipulating SharedPreferences.

Contains methods for saving and retrieving UserInfo (the object name in the code is UserData), formatting the object as a JSON string when saving and formatting the JSON string as an object when fetching.

public class SPUtils {
    /** * The name of the file saved in the phone */
    public static final String FILE_NAME = "share_data";
    
    /** * Store user information **@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);
    }

    /** * Get user data **@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);
        returnuserData; }}Copy the code

Take the UserInfo and save it to a cookie

This encapsulates a ProgressWebviewActivity with a progress bar that opens the Activity and passes in the URL of the web page. The synchronization cookie logic is performed in the Activity’s onResume lifecycle method. Why is it executed in onResume? It prevents the App from cutting from the background to the foreground and the WebView reloading doesn’t get the cookie, so it’s probably fine in onCreate most of the time, but it’s safest in onResume.

@Override
protected void onResume(a) {
    super.onResume();
    Logger.d("onResume " + url);
    // Synchronize cookies to webView
    syncCookie(url);
    webSettings.setJavaScriptEnabled(true);
}

/** * Synchronize webView Cookie */
private void syncCookie(String url) {
    boolean b = CookieUtils.syncCookie(url);
    Logger.d("Set cookie result:" + b);
}
Copy the code

The synchronization operations are encapsulated in the CookieUtils utility class. Here is the code for CookieUtils:

This utility class does three things: it takes the UserInfo from SharedPreferences, wraps the UserInfo into a Map, and traverses the Map to store cookies in turn.

public class CookieUtils {

    /** * Sync cookies to WebView **@paramUrl The URL that the WebView will load@returnTrue Cookie synchronization succeeds. False Cookie synchronization fails *@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;
    }

    /** * Assemble the value ** needed in the Cookie@return* /
    public static Map<String, String> getCookieMap(a) {

        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;
    }

    /** * concatenates the Cookie string **@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 days overdue
        return key + "=" + value + "; expires=" + date + "; path=/"; }}Copy the code

The last two lines of the syncCookie() method verify that the cookie was saved successfully.

At this point, Android’s work is done, and H5 can retrieve the data stored by Android directly from the Cookie.

ProgressWebviewActivity encapsulation

Here is the encapsulated ProgressWebviewActivity with a progress bar.

/** * WebView with progress bar. Use native 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(a) {
       //Webview
       mWebView = findViewById(R.id.web_view);
       / / the progress bar
       web_bar = findViewById(R.id.web_bar);
       // Set the color of the progress bar
       web_bar.getProgressDrawable().setColorFilter(Color.RED, android.graphics.PorterDuff.Mode.SRC_IN);

       // Configure the WebView as necessary
       settingWebView();
       settingWebViewClient();
       
       // Synchronize cookies to webView
       syncCookie(url);

       // Load the URL
       mWebView.loadUrl(url);
   }

   /** * Configure the webView as necessary */
   private void settingWebView(a) {
       webSettings = mWebView.getSettings();
       // The WebView must be set up to support Javascript if the page to be accessed interacts with Javascript
       // If the HTML is loaded and JS is performing animation and other operations, it will cause resource waste (CPU, battery).
       // Set setJavaScriptEnabled() to false in onStop and true in onResume
       webSettings.setJavaScriptEnabled(true);

       // Set up the adaptive screen
       webSettings.setUseWideViewPort(true); // Resize the image to fit the WebView
       webSettings.setLoadWithOverviewMode(true); // Zoom to the size of the screen

       // Zoom
       webSettings.setSupportZoom(true); // Supports scaling and defaults to true. That's the premise of the following one.
       webSettings.setBuiltInZoomControls(true); // Set the built-in zoom control. If false, the WebView is not scalable
       webSettings.setDisplayZoomControls(false); // Hide the native zoom control

       // Other details
       webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK); // Load the cache when there is no network
       //webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // Disable the webView cache
       webSettings.setAllowFileAccess(true); // Set access to files
       webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // Support to open new Windows through JS
       webSettings.setLoadsImagesAutomatically(true); // Support automatic loading of images
       webSettings.setDefaultTextEncodingName("utf-8");// Set the encoding format

       // If not added, some pages can not be loaded, is blank
       webSettings.setDomStorageEnabled(true);

       Android 5.0 and above cannot store third-party Cookies solutions using WebView
       if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
           CookieManager.getInstance().setAcceptThirdPartyCookies(mWebView, true); webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); }}/** * Set WebViewClient and WebChromeClient */
   private void settingWebViewClient(a) {
       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");
           }

           // All link jumps use this method
           @Override
           public boolean shouldOverrideUrlLoading(WebView view, String url) {
               Logger.d("url: ", url);
               view.loadUrl(url);// Forces the URL to be loaded in the current WebView
               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);
               // Update the progress bar
               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:"+ title); }}); }/**
    * 同步 webview 的Cookie
    */
   private void syncCookie(String url) {
       boolean b = CookieUtils.syncCookie(url);
       Logger.d("Set cookie result:" + b);
   }

   /** * Handle the android back key. If the WebView can return, return to the previous page. If the webView cannot return, exit the current webView */
   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
       if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
           mWebView.goBack();// Return to the previous page
           return true;
       }
       return super.onKeyDown(keyCode, event);
   }

   @Override
   protected void onResume(a) {
       super.onResume();
       Logger.d("onResume " + url);
       // Synchronize cookies to webView
       syncCookie(url);
       webSettings.setJavaScriptEnabled(true);
   }

   @Override
   protected void onStop(a) {
       super.onStop();
       webSettings.setJavaScriptEnabled(false); }}Copy the code

The layout file for the 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>
Copy the code

Above two files copy past can be used, the color of the progress bar can be customized.

Iii. H5 end code (Vue implementation)

In contrast, the code of H5 side is less, only need to take out the login information such as token from cookie when entering the page.

In fact, if your backend validation is to fetch the token from the cookie, the front-end can do nothing to access the token.

Because other interfaces need userId and other information, the UserInfo is retrieved from the cookie when entering the page and saved to vuex. UserInfo can be used anywhere at any time.

// Retrieve the login information from the Cookie and store it in 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);
}
Copy the code

Put this method in the earliest possible page lifecycle methods, such as created(), Mounted (), or activated(). Since I use

in my page, I put the above method in activated() to make sure I get the message every time I come in.

Here is a utility class called CookieUtils:

It basically fetches the corresponding value in the cookie based on the name.

/** * Utility class for manipulating cookies */
export default {

  /** * set Cookie * @param key * @param value */
  setCookie(key, value) {
    let exp = new Date(a); exp.setTime(exp.getTime() +3 * 24 * 60 * 60 * 1000); / / 3 days overdue
    document.cookie = key + '=' + value + '; expires=' + exp + "; path=/";

  },

  /** * remove Cookie * @param key */
  removeCookie(key) {
    setCookie(key, ' '.- 1);// You can delete the Cookie by returning it to the shelf life of one day
  },

  /** * get 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; }}Copy the code

This is the easiest way to synchronize your native Android login status to your H5 web page. If you have an easier way, feel free to talk in the comments section.


Full stack full platform open source project CodeRiver

CodeRiver is a free project collaboration platform with a vision to connect the upstream and downstream of the IT industry. Whether you are a product manager, designer, programmer, tester, or other industry personnel, you can come to CodeRiver to publish your project for free and gather like-minded team members to make your dream come true!

CodeRiver itself is also a large open source project, committed to creating full stack full platform enterprise quality open source projects. It covers React, Vue, Angular, applets, ReactNative, Android, Flutter, Java, Node and almost all the mainstream technology stacks, focusing on code quality.

So far, nearly 100 top developers have participated, and there are nearly 1,000 stars on Github. Each stack is staffed by a number of experienced gurus and two architects to guide the project architecture. No matter what language you want to learn and what skill level you are at, you can learn it here.

Help every developer grow quickly through high quality source code + blog + video.

Project address: github.com/cachecats/c…


Your encouragement is our biggest power forward, welcome to praise, welcome to send small stars ✨ ~