1. 程式人生 > >【騰訊內部乾貨分享】分析Dalvik位元組碼進行減包優化

【騰訊內部乾貨分享】分析Dalvik位元組碼進行減包優化

本文由騰訊WeTest團隊提供,更多資訊可直接戳連結檢視:http://wetest.qq.com/lab/
微訊號:TencentWeTest

小編導讀:無論是開發還是發行,不可避免的會遇到包體過大需要壓縮的情況。
對於發行商來說,儘管現在wifi遍地,但就算移動運營有一天開放4G免費,包體越小的你依然具備優勢。
對於玩家來說則更為簡單明瞭,使用者喜歡連續點選幾個應用同時下載,包體小的很快就跑完讀條更容易被玩家接受。
但真正壓縮的時候遇到的麻煩事可真的不少:對遊戲整體的壓縮卻不影響場景,對圖片的壓縮卻不影響品質。最慘的對程式碼進行壓縮,簡直是讓程式們熬白了頭髮只為包體再小几K。
今天,直面減包優化這件事,分享一下騰訊人是怎麼進行減包的!

作者:移動客戶端開發工程師 彭旭康

Android結合版最近幾個版本在包大小配額上超標了,先後採用了包括圖片壓縮,功能H5,無用程式碼移除等手段減包,還是有著很大的減包壓力。組內希望我能從程式碼的角度減少一些包大小,感覺有點壓力山大。經過一段時間對手q安裝包反編譯後的Dalvik位元組碼的分析,發現通過調整Java程式碼可以減少編譯後的Dalvik位元組碼,從而減少包大小。在這方面我做了許多的嘗試,有成功有失敗,拿出來給大家分享分享,多拍磚多交流。

優化思路
通過dexdump反編譯apk中的dex,得到對應Dalvik位元組碼,找到尋找冗餘的位元組碼,嘗試去除或替換冗餘的位元組碼

目前主要是替換或去除原有的java程式碼,減少對應的Dalvik指令,從而減少安裝包大小。

現在主要是從Dalvik位元組碼分析來調整Java程式碼,之後希望能夠通過ASM等框架直接調整位元組碼減少現在的包大小。

優化效果
去除初始化賦值方案 ————減少整個手q的釋出包大小80k左右。

插樁函式優化———減少整個手q的釋出包大小2k左右。

其它嘗試方案,包括字串拼接、移除interface很多空方法等,因為效果比較小、難以統一修改等問題,只是列舉下分析結果,大家如果專案中出現的量比較多也是可以嘗試去優化的。

優化方案如下:
1、去除初始化賦值冗餘
1.1、問題分析:
靜態變數為類的所有物件共享,在類載入的準備階段就會初始設定為系統零值(如下圖),比如String被設定初始值為null,而在類中存在

public static String A=null;

這樣的賦值行為會在之後的()類構造器方法中執行,重複設定String A為null,增加了對應的()方法的Dalvik指令,沒有必要,可以幹掉。

成員變數在物件建立記憶體分配完成後,對應的記憶體空間會被初始設定為系統零值(和靜態變數一樣),比如int型別被設定為0,而在類中存在

public int B=0;

這樣的賦值行為會在之後的()物件構造方法中執行,重複設定int B為0,增加了對應的方法中的Dalvik指令,沒有必要,可以幹掉。
圖片描述
對於初始化賦值為系統分配預設零值的靜態變數和成員變數,去掉初始化賦值,直接使用系統賦的系統零值,可以減少和中的Dalvik指令,從而減少包大小,而且可以提高類載入和物件建立的效率。

public static String A=null;    改成 public static String A;
public int B=0;  改成 public int B;

1.2、優化要點
注意對於static final的變數必須賦初值;

interface的變數都是static final型別的;

注意只有賦值為系統賦予的零值的靜態變數和成員變數才能按照這種方式優化,其它比如區域性變數的改動會導致編譯不通過等問題。

1.3、冗餘示例:
優化前:

public class FrostTest {
        public int report_posi=0;
        public FrostTest(){
        }
}

對應位元組碼:

