20211126のJavaに関する記事は5件です。

Javaのマルチスレッド実装例(前編)

この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の1日目の記事です。 こんにちは、シアトルコンサルティングの長岡と申します。 この度、弊社シアトルコンサルティング株式会社でAdvent Calendarに参加することになりました。 TeamTech Move the WorldをMissionに掲げ、日々全力で業務に取り組んでおります! 少しでも興味を持って頂けたら下記のサイトを覗いてみてください! コーポレートサイト https://www.seattleconsulting.co.jp/ Wantedly https://www.wantedly.com/companies/seattleconsulting よろしくお願い致します! はじめに 今回はJavaにおけるマルチスレッドの実装例を紹介します。 実務でシングルスレッドからマルチスレッドへの改修案件に携わったこともあり、振り返りついでに紹介します。 前編は基本的な実装例、後編では実装の注意点について実装例を交えて紹介します。 エンジニアになって初めて担当させて頂いた案件で、駆け出しエンジニアの方たちにも理解できる内容だと思うので、ぜひ読んでみてください! マルチスレッドとは Javaが採用している並行処理プログラミングのモデルが「マルチスレッド」と呼ばれています。 本来プログラムは上から下に順に処理していきますが、マルチスレッドを実装すると本来順番に呼ばれるプログラムを同時に動作させることができます。 マルチスレッドのメリット マルチスレッドを使用することにより、直列で処理していたプログラムを並列に処理していくため直列処理の待ち時間が無くなり、アプリケーション全体で見た実行速度が速くなります。 実装の注意点 各スレッドで同じオブジェクトにアクセスした際に、同じフィールドの値を変更してしまう為、期待した値にならない可能性があり注意が必要です。 実装例 マルチスレッドの実装には、RunnableかCallableを使用します。 Runnableの場合は戻り値の指定ができず、例外をスローできません。 Callableの場合は任意の戻り値を指定でき、例外をスローすることができます。 実務で使用したCallableを使用し、メインクラスから3つのサブクラスを並行して呼び出します。 サブクラスは、サブクラス名+開始 を表示した後、3秒間スリープし、サブクラス名+終了を表示させるプログラムにします。 Main.java package main; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) throws InterruptedException { // 引数に生成するスレッド数を渡す ExecutorService exec = Executors.newFixedThreadPool(3); Callable<String> sub1 = new Sub1(); Callable<String> sub2 = new Sub2(); Callable<String> sub3 = new Sub3(); List<Callable<String>> tasklist = new ArrayList<>(); tasklist.add(sub1); tasklist.add(sub2); tasklist.add(sub3); List<Future<String>> future = exec.invokeAll(tasklist); } } サブクラスの3つは処理内容は同じで出力するサブクラス名が違うだけなので他二つは割愛します Sub1.java package main; import java.util.concurrent.Callable; public class Sub1 implements Callable<String>{ @Override public String call() throws Exception { System.out.println("sub1開始"); Thread.sleep(3000); System.out.println("sub1終了"); return null; } } 実行してみます。 sub1開始 sub3開始 sub2開始 sub3終了 sub2終了 sub1終了 成功です。 マルチスレッドで実行している為、実行する度に開始と終了の順番がバラバラになります。 さいごに 今回はプログラムをマルチスレッドで動かしてみました。 次回は実装する上での注意点、排他制御について書いていこうと思います。 Javaのマルチスレッド実装例(後編) 参考 ・マルチスレッドとは ・マルチスレッドの利点 ・Callableについて
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaからRustを呼び出す

