1. 程式人生 > >App 研發錄、架構設計、Crash分析和競品技術分析------讀書筆記(第二章)

App 研發錄、架構設計、Crash分析和競品技術分析------讀書筆記(第二章)

網路底層框架設計

1、不要自己定義網路請求框架,網路層不要使用AsyncTask
2、在網路返回資料Response的應該有一個規範的格式

{
    "isError":true,
    "errorType":1,
    "errorMessage":"網路異常"
    "result":""
}

返回的真實資料應該放在result裡面
3、使用原生的ThreadPoolExecutor+Runnable+Handler這裡面只作學習,真真還是用第三方
4、通過介面回撥用的形式來處理網路返回的結果,錯誤的地方需要統一處理

5、應該設計App資料快取設計
我們將url作為快取的key,在請求網路的時候首先檢測快取中是否有資料,有則直接返回,否則請求網路,資料請求成功過後將資料快取起來,使用CacheManager用於操作讀寫快取資料,並判斷資料是否過期,快取中存放的實體就是CacheItem,對於快取資料是直接把字串存放到SD卡中,如果有需要加密,以bigy形式序列化到本地
6、App應該設定強制更新的按鈕,如果設定的時候長了不更新,使用者體驗就會不好,因此我們需要為設定一個強制更新按鈕
7、有時候服務端的介面還沒有設計好,而客戶端又需要進行開發,或者是已經商定好了,一些原因又需要更改,這時候客戶端就可以自己設計一個MockService來模擬資料

// 宣告一個介面,然後需要模擬的資料時,實現這個MockService重寫getJsonData()就可以了

import com.infrastructure.net.Response;

public abstract class MockService {
    public abstract String getJsonData();

    public Response getSuccessResponse() {
        Response response = new Response();
        response.setError(false);
        response.setErrorType(0
); response.setErrorMessage(""); return response; } public Response getFailResponse(int errorType, String errorMessage) { Response response = new Response(); response.setError(true); response.setErrorType(errorType); response.setErrorMessage(errorMessage); return
response; } }

下面模擬資料

// 模擬天氣
import com.alibaba.fastjson.JSON;
import com.infrastructure.net.Response;
import com.youngheart.entity.WeatherInfo;

public class MockWeatherInfo extends MockService {  
    @Override
    public String getJsonData() {
        WeatherInfo weather = new WeatherInfo();
        weather.setCity("Beijing");
        weather.setCityid("10000");

        Response response = getSuccessResponse();       
        response.setResult(JSON.toJSONString(weather));
        return JSON.toJSONString(response);
    }
}

// 模擬登入成功

import com.alibaba.fastjson.JSON;
import com.infrastructure.net.Response;
import com.youngheart.entity.UserInfo;

public class MockLoginSuccessInfo extends MockService {
    @Override
    public String getJsonData() {
        UserInfo userInfo = new UserInfo();
        userInfo.setLoginName("jianqiang.bao");
        userInfo.setUserName("包建強");
        userInfo.setScore(100);

        Response response = getSuccessResponse();       
        response.setResult(JSON.toJSONString(userInfo));
        return JSON.toJSONString(response);
    }
}

8、使用者登入場景的設計
首先,應該有一個全域性的User,在每次登入成功過後,會將其isLogin屬性標記為true,退出時標記為false,這個User全域性變數要支援序列化到本地的功能,這樣資料才不會因為記憶體回收而丟失
下面事例:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

import com.youngheart.utils.Utils;

public class User implements Serializable, Cloneable {
    /**
     * @Fields: serialVersionUID
     */
    private static final long serialVersionUID = 1L;

    private static User instance;

    private User() {

    }

    public static User getInstance() {
        if (instance == null) {
            Object object = Utils.restoreObject(AppConstants.CACHEDIR + TAG);
            if (object == null) { // App第一次啟動,檔案不存在,則新建之
                object = new User();
                Utils.saveObject(AppConstants.CACHEDIR + TAG, object);
            }

            instance = (User) object;
        }

        return instance;
    }

