Kotlin for Android:非同期呼び出しで弱いリンクを簡単に操作できるようにする

Javaで記述し、フラグメントまたはアクティビティで非同期API呼び出しを待ちますか? 匿名クラスを使用していますか? この記事では、GCおよび可能性のあるIllegalStateExceptionを損なうことなくKotlinでこれを実行する方法を説明します。


画像


この記事では、Androidアプリケーションのコンポーネントからの非同期呼び出しを待機する例での弱いリンクの動作について説明します。 ただし、この経験は、弱いリンクを使用する必要がある他の状況にも適用できます。


PS。 私はかなり長い間Swiftで書いています。 そして、以前にもJava 6でAndroidアプリケーションを作成しました。そして、少しの間でもAndroidアプリケーションに戻りたいとは思いませんでした。 しかし、義務として、Androidアプリケーションを開発する必要がありました。 その時までに、JetBrainsはすでにjvmでコンパイルされた言語Kotlinをリリースしていました(執筆時点でバージョン1.1.1)。 ドキュメントを見て、私は自分のプロジェクトがJavaに含まれないことを固く決心しました。


最初に、Androidの標準開発ツールであるJavaを使用して非同期呼び出しを処理する例を示します。


Java(<8)


アプリケーションをプロトタイプ化し、UIコンポーネント(この場合はアクティビティ)から直接リクエストを行うときの標準的な状況を考慮してください。


// MainActivity.java void loadComments() { api.getComments(new Api.ApiHandler<Api.Comment>() { @Override public void onResult(@Nullable List<Api.Comment> comments, @Nullable Exception exception) { if (comments != null) { updateUI(comments); } else { displayError(exception); } } }); } 

この場合の犯罪は明らかです。 匿名ハンドラークラスは、コンポーネント(デバッガーのこの$ 0の暗黙的なプロパティ)への強力な参照を保持します。これは、ユーザーがアクティビティを完了することを決定した場合、あまり良くありません。


画像


アクティビティへの弱いリンクを使用すると、この問題を解決できます。


 // MainActivity.java void loadCommentsWithWeakReferenceToThis() { final WeakReference<MainActivity> weakThis = new WeakReference<>(this); api.getComments(new Api.SimpleApiHandler<Api.Comment>() { @Override public void onResult(@Nullable List<Api.Comment> comments, @Nullable Exception exception) { MainActivity strongThis = weakThis.get(); if (strongThis != null) if (comments != null) strongThis.updateUI(comments); else strongThis.displayError(exception); } }); } 

もちろん、これは機能しません。 前述のように、匿名クラスは、作成されたオブジェクトへの強力な参照を保持します。


画像


唯一の解決策は、コンポーネントのライフサイクルにさらされていない別のオブジェクト(この場合、クラスApiのオブジェクト)に弱いリンクを渡す(または内部で作成する)ことです。


 // MainActivity.java public class MainActivity extends AppCompatActivity implements Api.ApiHandler<Api.Comment> { void loadCommentsWithWeakApi() { api.getCommentsWeak(this); } @Override public void onResult(@Nullable List<Api.Comment> comments, @Nullable Exception exception) { if (comments != null) updateUI(comments); else displayError(exception); } 

 // Api.java class Api { void getCommentsWeak(ApiHandler<Comment> handler) { final WeakReference<ApiHandler<Comment>> weakHandler = new WeakReference<>(handler); new Thread(new Runnable() { @Override public void run() { … // getting comments ApiHandler<Comment> strongHandler = weakHandler.get(); if (strongHandler != null) { strongHandler.onResult(new ArrayList<Comment>(), null); } } }).start(); } … } 

その結果、匿名クラスを完全に取り除き、ActivityはApiハンドラーインターフェースを実装し、結果を別のメソッドで受け取ります。 かさばる。 機能しません。 しかし、その後、アクティビティへのリンクが保留されなくなります。


私はSwiftでどうしますか:


 // ViewController.swift func loadComments() { api.getComments {[weak self] comments, error in //   self guard let `self` = self else { return } //  self ,   if let comments = comments { self.updateUI(comments) } else { self.displayError(error) } } } 

この場合、識別子self背後にあるオブジェクト(値はJavaのthisとほぼ同じです)が弱参照としてラムダに渡されます。


また、Pure Javaでは、このような動作を実装することはほとんどありません。


コトリン


Kotlinの機能を書き換えます:


 // MainActivity.kt fun loadComments() { api.getComments { list, exception -> if (list != null) { updateUI(list) } else { displayError(exception!!) } } } 

Kotlinラムダ(Java 8など)は匿名クラスよりもスマートであり、引数が使用される場合にのみ引数をキャプチャします。 残念ながら、(C ++やSwiftのように)キャプチャルールを指定することはできないため、アクティビティへのリンクは強力にキャプチャされます。


画像
(ここで、ラムダがFunction2<T,V>インターフェースを実装するオブジェクトであることがわかります)