0795b4:                                        |[0795b4] com.example.frosttest.FrostTest.<init>:()V
0795c4: 7010 5925 0100                         |0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // [email protected]
0795ca: 1200                                   |0003: const/4 v0, #int 0 // #0
0795cc: 5910 c909                              |0004: iput v0, v1, Lcom/example/frosttest/FrostTest;.report_posi:I // [email protected]
0795d6: 0e00                                   |0009: return-void

優化後:

public class FrostTest {
        public int report_posi;
        public FrostTest(){
        }
}

對應位元組碼:

0795b4:                                        |[0795b4] com.example.frosttest.FrostTest.<init>:()V
0795c4: 7010 5925 0000                         |0000: invoke-direct {v0}, Ljava/lang/Object;.<init>:()V // [email protected]
0795ca: 0e00                                   |0003: return-void

減少了兩行Dalvik指令的執行,最後分析結果平均優化一處可以減少安裝包8個位元組左右。
1.4、優化結果:
目前在手Q6.3.0分支上利用自行寫的過濾指令碼(可以私下找我要對應的優化指令碼用於對應的工程)可以看到優化的效果,如果對整個手q執行這個方案,預計能夠優化80k左右,修改了4677個檔案,修改了17164處冗餘。

2、調整插樁對應的程式碼
Qzone補丁包引入了插樁這一步,需要在所有qzone類的建構函式中加入對mqq.app.MobileQQ類的引用。
優化的方案是將插樁插入到物件建構函式中的語句由

CtConstructor localCtConstructor = arrayOfCtConstructor[0];
localCtConstructor.insertBeforeBody("if (com.qzone.dalvikhack.NotDoVerifyClasses.DO_VERIFY_CLASSES) System.out.print(mqq.app.MobileQQ.class);");
localCtClass.writeFile(str);

改為

CtConstructor localCtConstructor = arrayOfCtConstructor[0];
localCtConstructor.insertBeforeBody(“if (com.qzone.dalvikhack.NotDoVerifyClasses.DO_VERIFY_CLASSES) mqq.app.MobileQQ.class.getName();”);
localCtClass.writeFile(str);

以Qzone某個類的為例,由原本的位元組碼

0e640c:                                        |[0e640c] ADV_REPORT.E_REPORT_POSITION.<init>:()V
0e641c: 7010 0b84 0200                         |0000: invoke-direct {v2}, Ljava/lang/Object;.<init>:()V // [email protected]
0e6422: 6300 1f26                              |0003: sget-boolean v0, Lcom/qzone/dalvikhack/NotDoVerifyClasses;.DO_VERIFY_CLASSES:Z // [email protected]
0e6426: 3800 0900                              |0005: if-eqz v0, 000e // +0009
0e642a: 6200 4463                              |0007: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // [email protected]
0e642e: 1c01 2a14                              |0009: const-class v1, Lmqq/app/MobileQQ; // [email protected]
0e6432: 6e20 7883 1000                         |000b: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.print:(Ljava/lang/Object;)V // [email protected]
0e6438: 0e00                                   |000e: return-void

變成了

0e63a4:                                        |[0e63a4] ADV_REPORT.E_REPORT_POSITION.<init>:()V
0e63b4: 7010 8183 0100                         |0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // [email protected]
0e63ba: 6300 0326                              |0003: sget-boolean v0, Lcom/qzone/dalvikhack/NotDoVerifyClasses;.DO_VERIFY_CLASSES:Z // [email protected]
0e63be: 3800 0700                              |0005: if-eqz v0, 000c // +0007
0e63c2: 1c00 6714                              |0007: const-class v0, Lmqq/app/MobileQQ; // [email protected]
0e63c6: 6e10 2883 0000                         |0009: invoke-virtual {v0}, Ljava/lang/Class;.getName:()Ljava/lang/String; // [email protected]
0e63cc: 0e00                                   |000c: return-void

