GlideのDiskLruCacheの実装を読む
この記事では、Glideで使用されている DiskLruCache
のキャッシュ情報を保存する処理の実装と仕様についてみていきます。
glide/DiskLruCache.java at v4.11.0 · bumptech/glide
DiskLruCacheの概要
DiskLruCache
は、LRUでファイルのキャッシュを行うクラスです。キャッシュ情報の管理には、ジャーナルファイルとメモリ上のデータ構造を使用しています。
ジャーナルファイルについては後で述べますが、キャッシュの読み込みや書き込みを行った履歴を記録したファイルとなっています。
キャッシュの操作を行った時には、はじめにメモリ上の LinkedHashMap
に保存された後、ジャーナルファイルへの保存が行われます。
DiskLruCache
のインスタンスは DiskLruCache.open
を使用して取得することができ、 open
をした時にジャーナルファイルからメモリへキャッシュ情報が読み込まれます。
ジャーナルファイルの中身
ジャーナルファイルの中身は以下のような形式になっています。
libcore.io.DiskLruCache 1 100 2 CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 DIRTY 335c4c6028171cfddfbaae1a9c313c52 CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 REMOVE 335c4c6028171cfddfbaae1a9c313c52 DIRTY 1ab96a171faeeee38496d8b330771a7a CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 READ 335c4c6028171cfddfbaae1a9c313c52 READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
はじめの4行はそれぞれ、
libcore.io.DiskLruCache : 固定の文字列 1 : ディスクキャッシュのバージョン 100 : アプリが指定するバージョン(Glideでは固定で1) 2 : ValueCount 1つのkeyに紐づく値の個数(Glideでは固定で1)
を表しています。ディスクキャッシュやアプリが指定するバージョンが変わると、一度キャッシュがすべてクリアされます。
5行目以降がキャッシュ情報の取得や更新のログとなっています。アクションと付加情報がスペース区切りで1行ごとに記されていて、アクションは表に示す4種類があります。
アクション | 付加情報1 | 付加情報2~ |
---|---|---|
READ | キャッシュキー | - |
REMOVE | キャッシュキー | - |
DIRTY | キャッシュキー | - |
CLEAN | キャッシュキー | キャッシュしている各ファイルのサイズ ValueCountの数 |
各アクションの意味
それぞれのアクションについて説明していきたいと思います。
READ
/REMOVE
は想像がつきやすいですが、キャッシュ情報の取得/削除を行った時のアクションです。
個人的には、データベースなどに関する知識があまりないため、DIRTY
や CLEAN
が表す意味を理解するのが少し難しかったです。
DiskLruCache
では、キャッシュ情報を書き換える際に DiskLruCache#edit
などを使用して取得できる Editor
クラスを介する必要があります。アプリ側では、取得した Editor
に対してキャッシュ情報の追加や上書きを行った後に Editor#commit
を呼び出すことで、実際に画像ファイルキャッシュの更新が行われます。
この時に Editor
を取得してから commit
を行うまでの状態が DIRTY
となります。( DIRTY
のログが書き込まれるのはEditor
作成時)
DIRTY
となっている情報を commit
し終わると CLEAN
のログが書き込まれます。
キャッシュのmax sizeを超えた時の処理
何度も画像のキャッシュを行っていくと、当然ですがキャッシュサイズが増えていきmax sizeを越えることがあります。そのサイズを超えた時は、以下の処理が行われてキャッシュサイズがmax size以下に抑えられます。
ここでいう 無駄なログ
には何パターンかありますが、例えば
キーA
の情報が編集A(DIRTY)
->更新A(CLEAN)
->取得(READ)
-> .. ->更新X(CLEAN)
と操作された時の最後のDIRTY
またはCLEAN
以外のログキーA
の最終的な情報がキャッシュに保存されていれば良いので、それまでの更新履歴などは不要
- キャッシュ情報を取得した時のログ(READ)
その他の処理
キャッシュ情報の追加や削除を行う時には上記のメモリキャッシュの更新とジャーナルファイルへのログの記録が主な処理となっています。
しかし、エラーが起きる可能性もあり、その場合は基本的にキャッシュ情報の削除が行われます(キャッシュは消しても問題はないはずなので安全な方にたおしている)
また、 DIRTY
から CLEAN
へ状態がうつる場合の処理についても少し触れておきます。
DIRTY
と CLEAN
はそれぞれ別々のファイルとして画像を保存するようになっていて、commit
時に DIRTY
用のファイルから CLEAN
用のファイルへリネームが行われます。
glide/DiskLruCache.java at v4.11.0 · bumptech/glide
キャッシュファイルの名前の規則
DIRTY
用ファイルの名前: ${key}.${index}.tmp
CLEAN
用ファイルの名前: ${key}.${index}
glide/DiskLruCache.java at v4.11.0 · bumptech/glide
まとめ
DiskLruCache
ではただ画像ファイルを保存しているだけではなく、キャッシュの操作ログも記録をしておくことでLRUとして動作することを可能にしていた。
ジャーナルファイルについてはデータベースの処理とも関連がありそうなので今後さらに調べていきたいです。
Androidのxmlで画像がうまく読み込めずにハマった時のメモ
xmlで bitmap
タグを使って画像を読み込む際に以下のようなエラーが起きた場合と、9-patch画像がただの画像になってハマった時のメモ
Caused by: org.xmlpull.v1.XmlPullParserException: Binary XML file line #7: <bitmap> requires a valid 'src' attribute at android.graphics.drawable.BitmapDrawable.updateStateFromTypedArray(BitmapDrawable.java:851)
エラーが出た時の状況
my_drawable.xml
ファイルのルートに以下のような画像を設定していました。
<?xml version="1.0" encoding="utf-8"?> <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@drawable/ic_launcher_round" />
ic_launcher_round
は以下の階層にあるpng画像とベクター画像でした。
mipmap/ ic_launcher_round.png mipmap-anydpi-v26/ ic_launcher_round.xml
エラーの原因と解決方法
bitmap
タグで設定した画像は BitmapDrawable
クラスとして表現され、jpegやpngなどの画像しか対応されておらず、今回の場合にAPI26以上ではベクター画像が使われるため、無効な画像として扱われていました。
解決方法の1つとして、API26以上の場合にもpng画像を使用する方法があります。他にも画像を使う側でAPIレベルによって処理を分けるという方法もあります。(使う画像を変えて対応するか、使う側の処理を分けて対応するか)
9-patch画像がただの画像として表示される問題
こちらも先ほどと原因は同じで、9-patch画像は NinePatchDrawable
に変換される必要があり、 BitmapDrawable
ではただのpng画像と同じ扱いになります。
解決方法として、 bitmap
ではなく nine-patch
タグを使用する方法があります。
参考
bitmap
タグなど、xmlをパースする時の処理
DrawableInflater.java - Source
Drawable
リソースのドキュメント
ドローアブル リソース | Android デベロッパー | Android Developers
Drawable
のリファレンス
Drawable | Android デベロッパー | Android Developers
Glideのディスクキャッシュ処理のメモ
ディスクキャッシュのインスタンスの生成
ディスクキャッシュの処理を行うインスタンスは、 Glide.Builder
で設定した DiskCache.Factory
から作成されます。もしBuilderで設定しない場合は InternalCacheDiskCacheFactory
が使われます。
if (diskCacheFactory == null) { diskCacheFactory = new InternalCacheDiskCacheFactory(context); }
InternalCacheDiskCacheFactory
InternalCacheDiskCacheFactory
は DiskLruCacheFactory
を継承しているクラスとなっていて、
名前からLRUキャッシュが関係していそうなことが分かります。
InternalCacheDiskCacheFactory
のコンストラクタは以下のようになっています。
キャッシュサイズは DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE = 250MB
、保存場所はアプリのキャッシュディレクトリ内が指定されているようです。
public InternalCacheDiskCacheFactory(Context context) { this( context, DiskCache.Factory.DEFAULT_DISK_CACHE_DIR, DiskCache.Factory.DEFAULT_DISK_CACHE_SIZE); } public InternalCacheDiskCacheFactory( final Context context, final String diskCacheName, long diskCacheSize ) { super( new CacheDirectoryGetter() { @Override public File getCacheDirectory() { File cacheDirectory = context.getCacheDir(); if (cacheDirectory == null) { return null; } if (diskCacheName != null) { return new File(cacheDirectory, diskCacheName); } return cacheDirectory; } }, diskCacheSize); }
DiskLruCacheFactoryが生成するクラス
InternalCacheDiskCacheFactory
が継承している DiskLruCacheFactory
とファクトリが生成するクラスについてみていきます。
DiskLruCacheFactory
は DiskCache
のクラスを生成するための DiskCache.Factory
インタフェースを実装しています。
DiskCache
を生成する処理はいかのようになっており、 DiskLruCacheWrapper
を生成していることが分かりました。
public DiskCache build() { ... return DiskLruCacheWrapper.create(cacheDir, diskCacheSize); }
DiskCacheインタフェースについて
ここまでで、 DiskCache
の実装クラスとして DiskLruCacheWrapper
が使用されることが分かりました。
そもそも DiskCache
はどのようなインターフェースなのかをみておきます。
DiskCache
は以下のように、キャッシュキーを元にファイルを取得したり保存したりするためのメソッドが4つだけあります。 DiskCache.Writer
は指定されたファイルへデータを書き込むためのインタフェースになっています。
void clear() void delete(Key key) File get(Key key) void put(Key key, DiskCache.Writer writer)
DiskLruCacheWrapperの処理
DiskCache
インタフェースの役割を確認できたので、実際にどのような処理が行われているかをみていきたいと思いますが、
DiskLruCacheWrapper
はクラス名の通り DiskLruCache
をラップしたクラスとなっていて、キャッシュに関する主要な処理のほとんどは DiskLruCache
で行われています。
DiskLruCache
の生成処理は以下のようになっています。
private synchronized DiskLruCache getDiskCache() throws IOException { if (diskLruCache == null) { diskLruCache = DiskLruCache.open(directory, APP_VERSION, VALUE_COUNT, maxSize); } return diskLruCache; }
DiskLruCacheWrapper
では DiskLruCache
を扱うために、キャッシュキー(Key
型)を String
へ変換する処理も行っています。
キーの変換処理は SafeKeyGenerator
の calculateHexStringDigest
で行われ、 Key#updateDiskCacheKey
から取得できるバイト配列をSHA256でハッシュ化しているようです。
return Util.sha256BytesToHex(container.messageDigest.digest());
まとめ
Glideのデフォルトのディスクキャッシュは、LRUキャッシュで250MBまでキャッシュされることが分かりました。
(ちなみにドキュメントに書いてあります Glide v4 : Configuration - Disk Cache)
途中で出てきたキャッシュキー Key
に使われる値については Glide v4 : Caching に記述があります。
In Glide 4, all cache keys contain at least two elements:
- The model the load is requested for (File, Uri, Url). If you are using a custom model, it needs to > correctly implements hashCode() and equals()
- An optional Signature
In fact, the cache keys for steps 1-3 (Active resources, memory cache, resource disk cache) also > include a number of other pieces of data including:
- The width and height
- The optional Transformation
- Any added Options
- The requested data type (Bitmap, GIF, etc)
デバッグの際には Engine
タグで出力されるログが参考になります
adb shell setprop log.tag.Engine VERBOSE
V/Engine: Started new load in 0.594687ms, key: EngineKey{ model=content://media/external/images/media/322064, width=1050, height=1560, resourceClass=class java.lang.Object, transcodeClass=class android.graphics.drawable.Drawable, signature=com.bumptech.glide.signature.MediaStoreSignature@9ccb4e57, hashCode=-1605280832, transformations={class android.graphics.drawable.Drawable=com.bumptech.glide.load.resource.bitmap.DrawableTransformation@5db7ce1d, class android.graphics.Bitmap=com.bumptech.glide.load.resource.bitmap.FitCenter@5db7ce1d, class com.bumptech.glide.load.resource.gif.GifDrawable=com.bumptech.glide.load.resource.gif.GifDrawableTransformation@5db7ce1d, class android.graphics.drawable.BitmapDrawable=com.bumptech.glide.load.resource.bitmap.DrawableTransformation@5db7ce1d}, options=Options{values={Option{key='com.bumptech.glide.load.resource.bitmap.Downsampler.DownsampleStrategy'}=com.bumptech.glide.load.resource.bitmap.DownsampleStrategy$FitCenter@2fc7c43}}}
Glide.withに渡す引数による処理の違いについて
Androidの画像読み込みライブラリの Glide で画像を読み込む際に、 Glide.with
に渡す引数による処理の違いについて書きます。
Glide.withの処理を追いかける
Glide.with
では以下のように getRetriever(Context)
を使って取得した RequestManagerRetriever
の get
メソッドに with
の仮引数を渡して、最終的に RequestManager
を取得しています。
この流れは仮引数の型が Activity
や View
の場合も同様です。
public static RequestManager with(@NonNull Context context) { return getRetriever(context).get(context); }
private static RequestManagerRetriever getRetriever(@Nullable Context context) { ... return Glide.get(context).getRequestManagerRetriever(); }
RequestManager を生成するまで
RequestManagerRetriever#get
には Glide.with
と同じく Context
やActivity
, View
など様々な引数をとるメソッドが定義されています。
まずはじめに、 ApplicationContext
を指定した場合の処理を見ていきます。 get(Context)
のコードは以下のようになっており、 FragmentActivity
や Activity
などにキャストできる場合はキャストしてから再度 get
を呼び出しています。
ApplicationContext
の場合は getApplicationManager(Context)
が呼ばれ、その中で RequestManager
が作成されます。
@NonNull public RequestManager get(@NonNull Context context) { if (context == null) { ... } else if (Util.isOnMainThread() && !(context instanceof Application)) { if (context instanceof FragmentActivity) { return get((FragmentActivity) context); } else if (context instanceof Activity) { return get((Activity) context); } else if (context instanceof ContextWrapper && ((ContextWrapper) context).getBaseContext().getApplicationContext() != null) { return get(((ContextWrapper) context).getBaseContext()); } } return getApplicationManager(context); }
private RequestManager getApplicationManager(@NonNull Context context) { ... if (applicationManager == null) { Glide glide = Glide.get(context.getApplicationContext()); applicationManager = factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext()); } ... return applicationManager; }
次に Activity
を指定した場合や、Context
が Activity
にキャストできた場合の処理について見ていきます。
バックグラウンドスレッドで呼び出された場合は、先ほどみた ApplicationContext
を指定した時と同じ処理が呼ばれます。
そうでない場合は Factory
から RequestManager
を生成します。生成したインスタンスは RequestManager
を管理するための Fragment
で保持されています。
getRequestManagerFragment
では、 1つの FragmentManager
に対して1つの RequestManagerFragment
が保持されるように処理が行われています。
public RequestManager get(@NonNull Activity activity) { if (Util.isOnBackgroundThread()) { return get(activity.getApplicationContext()); } else { assertNotDestroyed(activity); android.app.FragmentManager fm = activity.getFragmentManager(); return fragmentGet(activity, fm, /*parentHint=*/ null, isActivityVisible(activity)); } }
private RequestManager fragmentGet( @NonNull Context context, @NonNull android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible); RequestManager requestManager = current.getRequestManager(); if (requestManager == null) { Glide glide = Glide.get(context); requestManager = factory.build(glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context); current.setRequestManager(requestManager); } return requestManager; }
Fragment
や FragmentActivity
, androidxの Fragment
の場合も FragmentManager
を取得して Activity
の時と同様の処理が行われます。
また、 View
の場合は View#getContext
を元に Activity
とその配下のすべての Fragment
を取得して、 View
が属する Fragment
や Activity
を探し出しています。
RequestManagerRetriever#get(View)
RequestManagerRetriever#findSupportFragment
RequestManagerを生成した後
ここまでで Glide.with
の返り値である RequestManager
を生成するタイミングが特定でき、以下のように大きく2パターンの RequestManager
が生成されることがわかりました。
ApplicationContextまたはバックグラウンドスレッド上で生成
factory.build( glide, new ApplicationLifecycle(), new EmptyRequestManagerTreeNode(), context.getApplicationContext() );
それ以外
factory.build( glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context );
factory
Factoryは、GlideBuilder
で指定することができます。指定しない場合は RequestManagerRetriever.DEFAULT_FACTORY
が使用されます。
RequestManagerRetriever.DEFAULT_FACTORY
Lifecycleによる違い
RequestManager
を生成する処理で、注目したいことは Lifecycle
の違いです。
ApplicationLifecycle
は常にアクティブなライフサイクルとなっています。
一方、 RequestManagerFragment#getGlideLifecycle()
は Fragment
のライフサイクルと等しくなります。
Glideでは、この Lifecycle
に応じて画像読み込みリクエストの開始/中断などが行われます。例えば、 Fragment
で画像を読み込みリクエストを開始し、読み込み完了前に Fragment
が破棄された時にリクエストを中断するということが可能です。
バックグラウンドスレッドでリクエストを行う時には、 Activity
や Fragment
のライフサイクルに応じてリクエストを止めたくないケースであることが想定されるため、 ApplicationLifecycle
で実行させているということが分かりました。
まとめ
Glide.with
に渡す引数によって、画像読み込み処理のライフサイクルが変わります。
Activity
や Fragment
と同じライフサイクルで画像を読み込めば十分な時には、 Activity
や Fragment
, View
を渡すと端末のリソースが無駄に使用されることがなくなります。そうでない場合は、ApplicationContext
を渡したり、バックグラウンドスレッド上で Glide.with
を呼び出す必要があります。
Jetpack Benchmarkを使ってView#measureにかかる時間を計測する
この記事は、 Android Advent Calendar 2019 - Qiita の23日目です。
はじめに
この記事では、ベンチマークを行うためのJetpack Benchmarkライブラリの紹介と、ベンチマークの例として View#measure
にかかる時間の計測を行います。
Jetpack Benchmarkとは
ベンチマークを行うためのライブラリで、Instrumented Tests( androidTest
ディレクトリ内のテスト)を書くかのようにベンチマークを行うことができます。
Benchmark app code | Android Developers
Jetpack Benchmarkの設定
ベンチマーク用のモジュールを追加
ベンチマーク用のコードを追加するためのモジュールを作成します。
Jetpack Benchmarkを使う際には debuggable=false
を指定する必要があるため、モジュールを分けておくと継続的に実行する際に便利です。
プラグインとライブラリを追加
ルートの build.gradle
にclasspathを設定した後、モジュールの build.gradle
でプラグインとライブラリを追加します。
classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0"
apply plugin: 'androidx.benchmark'
androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0"
また、モジュール作成時に記述されている defaultConfig
内の testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
を削除しておきます。
debuggableをfalseに設定する
AndroidTest
を実行する場合のみ debuggable=false
にするためには module/src/androidTest/AndroidManifest.xml
を作成後、以下のコードを追加します。
<application android:debuggable="false" tools:ignore="HardcodedDebugMode" tools:replace="android:debuggable"/>
以上で設定は終わりとなります。ここからは実際にベンチマークを取るためのコードを書いていきます。
サンプルのベンチマークを実行
androidTest/java/package
ディレクトリに以下のクラスを作成します。
@RunWith(AndroidJUnit4::class) class ViewMeasureTest { @get:Rule val benchmarkRule = BenchmarkRule() @Test fun benchmarkSomeWork() = benchmarkRule.measureRepeated { Thread.sleep(100) } }
注目部分は以下の2点です。
- BenchmarkRule の設定
- テストコードで
benchmarkRule.measureRepeated
を使用
measureRepeated
内に実行する処理が、ベンチマークの対象となります。
ベンチマークテストは通常のテストと同様に実行するだけで、ベンチマークをとることができます。今回は結果が分かりやすいように100 msのスリープ処理を実行するようにしました。
実行してみると以下のような出力が行われます。
benchmark: 100,160,468 ns ViewMeasureTest.benchmarkSomeWork
処理に 100,160,468 ns ≒ 100 ms
かかったという出力が得られました。
このようにJetpack Benchmarkでは、テストコードと同じ記述方法でベンチマークをとることができます。
View#measureにかかる時間を計測する
もう1つの例として、weight
を使った LinearLayout
を入れ子にした場合としなかった場合の View#measure
の計測を行います。
(weight
を入れ子で使用するとAndroidStudioでは Nested weights are bad for performance
というinspectionが表示されます。)
レイアウトファイル
使用したレイアウトファイルは以下の2つです
- 入れ子にした場合 https://github.com/sckm/MeasureViewSizeBenchmark/blob/master/view-measure/src/main/res/layout/layout_nested.xml
- 入れ子にしない場合 https://github.com/sckm/MeasureViewSizeBenchmark/blob/master/view-measure/src/main/res/layout/layout_no_nested.xml
レイアウトプレビューは以下の画像のようになります。
ベンチマーク用コード
以下のコードを実行して計測を行いました。
ベンチマークテストでは measureRepeated
内で実行する処理の計測を行いますが、 runWithTimingDisabled
内に書いた処理は計測から除外することが可能です。
@Test fun noNestedWeights() { benchmarkRule.measureRepeated { val container: ViewGroup = runWithTimingDisabled { val context = ApplicationProvider.getApplicationContext<Context>() LayoutInflater.from(context).inflate(R.layout.layout_no_nested, null) as ViewGroup } measureAndLayoutWrapLength(container) } } private fun measureAndLayoutWrapLength(container: ViewGroup) { val widthMeasureSpec = MeasureSpec.makeMeasureSpec(360, MeasureSpec.EXACTLY) val heightMeasureSpec = MeasureSpec.makeMeasureSpec(640, MeasureSpec.AT_MOST) container.measure(widthMeasureSpec, heightMeasureSpec) container.layout( 0, 0, container.measuredWidth, container.measuredHeight ) }
計測結果
ベンチマークテストを実行した結果は以下のようになりました。
レイアウト構造が少し異なるため一概には言えませんが、 weight
を入れ子で使用した場合は、しなかった場合と比べて処理に大きく時間がかかっていることが分かります。
入れ子にした場合 : benchmark: 475,000 ns ViewMeasureTest.nestedWeights 入れ子にしない場合: benchmark: 233,802 ns ViewMeasureTest.noNestedWeights
最後に
Jetpack Benchmarkライブラリを使用することで、簡単に安定したベンチマークをとることができます。
今回は View#measure
を例にしましたが、公式のサンプルにあるオートボクシングのようにAndroidに依存していないコードのベンチマーク計測も可能です。
公式のサンプルはこちら performance-samples/BenchmarkSample at master · android/performance-samples · GitHub
例で使用したコードなどは以下のレポジトリで確認することができます。 github.com
Scratchファイルを使って気軽に実行可能なコードを保存する
概要
この記事ではIntellijでサポートされたKotlinのScratchファイルについて紹介を行います
Scratchファイル自体はKotlinに限らずJavaなどもサポートされています
Scratchファイルとは
Kotlinなどの実行可能なコードを保存するファイルで、デフォルトではプロジェクト外に保存されます。 プロジェクト内にあるファイルと違う点は、あらかじめプロジェクトをビルドしておけば、Scratchファイルのビルドだけで実行可能なため、実行までの時間が速いことです。
実行環境
- IntelliJ IDEA 2018.3.1 (Community Edition)
- Kotlin Plugin 1.3.10-release-IJ2018.3-1
Hellow World!
プロジェクトにファイルを追加する時と同じように
New
->Scratch File
を選択Scratchファイルの形式が選べるので
Kotlin
を選択
ファイルが作成されるので
Use classpath of Module
からモジュールを選択してコードを記述します
実行してみる
左上にある実行ボタンを押すとコードが実行され、エディタ上に結果が表示されます
※実行前にはモジュールのBuildをするか Make Before Run
にチェックする必要があります
各行ごとに出力が表示されるのがとても便利です
選択したモジュール内のクラスを使う
Scratchファイルでは、モジュールの中で定義されているクラスを使用することが可能です
例えば以下のようなクラスが定義されている場合、Scratchファイルでも使用することが可能です
package com.github.sckm data class Person( val name: String, val age: Int )
ライブラリを使用する
ライブラリを使用する際は、Scratchファイル用に依存を追加することはできず、
使用するモジュールの build.gradle
などで依存を追加する必要があります
使用例
自分がよく使うパターンとしては、
- プロジェクトでたまに使うAPIのレスポンスを確認したい時
- IDE上でできるので1番よく使用しています
- ちょっとした処理の動作を確認したい時
- テストコードとして実行すると時間がかかるプロジェクトで時間を節約可能
Scratchファイルの保存場所
~/Library/Preferences/IdeaIC2018.3/scratches/
プロジェクトとは関係無い場所に保存がされるため間違えてGitリポジトリに含めてしまうという心配もありません
もちろん、IDE上からでもファイルを開くこともできます
まとめ
- コード実行時に新しくプロジェクトを作ったり、Git配下にファイルを追加する必要が無い
- モジュール内のクラスを使用可能
- テストコードと似た感覚でカジュアルに実行可能
参考
Scratches - Help | IntelliJ IDEA
Tuning IntelliJ IDEA - Help | IntelliJ IDEA
ブログに実行可能なKotlinコードを貼り付ける
ブログなどのサイト上で実行可能なKotlinコードを表示するためのスクリプト(Kotlin Playground)がJetBrainsから公開され、 Try Kotlin や Kotlinのリファレンスにあるような機能を自分のサイトでも使うことが可能になりました。
all - Kotlin Programming Language
使い方
scriptの記述
code
タグ内にコードを記述する場合は以下のコードをヘッダーなどに記述します。
<script src="https://unpkg.com/kotlin-playground@1" data-selector="code"></script>
上の記述ではcode
タグすべてにKotlinPlaygroundが適用されてしまいます。
はてなブログのMarkdownで使う場合には以下のようにKotlinコードのみで適用されるようにするのが良いでしょう。
以後の説明ではこちらを使っていると仮定して進めていきます。
<script src="https://unpkg.com/kotlin-playground@1"></script> <script> document.addEventListener('DOMContentLoaded', function() { KotlinPlayground('.lang-kotlin'); }); </script>
以上で準備は終了になります。
コードの記述
はてなブログのMarkdownの場合は、以下のようにコードを記述します
実際の表示
以下にKotlin Playgroundを使ったコードを表示しています。
fun main(args : Array<String>) { println("Hello Kotliner!") println("Click this green button at the top right!") }
最後に
- はてなブログなど、自分で自由にいじるのが難しい環境では少し扱いずらい部分がある
- KotlinPlaygroundが少し重い?
さらに詳しい情報は以下を参照してください