App Overview
I Use Douban Api instead of Google Book Api for this demo.
AsyncTask Inro
AsyncTask
is an abstract class, which means you must subclass it in order to use it. In this example the AsyncTask
performs a very simple background task: it sleeps for a random amount of time. In a real app, the background task could perform all sorts of work, from querying a database, to connecting to the internet, to calculating the next Go move to beat the current Go champion.
An AsyncTask
subclass has the following methods for performing work off of the main thread:
onPreExecute()
: This method runs on the UI thread, and is used for setting up your task (like showing a progress bar).doInBackground()
: This is where you implement the code to execute the work that is to be performed on the separate thread.onProgressUpdate()
: This is invoked on the UI thread and used for updating progress in the UI (such as filling up a progress bar)onPostExecute()
: Again on the UI thread, this is used for updating the results to the UI once theAsyncTask
has finished loading.
So, in a word, onPreExecute()
, onProgressUpdate()
and onPostExecute()
is run on UI thread, doINBackground()
run on worker thread.
Params
When you create an AsyncTask
subclass, you can configure it using these parameters:
- Params: The data type of the parameters sent to the task upon executing the
doInBackground()
override method. - Progress: The data type of the progress units published using the
onProgressUpdated()
override method. - Result: The data type of the result delivered by the
onPostExecute()
override method.
Simple implement
When onPreExecute()
, onProgressUpdate()
and onPostExecute()
is run on UI thread, we can define a callback to expose these method, so we can call in Activity, and do not need to pass UI elements to AsyncTask. So In Java, a callback is a interface.
-
Define a callback:
interface Callback<T, STATUS> { fun onNext(t: T) fun onUpdate(status: STATUS) fun onError(t: Throwable) }
-
Hold a weak reference in AsyncTask:
class SearchMovieAsyncTask(private val callback: WeakReference<Callback<ArrayList<Movie>, Boolean>>) : AsyncTask<String, Boolean, ArrayList<Movie>?>() { }
override fun doInBackground(vararg querys: String?): ArrayList<Movie>? { val query = querys[0] ?: return null publishProgress(true) return NetUtil.getMovieList(query) } override fun onProgressUpdate(vararg values: Boolean?) { callback.get()?.onUpdate(values[0] ?: true) } override fun onPostExecute(result: ArrayList<Movie>?) { super.onPostExecute(result) if (result != null) { callback.get()?.onNext(result) } else { callback.get()?.onError(NullPointerException(result)) } }
-
Implement callback in MainActivity:
callback = object : SearchMovieAsyncTask.Callback<ArrayList<Movie>, Int> { override fun onUpdate(status: Int) { progressBar.visibility = View.VISIBLE } override fun onNext(movies: ArrayList<Movie>) { adapter.clear() adapter.addAll(movies) progressBar.visibility = View.INVISIBLE } override fun onError(t: Throwable) { Toast.makeText(this@MainActivity, "Error: ${t.message}", Toast.LENGTH_SHORT).show() } }
AsyncTaskLoader
This is same as define a custom callback, but Android already done it for us.It called AsyncTaskLoader
.
If you want a more flexible interaction a custom callback is ok.
Create an AsyncTaskLoader
import android.support.v4.content.AsyncTaskLoader
class MovieLoader(val mContext: WeakReference<Context>, val query: String) :
AsyncTaskLoader<ArrayList<Movie>>(mContext.get()!!) {
}
Implement loadInBackground
Notice the similarity between this method and the initial doInBackground()
method from AsyncTask
.
override fun loadInBackground(): ArrayList<Movie>? {
return NetUtil.getMovieList(query)
}
Implement required methods
Inside the onStartLoading()
method stub, call forceLoad()
to start the loadInBackground()
method. The loader will not start loading data until you call the forceLoad()
method.
override fun onStartLoading() {
super.onStartLoading()
forceLoad()
}
Modify MainActivity
class MainActivity : AppCompatActivity(), LoaderManager.LoaderCallbacks<ArrayList<Movie>> {
override fun onCreateLoader(id: Int, args: Bundle?): Loader<ArrayList<Movie>> {
return MovieLoader(WeakReference(this), args?.getString("query")!!)
}
override fun onLoadFinished(loader: Loader<ArrayList<Movie>>, list: ArrayList<Movie>?) {
adapter.clear()
adapter.addAll(list ?: emptyList())
progressBar.visibility = View.INVISIBLE
}
override fun onLoaderReset(list: Loader<ArrayList<Movie>>) {
}
}
Start Loader
The restartLoader()
method is defined by the LoaderManager
, which manages all the loaders used in an activity or fragment. Each activity has exactly one LoaderManager
instance that is responsible for the lifecycle of the Loaders
that the activity manages.
val queryBundle = Bundle()
queryBundle.putString("query", s)
Log.d(TAG, "onQueryTextSubmit: restart loader")
this@MainActivity.supportLoaderManager.restartLoader(0, queryBundle, this@MainActivity)
Restore Loader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_search)
if (supportLoaderManager.getLoader<ArrayList<Movie>>(0) != null) {
supportLoaderManager.initLoader(0, null, this);
}
}
NetUtil
Without any library, just use raw java to do network and parse json:
object NetUtil {
const val TAG = "NetUtil"
val API = "https://api.douban.com/v2/movie/search?q=%s&apikey=0b2bdeda43b5688921839c8ecb20399b&start=0&count=20"
fun getMovieList(query: String): ArrayList<Movie>? {
val url = URL(API.format(query))
var urlConnection: URLConnection? = null
var bufferReader: BufferedReader? = null
var json: String = ""
try {
urlConnection = url.openConnection() as HttpURLConnection
with(urlConnection) {
requestMethod = "GET"
connect()
}
val inputStream = urlConnection.inputStream
bufferReader = BufferedReader(InputStreamReader(inputStream))
Log.d(TAG, "doInBackground: start read")
json = buildString {
var line = bufferReader.readLine()
while (line != null) {
append(line)
append("\n")
line = bufferReader.readLine()
}
}
if (json.isEmpty()) {
return null
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
bufferReader?.close()
}
Log.d(TAG, "doInBackground: $json")
val movieList = ArrayList<Movie>()
try {
val jsonObject = JSONObject(json)
val movieArray = jsonObject.getJSONArray("subjects")
var i = 0
while (i < movieArray.length()) {
val subject = movieArray.getJSONObject(i)
val title = subject.getString("title")
val year = subject.getString("year")
movieList.add(Movie(subject.getString("id"), title, year))
i++
Log.d(TAG, "doInBackground: title: $title, year: $year")
}
} catch (e: Exception) {
e.printStackTrace()
}
return movieList
}
}