這裡替換一處程式碼,將System.out.print改成getName,可以減少物件建構函式的一行Dalvik指令,替換了1314處初始化函式中插入的程式碼,最終將對應的qzone_plugin.apk減少了2459位元組,整個手q減少2457位元組左右。一行程式碼,2k收益,其實還是很划算的。
3、字串拼接
下面是我針對String拼接的特殊情況“變數+”””和“””+變數”的不同形式舉例分析Dalvik位元組碼。

public abstract class FrostTest implements FrostInterface{
public String a="f";
public int b=1;
@Override
public void doSth1() {
    Log.i("frostpeng", a);
}

@Override
public void doSth2() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", a+"");
}

@Override
public void doSth3() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", ""+a);
}

@Override
public void doSth4() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", String.valueOf(a));
}

@Override
public void doSth5() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", String.valueOf(b));
}

public void doSth6() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", b+"");
}

public void doSth7() {
    // TODO Auto-generated method stub
    Log.i("frostpeng", ""+b);
}

}

位元組碼

098ee4:                                        |[098ee4] com.example.frosttest.FrostTest.doSth1:()V
098ef4: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098ef8: 5421 c809                              |0002: iget-object v1, v2, Lcom/example/frosttest/FrostTest;.a:Ljava/lang/String; // [email protected]
098efc: 7120 a321 1000                         |0004: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
098f02: 0e00                                   |0007: return-void

098f04:                                        |[098f04] com.example.frosttest.FrostTest.doSth2:()V
098f14: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098f18: 2201 2f05                              |0002: new-instance v1, Ljava/lang/StringBuilder; // [email protected]
098f1c: 5432 c809                              |0004: iget-object v2, v3, Lcom/example/frosttest/FrostTest;.a:Ljava/lang/String; // [email protected]
098f20: 7110 a225 0200                         |0006: invoke-static {v2}, Ljava/lang/String;.valueOf:(Ljava/lang/Object;)Ljava/lang/String; // [email protected]
098f26: 0c02                                   |0009: move-result-object v2
098f28: 7020 a525 2100                         |000a: invoke-direct {v1, v2}, Ljava/lang/StringBuilder;.<init>:(Ljava/lang/String;)V // [email protected]
098f2e: 6e10 b125 0100                         |000d: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // [email protected]
098f34: 0c01                                   |0010: move-result-object v1
098f36: 7120 a321 1000                         |0011: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
098f3c: 0e00                                   |0014: return-void

098f40:                                        |[098f40] com.example.frosttest.FrostTest.doSth3:()V
098f50: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098f54: 2201 2f05                              |0002: new-instance v1, Ljava/lang/StringBuilder; // [email protected]
098f58: 7010 a325 0100                         |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // [email protected]
098f5e: 5432 c809                              |0007: iget-object v2, v3, Lcom/example/frosttest/FrostTest;.a:Ljava/lang/String; // [email protected]
098f62: 6e20 ac25 2100                         |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; // [email protected]
098f68: 0c01                                   |000c: move-result-object v1
098f6a: 6e10 b125 0100                         |000d: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // [email protected]
098f70: 0c01                                   |0010: move-result-object v1
098f72: 7120 a321 1000                         |0011: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
098f78: 0e00                                   |0014: return-void

098f7c:                                        |[098f7c] com.example.frosttest.FrostTest.doSth4:()V
098f8c: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098f90: 5421 c809                              |0002: iget-object v1, v2, Lcom/example/frosttest/FrostTest;.a:Ljava/lang/String; // [email protected]
098f94: 7110 a225 0100                         |0004: invoke-static {v1}, Ljava/lang/String;.valueOf:(Ljava/lang/Object;)Ljava/lang/String; // [email protected]
098f9a: 0c01                                   |0007: move-result-object v1
098f9c: 7120 a321 1000                         |0008: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
098fa2: 0e00                                   |000b: return-void

098fa4:                                        |[098fa4] com.example.frosttest.FrostTest.doSth5:()V
098fb4: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098fb8: 5221 c909                              |0002: iget v1, v2, Lcom/example/frosttest/FrostTest;.b:I // [email protected]
098fbc: 7110 a125 0100                         |0004: invoke-static {v1}, Ljava/lang/String;.valueOf:(I)Ljava/lang/String; // [email protected]
098fc2: 0c01                                   |0007: move-result-object v1
098fc4: 7120 a321 1000                         |0008: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
098fca: 0e00                                   |000b: return-void