概要 JNAを使ってRustを呼び出す方法をまとめました。 前準備 まず、JNAのライブラリをmavenなりbuild.sbtなりに追加します。 build.sbt scalaVersion := "2.13.6" libraryDependencies += "net.java.dev.jna" % "jna" % "5.9.0" libraryDependencies += "net.java.dev.jna" % "jna-platform" % "5.9.0" JavaからRustを呼び出す Hello World的なプログラム Rustで共有ライブラリを作る 以下のようなプログラムを作成します。 main.rs // #[no_mangle]を指定すると、コンパイル時にメソッド名を変更されないようすることができます。 #[no_mangle] fn main() { println!("Hello, world!"); } そして、(macの場合)以下のコマンドで共有ライブラリを作成します。 rustc main.rs --crate-type=dylib ※ 本当はcargoでビルドした方が実践的でよさそうですね Java側の実装 JnaHoge.java import com.sun.jna.Library; import com.sun.jna.Native; public class JnaHoge { public interface HogeInterface extends Library { HogeInterface INSTANCE = Native.load("main", HogeInterface.class); void main(); } public static void main(String[] args) { System.out.println("started"); HogeInterface.INSTANCE.main(); System.out.println("finished"); } } ディレクトリツリー 以下はmain.rs, libmain.dylib, JnaHoge.javaのディレクトリ配置です。 . ├── build.sbt ├── libmain.dylib ├── main.rs ├── project │   ├── build.properties | ├── src │   ├── main │   │   ├── java │   │   │   └── JnaHoge.java │   │   ├── resources │   │   └── scala │   └── test │   └── scala 実行結果 sbtの場合、sbt "runMain JnaHoge"で実行できます。 実行結果: started Hello, world! finished Rustで共有ライブラリを作って、Javaから呼び出すサンプルコード Rustのstd::os::rawモジュールで定義されている型は、Cの特定の型と同じ表現になることが保証されています。 従って、Rustで他言語から呼び出されるような関数を作成する際は、引数と戻り値でstd::os::rawの型を使用することになりそうです。 int型を渡してint型の値を得る main.rs use std::os::raw::c_int; #[no_mangle] fn plus_one(x: c_int) -> c_int { x + 1 } JnaHoge.java import com.sun.jna.Library; import com.sun.jna.Native; public class JnaHoge { public interface HogeInterface extends Library { HogeInterface INSTANCE = Native.load("main", HogeInterface.class); int plus_one(int x); } public static void main(String[] args) { System.out.println("started"); int x = HogeInterface.INSTANCE.plus_one(1); System.out.println(x); System.out.println("finished"); } } 実行結果: started 2 finished String型を渡してString型の値を得る main.rs use std::os::raw::c_char; use std::ffi::CString; #[no_mangle] unsafe fn append_hoge(cs: *mut c_char) -> *mut c_char { let mut cs_string = CString::from_raw(cs).into_string().expect("Failed to create String"); cs_string.push_str("hoge"); CString::new(cs_string).expect("Failed to create CString").into_raw() } JnaHoge.java import com.sun.jna.Library; import com.sun.jna.Native; public class JnaHoge { public interface HogeInterface extends Library { HogeInterface INSTANCE = Native.load("main", HogeInterface.class); String append_hoge(String str); } public static void main(String[] args) { System.out.println("started"); String str = HogeInterface.INSTANCE.append_hoge("ほげ_"); System.out.println(str); System.out.println("finished"); } } 実行結果: started ほげ_hoge finished 配列を渡して配列を得る main.rs use std::os::raw::c_int; #[no_mangle] unsafe fn add_one(array: *mut c_int, new_array: *mut c_int) { let new_slice = std::slice::from_raw_parts_mut(new_array, 3); let slice = std::slice::from_raw_parts(array, 3); new_slice[0] = slice[0] * 10; new_slice[1] = slice[1] * 10; new_slice[2] = slice[2] * 10; } JnaHoge.java import com.sun.jna.Library; import com.sun.jna.Native; public class JnaHoge { public interface HogeInterface extends Library { HogeInterface INSTANCE = Native.load("main", HogeInterface.class); void add_one(int[] array, int[] newArray); } public static void main(String[] args) { System.out.println("started"); int[] array = {4, 5, 6}; int[] newArray = new int[3]; HogeInterface.INSTANCE.add_one(array, newArray); System.out.println(newArray[0]); System.out.println(newArray[1]); System.out.println(newArray[2]); System.out.println("finished"); } } 実行結果: started 40 50 60 finished 構造体を渡してJavaのクラスを得る main.rs use std::os::raw::c_int; use std::os::raw::c_char; #[repr(C)] // C言語の構造体と互換性を持たせるようにするアトリビュート struct Person { age: c_int, name: *mut c_char } #[no_mangle] unsafe fn set_age(person: *mut Person) -> *mut Person { (*person).age = 25; person } JnaHoge.java import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Structure; public class JnaHoge { public interface HogeInterface extends Library { HogeInterface INSTANCE = Native.load("main", HogeInterface.class); @Structure.FieldOrder({"age", "name"}) class Person extends Structure { public int age; public String name; void setName(String str) { name = str; } } Person set_age(Person person); } public static void main(String[] args) { System.out.println("started"); HogeInterface.Person person = new HogeInterface.Person(); person.setName("パターソン"); HogeInterface.Person newPerson = HogeInterface.INSTANCE.set_age(person); System.out.println(person.name + "(" + person.age + ")"); System.out.println(newPerson.name + "(" + newPerson.age + ")"); System.out.println("finished"); } } 実行結果: started パターソン(25) パターソン(25) finished
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java の乱数発生器

