知識社群登入
位置: 艾鍗學院 Blog > 專業論壇 > 討論
[Android實作問題] How to close Android Log's printing
1樓
Hello Jarey, Everyone

1. 請問當App在Run時是否可以關掉Log的輸出,還是它當手機的開發模式選擇取消就不會有Log的輸出呢,要如何証明呢?
感謝告知!

2. 如何知道手機的OS的版本,是否有什麼get方法知道呢? 煩請有經驗的告知

B.R.
小K

2樓
1.. 請問當App在Run時是否可以關掉Log的輸出,還是它當手機的開發模式選擇取消就不會有Log的輸出呢,要如何証明呢?

Jarey回覆如下:
關於Log的問題是許多在將Android產品商品化時會擔優的問題,也有許多人問過我同樣的問題,因為你不會想人透過Log看到你的應用程式存在一些bug訊息,或是讓有心人事可以透過這些Log去更加容易理解與破解出你應用程式的架構與寫法。所以一般人會想在最終滙出要上架的APK檔可把不必要的資訊去除掉,當然這必須做一些手腳,在說明這一切之前我必須要讓你先清濋的理解Android Log的機制設計與官方的使用建議。

The order in terms of verbosity, from least to most is ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled into an application except during development. Debug logs are compiled in but stripped at runtime. Error, warning and info logs are always kept.

以上這段話是Google官方網站上所建議的Log使用方法,(需注意這裡指的是建議的做法,並沒有幫你實作,你必須自己照這機制去設計),這段話有三個重點如下:

1.Google建議你將Verbose的訊息在最終發布到Market時不要compiler進APK之中,而只有在開發時期可以被compiler APK裡方便你除錯用,簡單的說你可以將一些比較重要或是機密的除錯訊息寫在Verbose中,而這訊息在最終發布APK時並不會被編譯進去,所以你也就不用害怕說這些訊息會被使用者看見,就算有惡意人士直接用反組譯程式的手法去觀看你的程式碼,也完全看不到這些log訊息,因為根本就沒被編譯進去。

2.在來是Debug 層級的Log,Google官方建議你將這層級的訊息編譯進去APK之中,但在執行時期(runtime),不要將此訊息顯示出來,一般人會看不懂這段話的用意是什麼,但如果你是位真正有在Market上架APP的開發者,你就會理解這樣做是非常重要的。 因為當你發布APP的某先前的版本發生了bug,這時你為了要完整的模擬或是複制發生錯誤的環境,你必須要回頭去舊的版本,但這時你需要有debug訊息,這時你可以透過打開debug mode,來讓你的應用程式可以有debug訊息發送出來。你可以試想如果當初你連把debug的訊息沒compiler進去了,那麼大概除了重新builder軟體,不然就沒有機會在重新叫出debug訊息了。

3.最後Error、Info、Warning 官方建議你要永遠保留與顯示


OK上述是官方建議你如何去利用Log,但是要如何達成這需要你自行去設計實作,在教你如何設計前,我必須在說明Java編譯器的特性,與static final靜態常數的應用。

1.如何讓Log訊息在編譯時期被濾掉,不會被編入到APK檔中:
Java的編譯器會對程式碼做最佳化的處理,對於不可能被執行到的應用程式,會自動在編譯時期就被去除掉,例如以下程式碼: 是永遠不可能被執行的,因此在編譯時期下列的所有Log都會消失
if (false) {
Log.v("LOG TEST:", "Verbose");
Log.d("LOG TEST:", "Debug");
Log.i("LOG TEST:", "Info");
Log.w("LOG TEST:", "Warn");
Log.e("LOG TEST:", "Error");
}

因此你可以直接設計一個靜態的常數來決定那些Log訊息直接不要被Compiler進APK中
/**
 * Log控制類別
 * 
 * @author Jarey
 * @ittraining 艾鍗學院 2011/12/13 www.ittraining.com.tw
 */
public class Android_log_testActivity extends Activity {
/** Called when the activity is first created. */
public static final boolean isCompilerLog = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if(isCompilerLog){
Log.v("LOG TEST:", "Verbose");
Log.d("LOG TEST:", "Debug");
Log.i("LOG TEST:", "Info");
Log.w("LOG TEST:", "Warn");
Log.e("LOG TEST:", "Error");
}
}
}

我們接著將編譯後的APK檔在透過dextojar反組譯回來看:

public class Android_log_testActivity extends Activity
{
  public static final boolean isCompilerLog;

  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903040);
  }
}
從上述程式碼中可以看到,Log訊息全都不見了。



2.如何設計將Log編譯進APK中,但在執行時期不要顯示出來
你可以利用Adapter 設計模式,將Log類別包在一個Adapter使用,那麼你就可以自行定義有那些層級的Log不要輸出,但實際上Log訊息還是會被編譯進APK之中。在此要特別特別提醒的是,不想被編譯進APK中的Log不能透過此Adapter去輸出,因為將會讓Log訊息還是被編進了APK裡:例如你可能會想說這樣寫:

public class Android_log_testActivity extends Activity {
/** Called when the activity is first created. */
public static final boolean isCompilerLog = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

ITLog.v("LOG TEST:", "Verbose");
}
}

public class ITLog {
public static final boolean isCompilerLog = false;
public static void v(String tag, String msg) {
if (isCompilerLog ) {
Log.v(tag, msg);
}
}
}
上述的寫法只會讓ITLog類別中的Log.v(tag,msg);在編譯時期時消失掉,而你呼叫的ITLog.v("LOG TEST:", "Verbose"); 還是會被編譯進APK中,這點必須是要特別注意的。