098fcc:                                        |[098fcc] com.example.frosttest.FrostTest.doSth6:()V
098fdc: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
098fe0: 2201 2f05                              |0002: new-instance v1, Ljava/lang/StringBuilder; // [email protected]
098fe4: 5232 c909                              |0004: iget v2, v3, Lcom/example/frosttest/FrostTest;.b:I // [email protected]
098fe8: 7110 a125 0200                         |0006: invoke-static {v2}, Ljava/lang/String;.valueOf:(I)Ljava/lang/String; // [email protected]
098fee: 0c02                                   |0009: move-result-object v2
098ff0: 7020 a525 2100                         |000a: invoke-direct {v1, v2}, Ljava/lang/StringBuilder;.<init>:(Ljava/lang/String;)V // [email protected]
098ff6: 6e10 b125 0100                         |000d: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // [email protected]
098ffc: 0c01                                   |0010: move-result-object v1
098ffe: 7120 a321 1000                         |0011: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
099004: 0e00                                   |0014: return-void

099008:                                        |[099008] com.example.frosttest.FrostTest.doSth7:()V
099018: 1a00 b715                              |0000: const-string v0, "frostpeng" // [email protected]
09901c: 2201 2f05                              |0002: new-instance v1, Ljava/lang/StringBuilder; // [email protected]
099020: 7010 a325 0100                         |0004: invoke-direct {v1}, Ljava/lang/StringBuilder;.<init>:()V // [email protected]
099026: 5232 c909                              |0007: iget v2, v3, Lcom/example/frosttest/FrostTest;.b:I // [email protected]
09902a: 6e20 a825 2100                         |0009: invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;.append:(I)Ljava/lang/StringBuilder; // [email protected]
099030: 0c01                                   |000c: move-result-object v1
099032: 6e10 b125 0100                         |000d: invoke-virtual {v1}, Ljava/lang/StringBuilder;.toString:()Ljava/lang/String; // [email protected]
099038: 0c01                                   |0010: move-result-object v1
09903a: 7120 a321 1000                         |0011: invoke-static {v0, v1}, Landroid/util/Log;.i:(Ljava/lang/String;Ljava/lang/String;)I // [email protected]
099040: 0e00                                   |0014: return-void

從示例中可以看出各類字串拼接方式的優劣,如果用String.valueOf()絕對是最優方案。只是通過對“變數+”””和“””+變數”的形式在手q整個專案調整以後大概能夠優化6k左右,如果只是優化qzone部分,效果比較微小,指令碼方面不太好過濾對應情況,暫時沒有加入,只是做了下試驗。
PS:其實“String +”一般來說比StringBuffer的拼接更費位元組碼,這個部分可以自行驗證,前提是a+b+…的形式中首位a這個為變數,而不是常量,如果a是常量,則實際上和StringBuffer等同,這也是個優化點,具體可以參考文章 從位元組碼視角看java字串的拼接 。

4、調整interface到class,減少實現介面造成的空方法
很多程式碼中實現介面時有很多的空方法,並沒有作用但還是會佔用位元組碼,希望能夠通過調整對應的interface為class,去除冗餘的空方法,減少位元組碼,從而減少包大小。
示例如下:

public interface FrostInterface {
public abstract void doSth1();
public abstract void doSth2();
public abstract void doSth3();
}