これは何? Javaの乱数生成器について速度面から調査してみる という記事を見て、Java の乱数にも色々あるんだなと思い。 速度面以外を軽く調べた。 調べた java.util.Random Knuth 先生の有名なアルゴリズムを使っている。 有名だけど、メルセンヌ・ツイスタなんかと比べるとバラケ具合がしょぼいこともよく知られている。 java.util.concurrent.ThreadLocalRandom 内部状態は java long rnd; の一個、64bit だけ。定数が java long multiplier = 0x5DEECE66DL; long addend = 0xBL; long mask = (1L << 48) - 1; と用意されていて計算は java int next(int bits) { rnd = (rnd * multiplier + addend) & mask; return (int) (rnd >>> (48-bits)); } これだけ。bits はほしいビット数なので気分としては 32固定だと思っていいと思う。 実用的には困らない場面も多いとは思うけど、バラケ具合はあまり良くないと思う。 L??X??MixRandom L32X64MixRandom のソースコードらしきもの を見ると LXM ファミリの一員らしい。 M は、0xadb4a92d という値。 a は、コンストラクタで決まる奇数で。 xoroshiro64 で作られた乱数 x0 と、と $s=Ms+a$ で作られる系列 s の値を足したものを java public static int mixLea32(int z) { z = (z ^ (z >>> 16)) * 0xd36d884b; z = (z ^ (z >>> 16)) * 0xd36d884b; return z ^ (z >>> 16); } で混ぜたものらしい。 単なる xoroshiro64 だと弱いので別の乱数を混ぜた感じかな。 Xoroshiro128PlusPlus 中身は見てないけど、Xoroshiro128++ なんだろう。 Xoroshiro シリーズは https://prng.di.unimi.it/ を読むとなんだか分かる。 Xoshiro256PlusPlus 中身は見てないけど、Xoshiro256++ なんだろう。 Xoroshiro シリーズと同様、 https://prng.di.unimi.it/ を読むとなんだか分かる。 感想 メルセンヌ・ツイスタが無いのがちょっと意外。 最初に上げた記事では 個人的には、暗号やセッションキーの生成等ではSecureRandom。それ以外は通常ThreadLocalRandom。 となっているけど、私なら 予測不能性が必要ない場面なら基本 Xoroshiro128PlusPlus または Xoshiro256PlusPlus。どっちか速い方。どっちが速いか知らない今調べずに使うなら Xoshiro256PlusPlus かな。 $2^{256}$ より長い周期が必要になったら LXM ファミリから選ぶ かな。 とはいえ。 Java を使う機会自体がないのであった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Couchbase Lite機能紹介:クエリのトラブルシューティング