3.如何完整的實作官方的建議Log做法:
我己依照Google官方的建議做法,撰寫了一個簡單的實作範例提供大家參考:
/**
 * Log控制類別
 * 
 * @author Jarey
 * @ittraining 艾鍗學院 2011/12/13 www.ittraining.com.tw
 */
public class Android_log_testActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
/*Verbose的訊息透過靜態常數決定是否要被編譯進APK*/
if(ITLog.isCompilerLog){
Log.v("LOG TEST:", "Verbose");
}
/*設定只顯示到INFO層級的LOG*/
ITLog.displayLevel=Log.INFO;
/*透過外包的Log類别操作輸出Log*/
ITLog.d("LOG TEST:", "Debug");
ITLog.i("LOG TEST:", "Info");
ITLog.w("LOG TEST:", "Warn");
ITLog.e("LOG TEST:", "Error");
}
}

/**
 * Log控制類別
 * 
 * @author Jarey
 * @ittraining 艾鍗學院 2011/12/13 www.ittraining.com.tw
 */
public class ITLog {
public static final boolean isCompilerLog = false;

/* 要輸出顯示的Debug層級 */
public static int displayLevel = Log.VERBOSE;

public static void d(String tag, String msg) {
if (Log.DEBUG >= displayLevel) {
Log.d(tag, msg);
}
}

public static void i(String tag, String msg) {
if (Log.INFO >= displayLevel) {
Log.i(tag, msg);
}
}

public static void e(String tag, String msg) {
if (Log.ERROR >= displayLevel) {
Log.e(tag, msg);
}
}

public static void v(String tag, String msg) {
if (Log.VERBOSE >= displayLevel) {
Log.v(tag, msg);
}
}

public static void w(String tag, String msg) {
if (Log.WARN >= displayLevel) {
Log.w(tag, msg);
}
}
}


4.如何驗證上述的範例是正確的
我們直接將上述的範例輸出成APK檔後,在透過反組譯將APK轉成Java檔來進行驗證:

public class Android_log_testActivity extends Activity
{
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    setContentView(2130903040);
    ITLog.displayLevel = 4;
    ITLog.d("LOG TEST:", "Debug");
    ITLog.i("LOG TEST:", "Info");
    ITLog.w("LOG TEST:", "Warn");
    ITLog.e("LOG TEST:", "Error");
  }
}

public class ITLog
{
  public static int displayLevel = 2;
  public static final boolean isCompilerLog;

  public static void d(String paramString1, String paramString2)
  {
    if (3 >= displayLevel)
      Log.d(paramString1, paramString2);
  }

  public static void e(String paramString1, String paramString2)
  {
    if (6 >= displayLevel)
      Log.e(paramString1, paramString2);
  }

  public static void i(String paramString1, String paramString2)
  {
    if (4 >= displayLevel)
      Log.i(paramString1, paramString2);
  }

  public static void v(String paramString1, String paramString2)
  {
    if (2 >= displayLevel)
      Log.v(paramString1, paramString2);
  }

  public static void w(String paramString1, String paramString2)
  {
    if (5 >= displayLevel)
      Log.w(paramString1, paramString2);
  }
}


從上述反組譯出的程式可以看的出來,Verbose層級的Log並沒有被編譯進APK中所以程式碼己完全消失了。而雖然我們預設將displayLevel設為2,但在onCreate第一些,我們在執行時期,會將  ITLog.displayLevel = 4; 設定為4,所以可以看的出來只有INFO、WARN、ERROR的訊息會永遠被顯示出來,ERROR的程式碼有被編進APK中,但並不會被顯示出來。而這就是Google官方建議我們Log的應用方式。

以下為LogCat執行此範例時所輸出的訊息
-----------------------------------------------------------------
12-12 17:45:33.908: I/LOG TEST:(360): Info
12-12 17:45:33.937: W/LOG TEST:(360): Warn
12-12 17:45:33.937: E/LOG TEST:(360): Error




3樓
2. 如何知道手機的OS的版本,是否有什麼get方法知道呢? 

Jarey回覆如下:
你可以參考這個API
http://developer.android.com/reference/android/os/Build.VERSION.html

android.os.Build.VERSION.SDK_INT 這個靜態常數會回傳目前手機的OS Version.

要注意的是回傳的版本號碼將會是API的LEVEL編號,此外你還可以利用以下的常數去協助你做判斷用:
http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
android.os.Build.VERSION_CODES

VERSION_CODES裡定議了目前所有Android OS的API LEVEL版本常數以方便你用來做判斷用,例如你可以這樣寫:

if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.ECLAIR_MR1){
/*大於等於LEVEL 7的版本,Android 2.1(含)以上*/
}

最後要在提醒一點,android.os.Build.VERSION.SDK_INT  這個常數是在Android 1.5之後才支援的,也就是如果

你要判斷的手機是小於Android 1.5的,那麼將會找不到這個常數可以呼叫使用。低於1.5的版本必須使用以下常數判斷:

android.os.Build.VERSION.SDK

SDK_INT與SDK的差別只是在型別一個是直接轉好整數給你,一個是給你字串,你要在自己轉成整數。

4樓
Hello Jarey, Thank you for your reply !