I Use Douban Api instead of Google Book Api for this demo.
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 the AsyncTask
has finished loading.So, in a word, onPreExecute()
, onProgressUpdate()
and onPostExecute()
is run on UI thread, doINBackground()
run on worker thread.
When you create an AsyncTask
subclass, you can configure it using these parameters:
doInBackground()
override method.onProgressUpdated()
override method.onPostExecute()
override method.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()
}
}
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.
import android.support.v4.content.AsyncTaskLoader
class MovieLoader(val mContext: WeakReference<Context>, val query: String) :
AsyncTaskLoader<ArrayList<Movie>>(mContext.get()!!) {
}
loadInBackground
Notice the similarity between this method and the initial doInBackground()
method from AsyncTask
.
override fun loadInBackground(): ArrayList<Movie>? {
return NetUtil.getMovieList(query)
}
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()
}
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>>) {
}
}
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)
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);
}
}
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
}
}