public class FrostTest1 implements FrostInterface{

@Override
public void doSth1() {
    // TODO Auto-generated method stub

}

@Override
public void doSth2() {
    // TODO Auto-generated method stub

}

@Override
public void doSth3() {
    // TODO Auto-generated method stub

}

改成

public abstract class FrostTest implements FrostInterface{

@Override
public void doSth1() {
    // TODO Auto-generated method stub

}

@Override
public void doSth2() {
    // TODO Auto-generated method stub

}

@Override
public void doSth3() {
    // TODO Auto-generated method stub

}

}

public class FrostTest1 extends FrostTest{

}

該方案的缺點在於修改必須手動,難度大,qzone中場景不足以引起量變,而且因為Qzone中中還加入了插樁函式的負擔,所以整體優化效果不佳,優化完qzone才2k不到的大小縮減,優化難度高收益小,棄坑。
這些減包思路希望能夠給一起在減包路上踩坑的朋友們一些幫助吧。

本文由騰訊WeTest團隊提供,更多資訊可直接戳連結檢視:http://wetest.qq.com/lab/
微訊號:TencentWeTest

相關推薦

內部乾貨分享分析Dalvik位元組進行優化

本文由騰訊WeTest團隊提供,更多資訊可直接戳連結檢視:http://wetest.qq.com/lab/ 微訊號:TencentWeTest小編導讀:無論是開發還是發行,不可避免的會遇到包體過大需要壓縮的情況。 對於發行商來說,儘管現在wifi遍地,但就

Bugly乾貨分享Android程序保活招式大全

【騰訊Bugly乾貨分享】Android程序保活招式大全 本文來自於騰訊bugly開發者社群,非經作者同意,請勿轉載,原文地址:http://dev.qq.com/topic/57ac4a0ea374c75371c08ce8 作者:騰訊——張興華 目前市面上的應用,貌似除了微信和手Q都會

Bugly乾貨分享移動網際網路測試到質量的轉變

Dev Club 是一個交流移動開發技術,結交朋友,擴充套件人脈的社群,成員都是經過稽核的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。 本期,我們邀請了 TesterHome 測試技術社群聯合創始人“陳曄”,為大家分享《移動網際網路測試到質量的轉

Bugly乾貨分享聊聊蘋果的Bug

作者:張三華 導語 精神哥最近發現, 很多開發者在 iOS10 上遇到了一類堆疊為nano_free字樣的Crash,也有很多人向我們Bugly客服反饋遇到了這類問題,但並沒有好的解決方案。正當大家都束手無策的時候,微信強大的技術團隊針對這類

Bugly乾貨分享總結一個技術總監的教訓和經驗

導語 2017年來了,新年開篇,就不跟大家聊技術啦,給大家分享一篇鵝廠技術總監在多年工作中總結出的教訓和經驗。 這篇文章自從在騰訊內部論壇發表後,精神哥每年都會拿出來重新研讀一番,每次都有新的感悟和收穫,所以強烈推薦給大家。 正文 資深程式設

Bugly乾貨分享打造“微信小程式”元件化開發框架

作者:Gcaufy 導語 Bugly 之前發了一篇關於微信小程式的開發經驗分享(點選閱讀),小夥伴們在公眾賬號後臺問了很多關於小程式開發方面的問題,精神哥在查閱相關內容的時候,發現了龔澄同學自己寫了一個小程式開發框架,真的怒贊,趕緊安利給大家

Bugly乾貨分享美團大眾點評 Hybrid 化建設

本期 T 沙龍探討了移動端熱更新相關的話題。由於沙龍時間的限制,本期我們選取了美團的 Hybrid 化建設、去哪兒的跨平臺 ListView 效能優化、微博 Android 端熱更新踩過的坑話題。還期待熱更新、熱修復哪些話題?歡迎留言給我們。也歡迎報名參加

Bugly乾貨分享Android - 減少APK大小

本文是對Google官方文件 Reduce APK Size 的翻譯,點選“閱讀原文”可以檢視英文原文。 譯者簡介:damonxia(夏正冬),天天P圖Android工程師 使用者經常會避免下載看起來體積較大的應用,特別是在不穩定的2G、3G

Bugly乾貨分享跨平臺 ListView 效能優化

導語 精神哥前陣子去參加了好友小青在北京辦的T沙龍,探討移動端熱更新相關的話題。Bugly 曾為大家介紹過不少騰訊內部的熱更新的框架,正好這次看到了美團,去哪兒以及微博同學在應用熱更新方面的實踐。 上週為大家整理了《美團大眾點評 Hybrid 化建設

Bugly乾貨分享Android ListView與RecyclerView對比淺析--快取機制

作者:黃寧源 一,背景 RecyclerView是谷歌官方出的一個用於大量資料展示的新控制元件,可以用來代替傳統的ListView,更加強大和靈活。 最近,自己負責的業務,也遇到這樣的一個問題,關於是否要將ListView替換為Recycl

Bugly乾貨分享Android 新一代多渠道打包神器

關於作者: 李濤,騰訊Android工程師,14年加入騰訊SNG增值產品部,期間主要負責手Q動漫、企鵝電競等專案的功能開發和技術優化。業務時間喜歡折騰新技術,寫一些技術文章,個人技術部落格:www.ltlovezh.com 。 ApkChanne

Bugly乾貨分享WKWebView 那些坑

導語 WKWebView 是蘋果在 WWDC 2014 上推出的新一代 webView 元件,用以替代 UIKit 中笨重難用、記憶體洩漏的 UIWebView, 擁有60fps滾動重新整理率、和 safari 相同的 JavaScript 引擎。簡單

Bugly乾貨分享你為什麼需要 Kotlin

一、往事 曾經你有段時間研究 Intellij 的外掛開發,企圖編譯 Intellij Idea Community Edition (ICE)的原始碼,結果發現有個奇怪的東西讓你的程式碼無法編譯。。什麼鬼,kt 是什麼玩意兒? 怎麼又有

bugly乾貨分享精神哥手把手教你如何智鬥ANR

上帝說要有ANR,於是Bugly就有了ANR上報,那麼ANR到底是什麼? 最近很多童鞋問起精神哥ANR的問題,那麼這次就來聊一下,雞爪怎麼泡才好吃,噢不,是如何快速定位ANR。 ANR是什麼 簡單說,通常就是App執行的時候,duang~卡住了,怎麼搞都動不

bugly乾貨分享解耦---Hybrid H5跨平臺性思考

跨平臺,是H5最重要的能力之一。而 Hybrid H5 因強依賴於具體 app,往往不具有跨平臺性。這時,將強依賴關係解耦,即可恢復 H5 的跨平臺能力。近期本人負責 手Q 紅包打賞專案的前端開發,因專案涉及到多 app 跨平臺相容,對 hybrid H5

Bugly乾貨分享WebSocket 淺析

前言 在WebSocket API尚未被眾多瀏覽器實現和釋出的時期,開發者在開發需要接收來自伺服器的實時通知應用程式時,不得不求助於一些“hacks”來模擬實時連線以實現實時通訊,最流行的一種方式是長輪詢 。 長輪詢主要是發出一個HTTP請求到伺服器,

Bugly乾貨分享基於RxJava的一種MVP實現

Dev Club 是一個交流移動開發技術,結交朋友,擴充套件人脈的社群,成員都是經過稽核的移動開發工程師。每週都會舉行嘉賓分享,話題討論等活動。 本期,我們邀請了騰訊IEG Android 開發工程師——戴俊,為大家分享《基於RxJava的一種MVP實現》

Bugly乾貨分享RecyclerView 必知必會

導語 RecyclerView是Android 5.0提出的新UI控制元件,可以用來代替傳統的ListView。 Bugly之前也發過一篇相關文章,講解了 RecyclerView 與 ListView 在快取機制上的一些區別: 今天精神哥來給

Bugly乾貨分享深入理解 ButterKnife,讓你的程式學會寫程式碼

0、引子 話說我們做程式設計師的,都應該多少是個懶人,我們總是想辦法驅使我們的電腦幫我們幹活,所以我們學會了各式各樣的語言來告訴電腦該做什麼——儘管,他們有時候也會誤會我們的意思。 突然有一天,我覺得有些程式碼其實,可以按照某種規則生成,但你又不能不

Bugly乾貨分享Android 程序保活招式大全

本文來自於騰訊bugly開發者社群, ,原文地址:https://segmentfault.com/a/1190000006251859作者:騰訊——張興華目前市面上的應用,貌似除了微信和手Q都會比較擔心被使用者或者系統(廠商)殺死問題。本文對 Android 程序拉活進行一