?《App研發錄》這部書是包建強寫的,說來也巧,在讀這邊書之前在看池建強的《Mac 人生元編程》 ,所以讀這本書的時候,將這兩個建強搞混。這本書花了我一周多一點的時間看完。昨天晚看完久久不能寐,一是驚嘆這本書的干貨太多,這本書不同于市面上其他的Android 教程,給你講一堆API方法,Android 基礎,作者從一個APP團隊的負責人的角度高屋建瓴的講解App框架設計,Bug收集匯總分析,團隊建設,項目管理等等方面,而且都是非常適合APP的項目管理者的實在的經驗。二是敬佩作者進入Android 領域也不是很久,但是見解非凡,經驗老道,感到自己做Android 也快兩年了,為啥還達到沒有作者的一半高度。唉,看來自己之前的兩年過得太輕松了,每天混混日子,自以為懂些Android 技術,能實現產品交代的功能,就得過且過,沒有好好的利用好時間,認真學習,加速成長。
題外話太多了,自己覺得有些東西是多余的,是自己的無病呻吟,各位看官,可以忽略不看,直接看下面的干貨。
APP 框架
1. 項目結構
非常贊同作者的觀點,我們App必須具有良好的包結構,按照一定規則將不同的類放到不同的地方,目前市面上有兩者結構,一種是按照類的類型進行歸納放置,例如Activity 類放一起,adapter 放一起,以此類推;另外一種是按照業務邏輯,例如主頁頁面邏輯放在一個小包中,用戶信息邏輯放在一個小包中,當然小包中可以再分包。
兩種都可以,但必須具有一致項目結構,不要弄得不三不四,類到處放,沒有一定規則
2. baseActivity、BaseAdapter基類管理
3. 網絡框架,封裝好網絡層。
不要使用AsyTask,因為他在4.x 版本中是串行,效率不高,而且當請求線程數達到AsyTask上線時會有問題。建議用TheadPoolExecutor+Runnable+Handler 的方式進行封裝。
統一預處理成功及失敗回掉,
4. 數據緩存策略:
為減少請求網絡數據次數,有必要進行數據緩存,將請求網絡的數據,緩存在手機上,為保證數據即時性,設置緩存時間,而且可以根據業務需求對不同數據設置不同的緩存時間,有的數據不咋改變可以設置長些時間,有些數據經常變動,對即時性要求比較高,緩存時間設置短些,設置可以不設置緩存。
另外必須有接口能夠讓用戶強制更新數據,例如用戶下來刷新,就不要使用緩存中數據,而是直接請求最新的網絡數據
5. 用戶登錄
- 考慮用戶登錄各種場景
一種用戶開始登錄然后進入主頁面,二種用戶沒有登錄數據不能進入下一界面,需要跳轉登錄頁,再跳回來,三種用戶請求網路但是用戶身份驗證失敗需要跳到登錄注冊頁。前面一種好說,后面兩種可以設置回調進行處理,有種情況可能比較麻煩,例如用戶進入某一頁面沒有用戶信息,需要跳回登錄注冊頁,可是登錄注冊完成后又得填資料補全身份信息等最后,經過一系列流程后才跳回原始的頁面。
//起始頁面,可能需要跳轉登錄注冊頁
if(User.isLogin()){
//do something
}else{
Intent intent=new Intent(this,LoginActivity.class){
intent.putExtra(AppConstant.NeedCallback,true);
startActivityForResult(intent,LOGIN_REDIEECT_INSIDE);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode,Intent data){
if(resultCode!=Activity.RESULT_OK){
return;
}
swith(requestCode){
case LOGIN_REDIEECT_INSIDE:
// do something
break;
default:
break;
}
}
//登錄頁面 LoginActivity
if(getIntent.getBooleanExtra(AppConstant.NeedCallback){
setResult(Activity.RESULT_OK);
finish();
}ese{
//do otherthing
}
- 用戶信息存儲風險
用戶的信息我們在app中多處用到,我們建議將他持久化處理,并且將其文件存儲起來。要考慮我們用戶信息失效問題。
另外用戶的密碼必須經過加密處理,我建議客戶端將用戶輸入的密碼加密處理后才傳給服務端,服務端對傳過來的密碼也進行一層加密,以保證用戶安全。另外我不建議存儲用戶的密碼,即時是加密后的密碼,可以使用Token機制(有的也叫Cookie機制),儲存Token,這個Token可以當成用戶身份的唯一性標識。用戶客戶端登陸后,服務器生成Token,當用戶調用與用戶信息相關的接口時,將用戶ID 與Token 傳給服務器,服務器首先根據Token與ID驗證用戶正確性,然后才將數據傳給用戶 - 自動登陸功能
自動登陸功能需要儲存用戶的密碼,需要注意密碼的安全性問題。而且自動登陸與輸驗證碼相斥問題。 - 使用Cookie方法保證用戶信息安全
當用戶登錄成功后,從后臺獲取到Cookie,Cookie在Http-Respose的頭文件中,我們將他取出來,下次聯網請求數據時將Cookie放入Http-Request的頭文件中,如果請求數據與用戶信息相關,服務器驗證身份成功才返回數據。每次請求后客戶端必須更新Cookie。另外注意Cookie過期問題,及用戶注銷后,需要將用戶信息清空。
6. 防止黑客刷庫
- 第一種方法,登錄時添加驗證碼
- 第二種方法,登錄三次失敗后才需要用戶輸入驗證碼
- 第三種方法,后臺檢測某一IP在短時間內頻繁訪問登錄接口,然后用戶輸入驗證碼。
7. 時間校準問題
如果后臺或者用戶端對時間準確性要求比較高的話,需要考慮這個問題。因為我們用戶手機上的時間不是非常準確,一般都有一定的偏差。作者建議后臺采用UTC時間,包括入參和返回值,返回值為long類型。客戶端根據得到的UTC時間換算成本地時間。如果需要用戶傳給后臺準確的時間,這是個大問題。用戶那邊其實很難得到比較準確的時間,除了用戶時區問題,另外還有與準確時間偏差問題。可以用戶的時間傳給后臺,后臺比對差值,然后每次不停的比對減少誤差,最后找到一個準確值。
8. gzip 壓縮
一般http采用gzip壓縮,減少傳輸量,縮短傳輸時間。但是在HttpRespose 需要判斷content-encoding字段中是否有gzip,判斷后臺是否經過gzip壓縮。
9. Freso的三級緩存
第一層:Bitmap 緩存
在Android 5.0中,考慮內存管理有了很大的進步,所以講Bitmap 緩存在Java堆中,在之前版本中Bitmap緩存在ashmen中,當app切換到后臺時,Bitmap緩存將會被清空
第二層:內存緩存
內層緩存中存儲了圖片原始壓縮的格式,從內層緩存中取出圖片,在顯示之前必須進行解碼。當app切換到后臺,內存緩存也被清空
第三層:磁盤緩存,也叫本地緩存
本地存儲圖片原始壓縮格式,顯示之前也需先解碼,當app切換后臺,磁盤緩存不會丟失。
10. 流量優化
- 數據壓縮(常見gzip)
- 使用更好的數據傳遞協議。例如比JSON更好的ProtoBuffer
- 合并api,防止客戶端頻繁調用api請求
- http無狀態短連接改成TCP長連接。但是需要對服務器及后臺有要求
- 頁面銷毀需要取消之前的網絡請求
- 增加重試機制,例如get請求失敗重試三次,但是需要防止用戶重復下單,重復發布評論,重復付款等,
- 圖片請求附帶尺寸參數,服務端更加尺寸傳輸最近尺寸的圖片。
- 低流量模式,降低圖片質量或尺寸,甚至可以不返回圖片的極速模式
11.增量更新
app中的資料包,城市列表等素材可以使用增量更新。每次發版素材保證是最新的版本,當有更新時,首先對比本地素材版本號與線上素材版本號,后臺根據他們版本號返回對應的更新內容。客戶端收到后解壓。增量更新的內容有三部分:需要刪除的數據、不變的數據但是需要修改、新增的數據,用戶根據情況各自處理
12. Html 互換技術
當我們的app原生頁面遇到bug,可以馬上切換到Html頁面替換。
Crash及持續集成
1. Crash 的收集
收集的渠道
- 集成第三方統計工具。友盟、leadCould
- 集成Bug管理工具,例如Bugly,BugTag等
- app本地收集,將錯誤日志記錄在本地,然后發送到后臺服務器,另外需要標注Crash時間,Crash名稱,詳細信息,手機版本,設備號,渠道號,當前網絡類型,內存使用情況等等。
本地bug收集需要用到UncaughtExceptionHandler
這個類
2.Crash經驗
- 窗體泄露問題,主要是想關閉彈出框時,他所承載的activity 不在了,而activity關閉了,沒有及時dismiss會導致泄露窗口句柄,不要在子線程操作對話框,另外讓對話框是Activity成員變量,活躍在onCreate()和onDestroy()兩個方法之間,或者建議使用DialogFragment。
- 只有activity 才能添加窗體,傳給Dialog的Context 要為activity。
- 不要相信api 返回數據,必須做非空判斷,類型異常等容錯處理
- 遍歷集合時不能刪除集合中數據,否則發生崩潰。另外多個線程同時操作同一集合數據也可能會發生崩潰
- 當Service或者BroadcastReceiver等中當context 為非activity 跳轉activity需要加
Intent.FLAG_ACITIVITY_NEW_TASK
。 - 當fragment還沒有attach 到activity中調用
getResource().getString()
會報錯,建議在調用之前加上isAdd()
判斷 - Parcelable 反序列化時,例如
a=in.readParcelable(null)
會可能拋出BadParcelabeException:ClassNotFoundException when unmarshalling...
改成這樣就行a=in.readParcelable(A.class.getClassLoader)
。 - List<T> 沒有實現add()、remove()方法, 使用這兩個方法會拋異常,特別注意Arrays.asLis() 返回的是List<T>,而不是ArrayList();
- 只要修改了adapter中的集合的數據,就要馬上調用notifyDataSetChanged方法,以保證列表同步更新,否則崩潰。
- ListView滾動式點擊刷新按鈕后會崩潰,因為之前getCount還有數據,滾動時刷新數據集合就清掉了,getView 中數據為空,報數據越界等異常,一般解決方案是,滾動時,禁止刷新。
- PopupWindow創建后才能再調用setFocusable() 方法。
- PopupWindow.showLocation(view parent,int gravity,in x,int y),當參數parent 為空時,報Unable to add Windowtoken,因為popupWindow要依附activity,activity還沒有onCreate()執行完,就會報此錯誤。
- armeabi 和armeabi-v7a中so包數量不一致,會導致UnsatisfiedLinkError。
- SQLite 支持單線程、多線程、串行三種模式。但是多線程中使用單個數據庫連接不是安全的,當一個線程寫數據,一個在刪數據會拋I/O 異常,當一個操作完關閉數據庫,另外一個還在操作也會導致Crash。
-
<application android:largeHeap="true"
可以增加系統為當前app 分配內存,甚至能到達100M以上,但是每次GC 時間會延長,性能會下降。 - WebView中存在兩種緩存:網頁數據緩存,存儲打開過的頁面及資源;Html5緩存,及appCache。WebView自帶的緩存機制,會將url保存在webviewCashe.db中,將url內容保存在webViewCashe文件夾下,網頁數據圖片、html、js等文件也會存儲起來
ProGuard
ProGuard一共包含以下4個功能:
- 壓縮(Shrink):偵探并移除代碼中無用的類、字段、方法和特性(Attribute)。
- 優化(Optimize):對字節碼進行優化,移除無用的指令
- 混淆(Obfuscate):使用a、b、c、d這樣無意義名稱,對類、字段和方法進行重命名
- 預檢(Preverify):在Java平臺上對處理后的代碼進行預檢。
競品分析
1. 競品app分析方向(針對技術):
- 為啥他們的App體積比我們小
- 為啥他們App訪問速度比我們快
- 為啥他們App基本上不咋崩潰
- 等等···
2. 優化措施
- Splash 廣告提速:首次加載App包中圖片,同事調用API獲取下一次打開圖片的Url,并存儲本地,下次Splash就加載下載下來的新圖片,并調用API查看是否有新 的Splash圖片要下載。
- HTML5頁面提速:將常用的HTMl5打成Zip包,每次啟動時,啟動異步線程,將zip包解壓到本地,每次從本地讀取Html5頁面,就不用從服務器加載Html 頁面。為保證Html5為最新的,每次加載html5頁面之前,可以詢問服務器html的版本號,如果過期重新下載最新zip包。
- zip包采用增量更新機制:每次發版前將最新的Zip包放在App安裝包中。客戶端下載Zip只需包括新增的和修改的就行,另外控制增量包在100KB之內。
- png/jpg使用:手機會對png圖片進行硬件加速,同樣圖片png體積比jpg大,但是加載速度要快些。app圖片優先使用png格式。但是大尺寸圖片,為減少apk 包體積可使用jpg。考慮流量及下載速度需網絡下載的圖片使用jpg。
- 使用webP,vetorDrawable、ttf 等
- 自動選取最佳服務器策略:全國各地多臺服務器,并分別接入電信、移動或者聯通專線,讓app嘗試從哪個服務器連接速度快些
- TCP+ProtoBuf:已有公司拋棄短連接Http+json,開始走TCP+ProtoBuf路線。但是需要保證服務器的性能壓力,考慮網絡狀況情況。
- Native 頁面和Html 頁面相互替換。當原生頁面崩潰掉,使用html頁面頂上。