はじめに ここでは、Couchbase Liteデータベースの検索のために利用するクエリについて、トラブルシューティングなどの目的のために調査を行う方法について説明します。 Couchbase Liteは、内部的にSQLiteを利用しており、ここでの解説は、SQLiteのEXPLAIN QUERY PLANコマンドに関係してきます。 SQLiteのEXPLAIN QUERY PLANの詳細については、以下を参照してください。 https://www.sqlite.org/eqp.html 調査方法には、APIを利用する方法と、cbliteコマンドを利用する方法があります。これらのいずれの方法を用いた場合でも、出力内容は同じフォーマットとなります。 出力内容は、クエリのパフォーマンスの問題を診断したり、クエリを最適化したりするときに役立つ洞察を提供します。 使用法 cbliteでの利用例 ターミナル上で、データベースを指定して、インタラクティブモードで、cbliteコマンドを実行します。 cblite <your-database-name>.cblite2 --explainオプションを指定します。 次の例では、selectサブコマンドを使用してN1QLクエリとして入力しています。 (cblite) select --explain domains group by country order by country, name また、クエリはqueryサブコマンドをを使用してJSON文字列として入力することもできます。 (cblite) query --explain {"GROUP_BY":[[".country"]],"ORDER_BY":[[".country"],[".name"]],"WHAT":[[".domains"]]} APIでの利用 ここではKotlinでのコード例を示します(後にJavaのサンプルも提示します)。 CBLQuery *query = [CBLQueryBuilder select:@[[CBLQuerySelectResult all]] from:[CBLQueryDataSource database:database] where:[[CBLQueryExpression property:@"type"] equalTo:[CBLQueryExpression string:@"university"]] groupBy:@[[CBLQueryExpression property:@"country"]] orderBy:@[[[CBLQueryOrdering property:@"title"] descending]] ]; ① NSLog(@"%@", [query explain:&error]); ② ①通常どおりクエリを作成します ②クエリのexplainメソッドを呼び出します。ここでは、結果を、ログとして出力しています。 出力 APIと、cbliteコマンドとで、出力内容は同じフォーマットとになります。 SELECT fl_result(fl_value(_doc.body, 'domains')) FROM kv_default AS _doc WHERE (_doc.flags & 1 = 0) GROUP BY fl_value(_doc.body, 'country') ORDER BY fl_value(_doc.body, 'country'), fl_value(_doc.body, 'name') ① 7|0|0| SCAN TABLE kv_default AS _doc ② 12|0|0| USE TEMP B-TREE FOR GROUP BY 52|0|0| USE TEMP B-TREE FOR ORDER BY {"GROUP_BY":[[".country"]],"ORDER_BY":[[".country"],[".name"]],"WHAT":[[".domains"]]} ③ この出力は、次の3つの主要な要素で構成されています。 ① SQLクエリに翻訳された文字列(ただし、必ずしも何らかの実行可能なコードに対応しているわけではなく、診断用の情報)。 ② クエリプランは、SQLiteのSQLクエリを実装する方法の高レベルのビューを提供します。潜在的な問題を特定し、問題のあるクエリの最適化に役立てることができます。 ③ JSON文字列形式のクエリ。cbliteツールで直接実行することができます。 クエリプランセクション 出力中のクエリプランセクションには、クエリの実行プランが表形式で表示されます。 データがどのように取得されるか、また必要に応じて、どのように加工されるかを示しています。 7|0|0| SCAN TABLE kv_default AS _doc ① 12|0|0| USE TEMP B-TREE FOR GROUP BY ② 52|0|0| USE TEMP B-TREE FOR ORDER BY ③ ①検索方法(Retrieval method): この行は、クエリに使用されている検索方法を示しています。ここでは、データベースの順次読み取り(SCAN)です。最適化を検討できる可能性があります。詳細については、以下を参照してください。 ②グループ化方法(Grouping method): この行は、クエリでGroup By句が使用されており、データを並べ替える必要があり、bツリーが一時ストレージとして使用されることを示しています。 ③順序付け方法(Ordering method): この行は、クエリでOrder By句が使用されており、データを並べ替える必要があり、bツリーが一時ストレージとして使用されることを示しています。 検索方法(Retrieval method) クエリオプティマイザは、要求されたデータアイテムを可能な限り効率的に取得しようとします。これは通常、1つ以上の使用可能なインデックスを使用することによって行われます。 取得方法(Retrieval Method)毎の説明を以下に記します。 Search クエリはキーを使用して必要なデータに直接アクセスできます。Searchモードを使用したクエリが最速です。 Scan Index クエリはインデックスの全部または一部をスキャンすることでデータを取得できます(たとえば、範囲内の値を一致させようとする場合)。このタイプのクエリはSearchよりも低速ですが、少なくとも、コンパクトで順序付けられた形式のインデックスの恩恵を受けます。 Scan Table クエリはデータベーステーブルをスキャンして、必要なデータを取得する必要があります。これはこれらの方法の中で最も遅く、最適化を検討することが考えられます。 ベストプラクティス インデックスを作成すると、クエリのパフォーマンスを高速化できます。 以下、インデックスに対して適切なアプローチをとることの重要性について説明します。 ワイルドカードの扱い LIKE検索では、(利用できるインデックスがあり、かつ)次の条件が満たされている場合のみインデックスを使用できます。 検索文字列がワイルドカードで始まらない 検索文字列は実行時に既知の定数である(クエリの処理中に導出された値ではない) 以下のクエリ例では、ワイルドカードを接頭辞と接尾辞に使用しています。 Query query = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(database)) .where(Expression.property("type").like(Expression.string("%hotel%"))); Log.i(query.explain()); インデックス付きプロパティtypeに対する条件句は、ワイルドカードプレフィックスが接頭辞として使われているため、そのインデックスを使用できません。 結果のクエリプランでは、取得方法として「Scan Table」に決定していることがわかります。 2|0|0| SCAN TABLE kv_default AS _doc 接頭辞として使われているワイルドカードプレフィックス%を削除すると、クエリプランの取得方法が変更されてインデックス検索になることがわかります。このような単純な変更により、クエリのパフォーマンスに大きな違いが生じる可能性があります。 Query query = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(database)) .where(Expression.property("type").like(Expression.string("hotel%")) .and(Expression.property("name").like(Expression.string("%royal%")))); Log.i(query.explain()); 結果のクエリプランから、クエリオプティマイザがtypeIndexにアクセスできるようになり、検索がより効率的になっていることがわかります。 3|0|0| SEARCH TABLE kv_default AS _doc USING INDEX typeIndex (<expr>>? AND <expr><?) 関数を適切に利用する 関数はクエリを作成するのに非常に便利なツールですが、クエリオプティマイザーがインデックスを使用できるかどうかに影響を与える可能性があることに注意する必要があります。 たとえば、インデックスが設定されているプロパティに対して、lower()関数を使用した例について見てみます。 Query query = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(database)) .where(Function.lower(Expression.property("type")).equalTo(Expression.string("hotel"))); Log.i(query.explain()); ここでは、Where式で、プロパティtypeにlower()関数を使用しています クエリプランは、以下の通りです。 2|0|0| SCAN TABLE kv_default AS _doc lower()関数を削除すると、状況が変わります。 Query query = QueryBuilder .select(SelectResult.all()) .from(DataSource.database(database)) .where(Expression.property("type").equalTo(Expression.string("hotel"))); Log.i(query.explain()); 以下のようにクエリプランが変化します。 3|0|0| SEARCH TABLE kv_default AS _doc USING INDEX typeIndex (<expr>=?) このことを踏まえると、インデックスを作成する方法を検討できます。たとえば、クエリ実行時ではなく、インデックスを作成するときにlower()関数を使用することが考えられます。 インデックス更新 クエリの2回目の実行、データベースを再度開いた後、またはインデックスを削除して再作成した後など、それ以前と比べてクエリの実行速度が速くなる場合があります。これは、SQLクエリオプティマイザーがクエリを最適化するため、以前よりも効果的な統計情報を収集したことを意味します。 統計は、次のような特定のイベントの後に収集されます。 インデックス作成後 データベースクローズ後 データベース圧縮後 クエリの調査を行う際に、データベースの圧縮を検討することが考えられるでしょう。 関連情報
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Timber×Crashlytics でログをクラッシュレポート送信(Timber編)