しかし、ラムダへの弱いリンクを渡すことを妨げるもの:


 // MainActivity.kt fun loadCommentsWeak() { val thisRef = WeakReference(this) //    Activity api.getComments { list, exception -> val `this` = thisRef.get() //  Activity  null if (`this` != null) if (list != null) { `this`.updateUI(list) } else { `this`.displayError(exception!!) } } } 

画像


デバッガーからわかるように、ハンドラーにはアクティビティへの直接リンクがありません。これは達成する必要がありました。 機能的なスタイルで記述された安全な非同期呼び出し応答ハンドラーを取得しました。


ただし、Kotlin sugarを使用すると、Swift構文にさらに近づくことができます。


 // MainActivity.kt fun loadCommentsWithMagic() { val weakThis by weak(this) //  weak- api.getComments { list, exception -> val `this` = weakThis?.let { it } ?: return@getComments if (list != null) `this`.updateUI(list) else `this`.displayError(exception!!) } } 

val A by B構成val A by Bは、変数Aの値が設定および受信されるデリゲートオブジェクトBへの変数Aの割り当てです。


weak(this)は、 WeakRef特殊クラスの単純化されたコンストラクター関数です。


 // WeakRef.kt class WeakRef<T>(obj: T? = null): ReadWriteProperty<Any?, T?> { private var wref : WeakReference<T>? init { this.wref = obj?.let { WeakReference(it) } } override fun getValue(thisRef:Any? , property: KProperty<*>): T? { return wref?.get() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) { wref = value?.let { WeakReference(it) } } } //   - fun <T> weak(obj: T? = null) = WeakRef(obj) 

WeakRefは、デリゲートとして使用できるようにするWeakRefデコレータです。 Kotlinの委任の詳細については、言語のWebサイトをご覧ください


現在、 val A by weak(B)の構築により、弱い変数とプロパティを宣言できます。 たとえば、Swiftでは、この機能は言語レベルでサポートされています。


 weak var A = B 

別の砂糖を追加


 // MainActivity.kt fun loadCommentsWithSugar() { val weakThis by weak(this) api.getComments { list, exception -> weakThis?.run { if (list != null) updateUI(list) else displayError(exception!!) }} } 

コードの特定の部分では、特定のオブジェクトを指定しなくてもActivityの関数を呼び出し始めます。これは、元のアクティビティを参照しているかのように、自動的にハンドラにキャプチャします。 そして、私たちはこれを長い間取り除こうとしてきました。


画像


デバッガーからわかるように、これは起こりません。


Kotlinのラムダの優れた機能は、(JavaScriptのように)所有者を設定できることです。 したがって、 thisweakThis?.run後のラムダで実行され、Activityオブジェクトの値を取り、ラムダ自体はこのオブジェクトがまだメモリにあるときにのみ実行されます。 run()関数は任意のタイプの拡張機能であり、呼び出されるオブジェクトの所有者を使用してラムダを作成できます( let()apply()let()ような他のマジック関数also() apply() )。


画像


デバッガーでは、ラムダ所有者が$receiverプロパティとして指定されます。


Kotlinのラムダに関する詳細は、言語のWebサイトで見つけることができます。


最後に、もう少し砂糖:


 // MainActivity.kt fun loadCommentsWithDoubleSugar() = this.weak().run { //  this  WeakReference<Activity> api.getComments { list, exception -> this.get()?.run { //  this  Activity if (list != null) updateUI(list) else displayError(exception!!) }}} // weakref.kt //      weak() fun <T>T.weak() = WeakReference(this) 

更新: Java 8


Java 8のラムダも、作成されたオブジェクトをキャプチャしません。


 void loadCommentsWithLambdaAndWeakReferenceToThis() { final WeakReference<MainActivity> weakThis = new WeakReference<>(this); api.getComments((comments, exception) -> { MainActivity strongThis = weakThis.get(); if (strongThis != null) if (comments != null) strongThis.updateUI(comments); else strongThis.displayError(exception); }); } 

画像


AndroidはまだJava 8を完全にはサポートしていませんが、 一部の機能は既にサポートされています。 Android Studio 2.4 Preview 4より前のバージョンでは、 Jackツールチェーンを使用する必要があります。


結論


この記事では、Androidアプリケーションのライフサイクルのコンポーネントからの非同期呼び出しを安全に待機する問題をKotlinを使用して解決する方法の例を示し、Javaが提供するソリューション(<8)と比較しました。


Kotlinを使用すると、ライフサイクルのセキュリティを損なうことなく機能スタイルでコードを記述できます。これは間違いなくプラスです。


→言語のすべての機能を理解するには、 ドキュメントを読むことができます
→KotlinをAndroidプロジェクトに統合する方法については、 こちらをご覧ください
gitのプロジェクトのソース。


更新 :Java 8で追加



Source: https://habr.com/ru/post/J325966/


All Articles