kotlinで複数のAsyncTaskLoaderでカジュアルにview操作を行う
複数のAsyncTaskLoaderを実行して非同期でview操作を行いたかったので。thread立ててhandlerで処理すれば手っ取り早いのだが、Androidでthreadを乱発させるのはお作法が悪いようなのでAsyncTaskLoaderでやることにした。
ベースとなるAsyncTaskLoaderの実装
class MyAsyncTaskLoader(context: Context, val backgroundFunc:(()->String?)? = null): AsyncTaskLoader<String>(context){
override fun loadInBackground(): String? {
backgroundFunc?.let{
return it()
}
return null
}
override fun onStartLoading(){
forceLoad()
}
}
コンストラクタでloadInBackground内で処理するクロージャを受け取るようにした。戻り値はloadInBackgroundの戻り値と同様のStringのオプショナルにしたが、このあたりは適宜使い方に合わせて変更したり、loadInBackgroundを実装したり、継承したり自由にすればいいかな。
LoaderManager.LoaderCallbacks の実装
サンプルコードではよくActivityのインターフェースとして実装されているが、カジュアルに非同期処理を行いたかったので、別インスタンスとして使用できるよう切り出しを行った。
class MyLoaderManager(
val context: Context,
val loaderManager: LoaderManager,
val createFunc:(()->String?)? = null,
val finishFunc:((data:String?)->Unit)? = null )
: LoaderManager.LoaderCallbacks<String>{
fun initLoader(i:Int,bundle: Bundle?){
loaderManager.initLoader(i, bundle, this)
}
override fun onCreateLoader(p0: Int, p1: Bundle?):Loader<String>{
return MyAsyncTaskLoader(context, createFunc)
}
override fun onLoadFinished(p0: Loader<String>, p1: String?) {
finishFunc?.let {
it(p1)
}
}
override fun onLoaderReset(p0: Loader<String>) {
}
}
こちらはコンストラクタにonCreateLoaderに渡すクロージャー( = loadInBackgroundで実行する処理 )とonLoadFinishedで実行するクロージャーを渡す。実装はActivity側に記述することでカジュアルにインスタンスを利用することができる。で、いつもはActivity側で実行するinitLoaderをこちらのクラスに移管した。
Activity側で利用はこんな感じ
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val lm = LoaderManager.getInstance(this)
//スレッドAはlayoutIdAのviewを操作
val loaderA = MyLoaderManager( this, lm, {
//loadInBackgroundで実行したい処理
"String Data"
},{
//onLoadFinishedで実行したい処理
//ここではviewの操作 String Dataと表示
layoutIdA.text = it
})
loaderA.initLoader( 1, null )
//スレッドBはlayoutIdBのviewを操作
val loaderB = MyLoaderManager( this, lm, {
//loadInBackgroundで実行したい処理
"スレッドB Data"
},{
//スレッドB Dataと表示
layoutIdB.text = it
})
loaderB.initLoader( 2, null )
}
}
LoaderManager.getInstance(this)に渡すcontextはFragmentActivityのコンテキストじゃないと叱られるようなので
class MainActivity : FragmentActivity()
と継承するようにした。 やりたかったことは単純にコールバックそのものをActivityで実装するんじゃなくてswiftライクに必要な処理だけコールバックに追加したかった。という話し。
例えば通信したで取得したデータをviewに表示する。
val lm = LoaderManager.getInstance(this)
val loader = MyLoaderManager( this, lm, {
val connection = URL("https://yahoo.co.jp").openConnection() as HttpsURLConnection
connection.connect()
val stringBuilder = StringBuilder()
InputStreamReader( BufferedInputStream(connection.inputStream) ).forEachLine {
stringBuilder.append(it)
}
stringBuilder.toString()
},{
textViewID.text = it
})
とかすると、サクっとtextViewに表示できたりする。まあこの例だけでいうとretrofit使おうよという話しなんだけど(^_^;)