Androidでのログの出力について検証

本当にお久しぶりです。Nです。

今回は、Androidでのログの出力について検証してみました。
Androidでは、Android.util.Logに定義されているLogクラスを用いて、
簡単にログを出力することができます。

しかし、出力されたログは誰でも確認(adb logcatコマンドで可能)できるため、
リリース版のアプリでログを出力することは避けるべきだと考えられます。
アプリの動作を出力する程度なら、危険度は低いですが、
ユーザートークンを出力させていたりする場合、
第三者が容易に閲覧できる状態は好ましくありません。
従って、ログはリリース時には表示させないようにするべきです。

では、どのように表示非表示を切り替えるかを考えた場合、
Androidの次のコードは参考になるかもしれません。

android/app/LoaderManager.java

// 前略
static boolean DEBUG = false;
// 中略
if (DEBUG) Log.v(TAG, "  Starting: " + this);
// 後略

ここでは、staticなメンバー変数としてDEBUGを定義し、
DEBUGの真偽によってLog.vの呼び出しを制御しています。

これに習って、次のようなコードを記述しました。
MainActivity.java

package com.example.androidlogtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity {
	private static final String TAG = "MainActivity";

	// デバッグ時にはtrueに設定する。
	// ADTのバージョンが17以降であるなら、
	// BuildConfig.DEBUGの値を使うことも可能である。
	private static final boolean DEBUG = true;

	// 中略

	// デバッグ時だけログを表示する。
	private void logB() {
		int n;

		n = 0;
		for (int i = 0; i < 100; i++) {
			n += i;
		}

		if (DEBUG) {
			Log.v(TAG, String.valueOf(n));
		}
	}
}

ところで、”if (DEBUG)”はどれぐらいの計算コストを消費するでしょうか。
C言語などであれば、”#ifdef”等のプリプロセッサ命令を用いて、
実行時に演算させる必要も無いため、計算コストは考えなくてよいのですが、
Javaにはそのような都合のよい?物はありません…。

でも、もしかしたらstatic finalでDEBUGを宣言しており、
コンパイラによる最適化が働いているかもしれないと思い、
実際に検証を行うことにしました。

検証のために、次のコードも付け足します。
MainActivity.java

	// いつでもログを表示する。
	private void logA() {
		int n;

		n = 0;
		for (int i = 0; i < 100; i++) {
			n += i;
		}

		Log.v(TAG, String.valueOf(n));
	}

	// 全くログを表示しない。
	private void logC() {
		int n;

		n = 0;
		for (int i = 0; i < 100; i++) {
			n += i;
		}
	}

DEBUGの真偽を切り替えて、未署名パッケージを生成します。
ちなみに、次のようにして生成できます。
プロジェクトを右クリック→Android ツール
→未署名アプリケーション・パッケージのエクスポート…
20130517 Export Unsigned APK

今回は、AndroidLogTestDebug.apkとAndroidLogTestRelease.apkで出力しました。
名前からわかるとおり、AndroidLogTestDebug.apkがDEBUG = trueで定義、
AndroidLogTestRelease.apkがDEBUG = falseで定義されています。

次に、今コンパイルしたapkファイルをデコンパイルして、
ソースを確かめることで最適化の程を確認します。
まず、拡張子apkをzipに変更し、中のclasses.dexを解凍します。
そして、dex2jarを用いてdexファイルをjarファイルに変換します。
最後に、jd-gui.exeでソースを確認し、検証を行います。
dex2jarとjd-gui.exeはGoogleで検索すればすぐに見つかります。
どちらも無料です。

結果、次のようになりました。
MainActivity.java (DEBUG = true版)

package com.example.androidlogtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity
{
  private static final boolean DEBUG = true;
  private static final String TAG = "MainActivity";

  private void logA()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
      {
        Log.v("MainActivity", String.valueOf(i));
        return;
      }
      i += j;
    }
  }

  private void logB()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
      {
        Log.v("MainActivity", String.valueOf(i));
        return;
      }
      i += j;
    }
  }

  private void logC()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
        return;
      i += j;
    }
  }

  // 後略
}

MainActivity.java (DEBUG = false版)

package com.example.androidlogtest;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends Activity
{
  private static final boolean DEBUG = false;
  private static final String TAG = "MainActivity";

  private void logA()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
      {
        Log.v("MainActivity", String.valueOf(i));
        return;
      }
      i += j;
    }
  }

  private void logB()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
        return;
      i += j;
    }
  }

  private void logC()
  {
    int i = 0;
    for (int j = 0; ; j++)
    {
      if (j >= 100)
        return;
      i += j;
    }
  }

  // 後略
}

ここで、logBメソッドがどう変化したかに着目してください。
DEBUG = trueの場合、logBメソッドはlogAメソッドと同一の物になりました。
一方で、DEBUG = falseの場合、logBメソッドはlogCメソッドと同一の物になりました。
logBメソッドでは、if文が消滅し、DEBUG = trueの時だけLog.vが実行されています。

ここからわかることは、ログを出力したい場合は、
“if (DEBUG)”でデバッグ時に限定して出力させるべきだということです。
(DEBUGはstatic final等で定数に宣言する必要があると思われます。)
こうすることで、セキュリティ的にも速度的にも改善を図れます。

DEBUGを定義するのが面倒だという方は、
BuildConfig.DEBUGを利用されるのも一つの手だと思います。
この定数はADTのv17から用意されているらしく、
署名パッケージを生成した場合だけ、自動的にDEBUG = falseになるようです。

今回実験に用いたプロジェクトを次に公開しておきますので、
ご自由にご利用ください。
AndroidLogTest.zip

USP Browserの開発はなかなか目立った進展がみられない、今日この頃です。
最近ではバグつぶしに伴うバージョンアップがほとんどです。
そろそろ、ベータ版の冠を外そうかと考えています。
それと同時に、オープンソース化しようかとも考えています。
ライセンスは、Apache License Version 2.0とかで…。

別にはなりますが、近々、
アイコンと利用規約の変更を伴うバージョンアップを計画しています。
更新時には、是非ダウンロードしていただければ幸いです。

  1. コメント 0

  1. トラックバック 0

return top