Android優化系列一:日誌清理
簡介
在Android應用開發過程中,通過Log類輸出日誌是一種很重要的除錯手段。
大家對於Log類的使用,一般會形成幾點共識:
- 在Debug模式下列印日誌,在Release模式下不列印日誌
- 避免濫用Log類進行輸出日誌。因為這樣可能造成日誌刷屏,淹沒真正有用的日誌。
- 封裝Log類,以提供同時輸出日誌到檔案等功能
具體細化為以下幾點建議:
- 禁用System.out.println
Android應用中,一般通過封裝過的Log類來輸出日誌,方便控制。而System.out.println是標準的Java輸出方法,使用不當,可能造成Release模式下輸出日誌的結果。 - 禁用e.printStackTrace
禁用理由同上
建議通過封裝過的Log類來輸出異常堆疊資訊
-
Debug模式下,通過一個靜態變數,控制日誌的顯示隱藏。
我一般習慣直接使用BuildConfig.DEBUG,當然,你也可以自己定義一個。
private static final boolean isDebug = BuildConfig.DEBUG;
public static void i(String tag, String msg) {
if (isDebug) {
Log.i(tag, msg);
}
}
4.Release模式下,通過Proguard配置來移除日誌
在Proguard配置檔案中,確保沒有新增 –dontoptimize選項 來禁用優化的前提下,
新增以下程式碼:
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** println(...);
public static *** w(...);
public static *** wtf(...);
}
那麼,是否我們按照上面的做,就真的一勞永逸呢?
我的腦海中浮現出幾個相關問題:
1.Proguard配置中新增的配置,真的可以在Release模式下,移除日誌嗎?
2.如果我們用的是封裝過的Log工具類,應該怎麼配置?
3.移除日誌後,原來在日誌方法中的拼接字串引數,是否還會申請/佔用記憶體?
…
本著大膽假設,小心求證的原則,下面我們通過實踐來探索上面的問題答案。
本文基於以下專案進行測試實踐:
https://github.com/snowdream/test/tree/master/android/test/logtest
反編譯工具:JD-GUI
驗證Proguard配置清理日誌的有效性
CASE
Log.i(TAG,"這樣使用,得到的LOGTAG的值就是DroidSettings," +
"然而並非如此,當DroidSettings這個類進行了混淆之後,類名變成了類似a,b,c這樣的名稱," +
"LOGTAG則不再是DroidSettings這個值了。這樣可能造成的問題就是,內部混淆有日誌的包,我們去過濾DroidSettings " +
"卻永遠得不到任何資訊。");
在新增上述Proguard配置前後,編譯打包Release模式的正式包,使用JD-GUI進行反編譯,對比上述程式碼的編譯後程式碼。
結果
新增配置前
新增配置後
結論
通過比對結果,我們可以得出結論:
通過新增Proguard配置,可以在Release模式下,移除掉日誌。
驗證封裝過的Log工具類,是否有必要進行而外配置
CASE
LogUtil.i(TAG,"這樣使用,得到的LOGTAG的值就是DroidSettings," +
"然而並非如此,當DroidSettings這個類進行了混淆之後,類名變成了類似a,b,c這樣的名稱," +
"LOGTAG則不再是DroidSettings這個值了。這樣可能造成的問題就是,內部混淆有日誌的包,我們去過濾DroidSettings " +
"卻永遠得不到任何資訊。");
在新增上述Proguard配置前後,編譯打包Release模式的正式包,使用JD-GUI進行反編譯,對比上述程式碼的編譯後程式碼。
結果
新增配置前
新增配置後
結論
通過比對結果,我們可以得出結論:
在這種簡單封裝的情況下,我們不需要額外的配置,也可以將封裝過的Log工具類呼叫日誌一起移除。
當然,實際使用過程中,可能封裝更復雜。為了保險起見,可以也新增上Log工具類的配置。示例如下:
-assumenosideeffects class com.github.snowdream.logtest.LogUtil {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** w(...);
}
驗證移除日誌後,字串拼接是否還存在?
CASE
Log.i(TAG,"這樣使用,得到的LOGTAG的值就是DroidSettings," +
"然而並非如此,當DroidSettings這個類進行了混淆之後,類名變成了類似a,b,c這樣的名稱," +
"LOGTAG則不再是DroidSettings這個值了。這樣可能造成的問題就是,內部混淆有日誌的包,我們去過濾DroidSettings " +
"卻永遠得不到任何資訊。");
Log.i(TAG, "這樣使用,得到的LOGTAG的值就是DroidSettings," +
"然而並非如此,當DroidSettings這個類進行了混淆之後,類名變成了類似a,b,c這樣的名稱," +
"LOGTAG則不再是DroidSettings這個值了。這樣可能造成的問題就是,內部混淆有日誌的包,我們去過濾DroidSettings " +
"卻永遠得不到任何資訊。" + index ++);
上面程式碼的區別是:
前面是簡單的字串相加,而後面是字串和變數的相加
在新增上述Proguard配置的前提下,分別針對以上兩段程式碼,編譯打包Release模式的正式包,使用JD-GUI進行反編譯,對比上述程式碼的編譯後程式碼。
結果
簡單字串相加
字串和變數相加
結論
通過比對結果,我們可以得出結論:
如果只是簡單字串相加,是會徹底移除的,並且字串拼接也不見了,不會佔用記憶體。
而如果是字串和變數相加,日誌會移除,但是字串拼接還在,還會佔用記憶體。
驗證日誌中使用函返回值的情況
CASE
LogUtil.i(TAG, getMessage());
LogUtil.i(TAG, "FROM FUNCTION " + getMessage());
private String getMessage() {
return "這樣使用,得到的LOGTAG的值就是DroidSettings," +
"然而並非如此,當DroidSettings這個類進行了混淆之後,類名變成了類似a,b,c這樣的名稱," +
"LOGTAG則不再是DroidSettings這個值了。這樣可能造成的問題就是,內部混淆有日誌的包,我們去過濾DroidSettings " +
"卻永遠得不到任何資訊。";
}
上面程式碼的區別是:
前面是直接使用函式返回值,而後面是字串和函式返回值的相加
在新增上述Proguard配置的前提下,分別針對以上兩段程式碼,編譯打包Release模式的正式包,使用JD-GUI進行反編譯,對比上述程式碼的編譯後程式碼。
結果
直接使用函式返回值
字串和函式返回值相加
結論
通過比對結果,我們可以得出結論:
以上兩種場景下,日誌移除,拼接字串不在了,也不會佔用記憶體。
經過大量實踐後的結論
如果你以為上面就是全部真相的話,就錯了。
經過大量的測試實踐,實際上真相更復雜。
以下是開啟Proguard前提下,各種情況下的測試結論:
- Log.i(簡單字串)
- Log.i(區域性變數)
- Log.i(成員變數)
- Log.i(簡單字串+區域性變數)
以上四種情況,日誌被徹底移除,不會額外增加記憶體。 - Log.i(簡單字串+成員變數)
日誌被移除,但是字串拼接會存在,並佔用記憶體。 - Log.i(成員函式) 其中,成員函式返回值為: 簡單字串
- Log.i(成員函式) 其中,成員函式返回值為: 簡單字串+區域性變數
以上兩種情況,日誌被徹底移除,不會額外增加記憶體。 - Log.i(成員函式) 其中,成員函式返回值為: 簡單字串+成員變數
日誌被移除,但是字串拼接會存在,並佔用記憶體。
注:以上所有情況,引數都是指第二個或者後面的引數。第一個引數,我都使用了靜態成員變數:
private static final String TAG = MainActivity.class.getSimpleName();
優化建議
1.確保沒有開啟 –dontoptimize選項的前提下,新增Proguard優化日誌配置
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** e(...);
public static *** i(...);
public static *** v(...);
public static *** println(...);
public static *** w(...);
public static *** wtf(...);
}
2.針對這種情況“Log.i(成員函式) 其中,成員函式返回值為: 簡單字串+成員變數”
目前並沒有辦法規避,不建議這麼使用。
3.針對這種情況”Log.i(簡單字串+成員變數)”
我們的解決方案是,在封裝的Log工具類方法中,使用變長引數。
下面是一個簡單的示例:
package com.github.snowdream.logtest;
import android.text.TextUtils;
import android.util.Log;
/**
* Created by snowdream on 16-10-22.
*/
public class LogUtil {
private static final boolean isDebug = BuildConfig.DEBUG;
public static void i(String tag, String... args) {
if (isDebug) {
Log.i(tag, getLog(tag,args));
}
}
public static void d(String tag, String... args) {
if (isDebug) {
Log.i(tag, getLog(tag,args));
}
}
public static void v(String tag, String... args) {
if (isDebug) {
Log.i(tag, getLog(tag,args));
}
}
public static void w(String tag, String... args) {
if (isDebug) {
Log.i(tag, getLog(tag,args));
}
}
public static void e(String tag, String... args) {
if (isDebug) {
Log.i(tag, getLog(tag,args));
}
}
private static String getLog(String tag, String... args){
StringBuilder builder = new StringBuilder();
for (String arg : args){
if (TextUtils.isEmpty(arg)) continue;
builder.append(arg);
}
return builder.toString();
}
}
參考
1.如何安全地列印日誌
2.關於Android Log的一些思考
3.Androrid應用打包release版時關閉log日誌輸出
相關文章
- mysql清理日誌MySql
- 清理日誌 scripts
- 如何清理日誌
- Docker容器日誌清理Docker
- MySQL慢日誌優化MySql優化
- docker 容器日誌清理方案Docker
- oracle 審計日誌清理Oracle
- oracle清理監聽日誌Oracle
- PeopleSoft日誌檔案清理
- rman清理歸檔日誌
- Oracle歸檔日誌清理Oracle
- 減肥日誌系列之一
- 日誌檔案過大清理
- Mongodb預設日誌的清理!MongoDB
- go開發屬於自己的日誌庫-日誌庫優化Go優化
- oracle歸檔日誌過滿清理Oracle
- 【ZooKeeper Notes 9】ZooKeepr日誌清理
- oracle物化檢視日誌系列(一)Oracle
- SEO優化之淺談蜘蛛日誌優化
- Apche日誌系列(4):日誌分析(轉)
- Android 記憶體優化(二)DVM 和 ART 的 GC 日誌分析Android記憶體優化GC
- 雲原生系列5 容器化日誌之EFK
- Oracle之備份和清理監聽日誌、告警日誌指令碼Oracle指令碼
- SQLServer資料庫日誌清理 清除sqlserver2005日誌SQLServer資料庫
- Apche日誌系列(1):訪問日誌(轉)
- Apche日誌系列(2):錯誤日誌(轉)
- Apche日誌系列(3):定製日誌(轉)
- zookeeper 清理snapshot及事務日誌
- 清理tomcat日誌大的檔案Tomcat
- 清理日誌檔案嘗試有效哦
- 最佳實踐(保持、清理ORACLE alert日誌)Oracle
- ELK日誌定期清理 ES索引資料索引
- Oracle歸檔日誌暴增排查優化Oracle優化
- Android效能優化之UncaughtExceptionHandler定製自己的錯誤日誌系統Android優化Exception
- 首屏優化系列(一)優化
- Android 優化之路(一)佈局優化Android優化
- Android效能優化——程式碼優化(一)Android優化
- Docker 日誌自動輪轉和清理配置Docker