はじめに リリースしたモバイルアプリで、未検出のバグによりクラッシュしてしまう場合、ユーザーが声を上げない限り検知できない・・ということは避けたいですよね。 また、万が一そのような不具合があった場合、再現手順やユーザーの状態を調査・分析するのは骨が折れます。 更には、リリースしたモバイルアプリの動作ログを自前のプラットフォームに集約するような仕組みも敷居が高いです。 それらを踏まえて、 クラッシュするまでの詳細なログをクラッシュレポートで確認したい ログはいい感じに出力したい(if (Logger.isDebugEnabled()) { ... }みたいな分岐はイヤ) 手軽に導入したい という思いのもと、Timber と Firebase Crashlytics での実装例を紹介したいと思います。 Timber Timber は、Android向けのログ出力ライブラリです。 ログと言えば、Androidの標準ロガーとして、android.util.Logを使用することが多いと思います。 Androidの標準ロガー しかし、下記の記事で触れられていますが、標準のロガーを使用していると以下の問題が出てきます。 クラスごとにログ出力時のタグ用の変数を用意する必要がある private final String TAG = MainActivity.class.getName(); と冒頭に宣言 リリースビルドしたアプリの logcat にデバッグログが出力されてしまう ProGuard 等で削除していれば問題はないが、機密情報を出力していたとしたら大変・・・ デバッグビルドなのに Crashlytics へのクラッシュレポートにログが出力されてしまう ログごとに “リリースビルドなら“ みたいな分岐はしたくない・・・ Timber を使うメリット 各問題に対して、Timber を使用することでのメリットを挙げます。 ・クラスごとにログ出力時のタグ用の変数を用意する必要がある 呼び出し元クラス名が自動でタグに設定されるため、変数の用意や引数への指定が不要になります。 // 導入前 private final String TAG = MainActivity.class.getName(); ... Log.d(TAG, String.format("昨日のご飯は%s鍋!", "トマト")); // 出力例 // D/com.example.timbercrashlyticstest.MainActivity: 昨日のご飯はトマト鍋! // 導入後 Timber.d("昨日のご飯は%s鍋!", "トマト"); // 出力例 // D/MainActivity: 昨日のご飯はトマト鍋! ・リリースビルドしたアプリの logcat にデバッグログが出力されてしまう ・デバッグビルドなのに Crashlytics へのクラッシュレポートに出力されてしまう リリースビルド/デバッグビルドによって、ログ出力を切り替えできるため、容易に制御が可能になります。 ※ 後述します 導入方法 build.gradle(:app) TimberのGitHub を参考にモジュールを追加します。 builde.grade(app) dependencies { + implementation 'com.jakewharton.timber:timber:4.7.1' } ※ 執筆時点(2021/11/18)での最新は 5.0.1 ですが、筆者の環境の都合上バージョンを落としています MyApplication Tree クラスを plant することで、アプリケーション全体で Timber クラスを使用できます。 MyApplication.java import android.app.Application; + import timber.log.Timber; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); + Timber.plant(new Timber.DebugTree()); } } AndroidManifest.xmla manifest/application/android:name に、アプリケーションクラスを指定 AndroidManifest.xml <application ... + android:name=".MyApplication" ...> MainActivity MainActivity.java public class MainActivity extends AppCompatActivity { private final String TAG = MainActivity.class.getName() + "_TAG"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Log.v(TAG, "Log VERBOSE"); Log.d(TAG, "Log DEBUG"); Log.i(TAG, "Log INFO"); Log.w(TAG, "Log WARN"); Log.e(TAG, "Log ERROR"); Log.println(Log.ASSERT, TAG, "Log ASSERT"); Timber.v("Timber VERBOSE"); Timber.d("Timber DEBUG"); Timber.i("Timber INFO"); Timber.w("Timber WARN"); Timber.e("Timber ERROR"); Timber.wtf("Timber ASSERT"); } } 出力例 Timber の方はパッケージ名が出力されないのですっきりしています。 パッケージ名が必要であれば、お好みで TAG 変数をタグとして設定してください。 ※ Crashlytics を使用する場合、パッケージ名が冗長となり得るなら除く方針をお勧めします Logcat Runウィンドウ ビルドによる切り替え 下記のように、ビルドによって plant するクラスを切り替えることで、logcat に出力するログを優先度ごとに制御することが可能になります。 つまり、MainActivity では、ビルドモードを意識して分岐するといった処理が必要なくなります。 ※ Tree.DebugTree は全優先度のログを出力します MyApplication BuildConfig.DEBUG でビルド別に分岐します。 デバッグビルドの場合は DebugTree、リリースビルドの場合は ReleaseTree を plant します。 MyApplication.java public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); + if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); + } else { + Timber.plant(new ReleaseTree()); + } } } ReleaseTree Timber.Tree を継承したリリースビルド用の Tree クラスを作成します。 下記は実装の一例です。 ReleaseTree.java public class ReleaseTree extends Timber.Tree { @Override protected void log(int priority, @Nullable String tag, @NotNull String message, @Nullable Throwable t) { switch (priority) { case Log.INFO: case Log.WARN: case Log.ERROR: case Log.ASSERT: // Crashlytics ログ出力処理を記述(Crashlytics編で記載) break; default: /* 下記の優先度は出力なし ・Log.VERBOSE ・Log.DEBUG */ break; } } } おまけ Lint Timber を追加すると Lint も設定されるため、既に Log クラスで実装してしまった場合でも、Log クラスで実装しようとしたときも、気付けるようになっていて便利です。 関数名の出力 Timber.Tree ではなく、Timber.DebugTree を継承し、createStackElementTag をオーバーライドすることで、呼び出し元の関数名を出力するようにカスタマイズが可能です。 OmakeDebugTree.java public class OmakeDebugTree extends Timber.DebugTree { @Override protected @Nullable String createStackElementTag(@NotNull StackTraceElement element) { // ClassName#MethodName() return String.format( "%s#%s", super.createStackElementTag(element), element.getMethodName() ); } } ※ OmakeDebugTree を plant してください 出力例(Runウィンドウ) #のあとに、関数名が出力されました。 おわりに これで、デバッグビルドの際はコンソール上にログを出力し、リリースビルドの際はクラッシュレポートに必要なログのみを出力する基盤ができました。 クラッシュレポートを送信するサービスは様々なものがありますので、応用する際に参考になれば幸いです! 次回は Firebase Crashlytics を ReleaseTree に組み込んでいきたいと思います。 参考 Androidのログ出力ライブラリ”Timber” ProGuardでデバッグログを"完全"に削除することの難しさ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む