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解析的方式不一樣