    public final static String TAG = "User";

    private String loginName;
    private String userName;
    private int score;
    private boolean loginStatus;

    public void reset() {
        loginName = null;
        userName = null;
        score = 0;
        loginStatus = false;
    }

    public void save() {
        Utils.saveObject(AppConstants.CACHEDIR + TAG, this);
    }

    public String getLoginName() {
        return loginName;
    }
    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
    public boolean isLogin() {
        return loginStatus;
    }
    public void setLoginStatus(boolean loginStatus) {
        this.loginStatus = loginStatus;
    }

    // -----------以下3個方法用於序列化-----------------
    public User readResolve() throws ObjectStreamException, CloneNotSupportedException {
        instance = (User) this.clone();
        return instance;
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ois.defaultReadObject();
    }

    public Object Clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

9、自動登入
所謂自動登入,就是登入成功後,重啟App後用戶仍然是登入狀態。
一個比較簡單的做法就是本地儲存使用者名稱和密碼,在重啟App後,檢查本地是否有使用者名稱或者是密碼,有直接傳入登入介面
這樣做是有風險的:

  • 本地儲存使用者名稱密碼是有安全風險,要使用不對稱加密儲存
  • 還有一般的登入成功過後就會拿著這個使用者的Id然後介面返回資料,這樣做也是不安全的,一種補救措施,每次呼叫使用者相關的介面時,都需要把userId和加密後的密碼一起傳入過去,密碼必須是經過雜湊雜湊演算法不對稱加密
  • 本地儲存使用者名稱和密碼另一個問題就是登入頁面一閃而過,我們放棄這種方式,另外一種方式就是使用Cookie也就是access_tokn來解決問題

下面我們需要修改App網路底層的
1)、登入成功過後需要儲存本地的token,然後放到HttpRequest的header頭裡面,
saveCookies()
2)、在每次請求的時候取出Cookies增加在頭裡面
3)、如果使用者資訊相關的,則判斷HttpRequest中的Cookies是否有效,如果有效,就去執行後續邏輯並返回結果;否則,返回Cookies過期失效的錯誤資訊。
4)、如果是使用者無關的,則不需要檢查HttpRequest中Cookie,直接執行下面的邏輯

  • 使用者登出過後應該清除Cookies、重置判斷使用者App是否登入標誌
  • 註冊成功過後,
  • 對於Cookie過期,App應該跳轉到登入頁面,讓使用者手動進行登入,這裡有一個比較挑戰性的工作,就是登入成功後,應該返回手動登入之前的那個頁面、後面再講解這個技術

10、Cookie過期需要統一處理,就是在網路請求返回結果的時候如果返回的錯誤是Cookie過期就需要處理,重新登入,修改介面回撥那就是onCookieExpired();

11、防止黑客刷庫

  • 一種解決辦法是為登入介面增加第三個引數,也就是上文提及的驗證碼,每次登入的時候都要求輸入驗證碼,其實就是為了防止黑客刷庫
  • 還有其他方案,需要服務端和客戶端配合處理,同一時間發現在一個ip地址訪問頻繁就返回錯誤或者要求輸入驗證碼

12、HTTP 頭中的奧妙
http請求都header和body兩部分,http頭裡面可以加一些
accept 、accept-lanaguage、referrer、user-agent、accept-encoding

13、時間校準
http response頭中的另一個重要屬性:Date,這個屬性中記錄了MobildeApi當前的伺服器時間,介面永遠使用UTC,而是減去UIC時間1970年1月1日的差值,這是一個long型別的長整數,需要修改網路請求

14、開啟Gzip壓縮
在App發起請求,新增要求支援gzip的key-value,這裡的key是Accept-Encodeing,value是Gzip增加在header頭裡面,加完過後怎麼檢測在返回的HttpResponse頭中的content-encodeing欄位是否包含了gzip值,有沒有gzip解析的方式不一樣