安卓利用Handlers,AsyncTask和Loaders运行后台程序
安卓的用户界面线程(user interface thread)
1.1 主线程
安卓修改用户界面并从一个单一用户界面线程中处理输入事件,这个线程也被称作主线程(main thread)
Android将所有的事件都收集进一个队列中,并且作为Looper类的一个实例来进行处理
1.2为什么要利用并发性
如果程序不使用任何并发结构,那么一个安卓app的所有代码都运行在一个主线程中,而且每一段代码都要在等待它前面的代码运行完之后才能运行。
如果有一个持续时间比较长的操作,比如从网络上加载数据,那么这个应用就会被卡住,必须一直等到相应的操作完成。
要想有一个好的用户体验,所有潜在的,运行较慢的操作都应该构建异步。
在安卓中使用java线程结构
2.1使用java中的Thread和Thread pools
安卓支持使用Thread类来实现异步运行,安卓也支持用java.util.concurrent包中的类来实现后台运行,比如使用ThreadPools和Executor类,如果你想在新的线程中更新用户界面,你需要与用户界面线程进行同步。
2.2在Android中使用java Thread类的弊端
-
如果使用Thread类,就必须在你的代码中处理以下需求
-
如果要将结果返回到用户界面,则需要和主线程同步
-
默认不会取消线程
-
没有默认的线程池
-
没有默认的处理配置更改
因为这些限制,安卓开发者一般会使用android特殊的处理方式。
安卓中的并发结构
Android提供了另外的结构来处理异步性,可以使用android.os.Handler类或者AsyncTasks类,更加复杂的方法是基于Loader类的留存片段及服务
Handler
3.1使用Handler类的目的
Handler可以登记到一个线程中并且提供一个简单的通道来传送数据到这个线程中。
一个Handler对象将自己与它创建的线程相关联。比如说在onCreate()方法中创建Handler类的实例,则此Handler可以传送数据到主线程中。
通过Handler的数据可以是Message的一个实例,或者是Runnable的一个实例
3.2创建并重用Handler实例
要想使用handler必须重写handleMessage()方法来处理messages线程可以通过sendMessage(Message)来传送messages,或者使用sendEmptyMessage()
在Runnable中,可以使用post()方法,为了避免创建对象,可以重用activity中已经存在的Handler对象。
Handler = getWindow().getDecorView().getHandler();
View类允许通过post()方法传递Runnable类型的对象。
3.3举例
以下代码演示如何通过View来使用Handler
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:indeterminate="false" android:max="10" android:padding="4dip" > </ProgressBar> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" > </TextView> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="startProgress" android:text="Start Progress" > </Button> </LinearLayout>
package de.vogella.android.handler; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class ProgressTestActivity extends Activity { private ProgressBar progress; private TextView text; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); progress = (ProgressBar) findViewById(R.id.progressBar1); text = (TextView) findViewById(R.id.textView1); } public void startProgress(View view) { // do something long Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i <= 10; i++) { final int value = i; doFakeWork(); progress.post(new Runnable() { @Override public void run() { text.setText("Updating"); progress.setProgress(value); } }); } } }; new Thread(runnable).start(); } // Simulating something timeconsuming private void doFakeWork() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }
AsyncTask
4.1使用AsyncTask类的目的
AsyncTask类将创建后台运行程序和与主线程同步的所有操作都封装起来。它还支持通知运行中的程序的进度
4.2使用AsyncTask类
想使用AsyncTask必须继承它,AsyncTask使用泛型和可变参数。参数如下AsyncTask <TypeOfVarArgParams , ProgressValue , ResultValue>
一个AsyncTask通过execute方法开始
Execute()方法调用doInBackground()和onPostExecute()方法。
TypeOfVarArgParams 传递到doInBackground()方法中,ProgressValue作为进度信息,ResultValue必须从doInBackground()方法中返回并作为参数传递到onPostExecute()方法中。
doInBackground()方法包含在后台线程中执行的程序,这个方法在一个单独的线程中自动运行。
onPostExecute()方法与主线程同步,并更新主线程,这个方法在doInBackground()完成后被调用。
4.3多个异步任务的并行执行
在Android中可以使用executeOnExecutor()方法来实现并行执行,将AsyncTask.THREAD_POOL_EXECUTOR作为第一个参数。
// ImageLoader extends AsyncTask ImageLoader imageLoader = new ImageLoader(imageView); // Execute in parallel imageLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "http://url.com/image.png");
4.4AsyncTask的弊端
AsyncTask不会自动处理配置的变化,如果活动被重新创建,程序员必须在代码中自己处理。解决这种问题的一般方法是在一个保留片段中声明AsyncTask
4.5例子
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/readWebpage" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="onClick" android:text="Load Webpage" > </Button> <TextView android:id="@+id/TextView01" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Placeholder" > </TextView> </LinearLayout>
package de.vogella.android.asynctask; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import de.vogella.android.asyntask.R; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.TextView; public class ReadWebpageAsyncTask extends Activity { private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textView = (TextView) findViewById(R.id.TextView01); } private class DownloadWebPageTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... urls) { String response = ""; for (String url : urls) { DefaultHttpClient client = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(url); try { HttpResponse execute = client.execute(httpGet); InputStream content = execute.getEntity().getContent(); BufferedReader buffer = new BufferedReader(new InputStreamReader(content)); String s = ""; while ((s = buffer.readLine()) != null) { response += s; } } catch (Exception e) { e.printStackTrace(); } } return response; } @Override protected void onPostExecute(String result) { textView.setText(result); } } public void onClick(View view) { DownloadWebPageTask task = new DownloadWebPageTask(); task.execute(new String[] { "http://www.vogella.com" }); } }
后台程序运行及生命周期处理
5.1当配置发生改变时恢复状态
在使用线程的时候,面临的一个挑战,就是需要考虑到应用的生命周期。安卓系统会杀死activity或者产生一个可以触发activity重启的配置变化。
同时,你还要处理已经打开的对话框,因为对话框也与activity相关联,当activity重启的时候,访问现有的对话框时,会得到一个View not attached to window manager 的异常
可以使用onRetainNonConfigurationInstance()方法来保存对象,通过getLastNonConfigurationInstance()方法来取回之前保存的对象。如果活动是第一次启动,或者通过finish()方法结束,则getLastNonConfigurationInstance()方法返回null,但是onRetainNonConfigurationInstance()在API13之后就被废除了,建议使用fragment和setRetainInstace()方法来保存数据。
5.2使用appliccation来存储对象
如果当配置发生变化时,多于一个对象要被存储在活动中,则可以通过继承Application类来实现,需要在AndroidManifest.xml中配置,将继承了Application类的类名分配给android:name属性。
<application android:icon="@drawable/icon" android:label="@string/app_name" android:name="MyApplicationClass"> <activity android:name=".ThreadsLifecycleActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>
应用类将会被Android runtime自动创建,并且一直可用,除非整个应用进程终止。
这个类可以用来获取那些可以在活动之间传递或者在整个应用周期中都可用的数据,在onCreate()方法里,你可以创建对象并且将他们设为public或者创建getter方法以便获取他们。
onTerminate()方法只是被用来测试。如果Android终止进程,则所有的相关资源都会被释放。
可以通过getApplication()来得到Application类
Loader
6.1 使用Loader的目的
Loader类可以允许你在活动或者碎片中异步加载数据,他们可以监控数据源并且当内容发送变化时,将最新的结果传递出来。能够保持数据,即使环境发送变化。
如果结果被Loader恢复,当对象和它的父(活动或者碎片)已经失去联系的时候,Loader可以缓存数据。
6.2 实现一个Loader
可以使用一抽象类AsyncTaskLoader作为你自己的Loader的基础,并实现它
LoaderManager管理一个或多个Loader实例,可以以下面的方法来创建一个Loader
# start a new loader or re-connect to existing one getLoaderManager().initLoader(0, null, this);
第一个参数是一个唯一的id,用于在回调时辨别是哪个Loader。第二个参数是一个bundle,用于携带信息,第三个参数回调类,必须继承LoaderManger.LoaderCallbacks接口、
Loader不是直接通过调用getLoaderManager().initLoader()方法创建的,而是必须在回调方法onCreateLoader()中创建。一旦Loader完成了读取数据的工作,就会触发onLoadFinished()方法,在这里方法里可以更新用户界面
6.3 SQLite数据库和CursorLoader
Android提供了一个Loader,这个Loader默认实现了如何和SQlite数据库相关联的操作,即
CursorLoader类
如果Cursor无法使用时,onLoaderReset()方法就会被触发。
Custom Loader for preference
下面的代码将创建一个自定义的loader,并实现管理偏好。
package com.vogella.android.loader.preferences; import android.content.AsyncTaskLoader; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; public class SharedPreferencesLoader extends AsyncTaskLoader<SharedPreferences> implements SharedPreferences.OnSharedPreferenceChangeListener { private SharedPreferences prefs = null; public static void persist(final SharedPreferences.Editor editor) { editor.apply(); } public SharedPreferencesLoader(Context context) { super(context); } // Load the data asynchronously @Override public SharedPreferences loadInBackground() { prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); prefs.registerOnSharedPreferenceChangeListener(this); return (prefs); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // notify loader that content has changed onContentChanged(); } /** * starts the loading of the data * once result is ready the onLoadFinished method is called * in the main thread. It loader was started earlier the result * is return directly * method must be called from main thread. */ @Override protected void onStartLoading() { if (prefs != null) { deliverResult(prefs); } if (takeContentChanged() || prefs == null) { forceLoad(); } } }
在活动中使用此loader
package com.vogella.android.loader.preferences; import android.annotation.SuppressLint; import android.app.Activity; import android.app.LoaderManager; import android.content.Loader; import android.content.SharedPreferences; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks<SharedPreferences> { private static final String KEY = "prefs"; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.prefs); getLoaderManager().initLoader(0, null, this); } @Override public Loader<SharedPreferences> onCreateLoader(int id, Bundle args) { return (new SharedPreferencesLoader(this)); } @SuppressLint("CommitPrefEdits") @Override public void onLoadFinished(Loader<SharedPreferences> loader, SharedPreferences prefs) { int value = prefs.getInt(KEY, 0); value += 1; textView.setText(String.valueOf(value)); // update value SharedPreferences.Editor editor = prefs.edit(); editor.putInt(KEY, value); SharedPreferencesLoader.persist(editor); } @Override public void onLoaderReset(Loader<SharedPreferences> loader) { // NOT used } }
利用service运行后台任务
下面的代码将实现下载图片,并且展示有个dialog,直到下载完成,这个dialog才会消失。要确保即使活动重启线程也被保留下来。
public class ThreadsLifecycleActivity extends Activity { // Static so that the thread access the latest attribute private static ProgressDialog dialog; private static Bitmap downloadBitmap; private static Handler handler; private ImageView imageView; private Thread downloadThread; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // create a handler to update the UI handler = new Handler() { @Override public void handleMessage(Message msg) { imageView.setImageBitmap(downloadBitmap); dialog.dismiss(); } }; // get the latest imageView after restart of the application imageView = (ImageView) findViewById(R.id.imageView1); Context context = imageView.getContext(); System.out.println(context); // Did we already download the image? if (downloadBitmap != null) { imageView.setImageBitmap(downloadBitmap); } // check if the thread is already running downloadThread = (Thread) getLastNonConfigurationInstance(); if (downloadThread != null && downloadThread.isAlive()) { dialog = ProgressDialog.show(this, "Download", "downloading"); } } public void resetPicture(View view) { if (downloadBitmap != null) { downloadBitmap = null; } imageView.setImageResource(R.drawable.icon); } public void downloadPicture(View view) { dialog = ProgressDialog.show(this, "Download", "downloading"); downloadThread = new MyThread(); downloadThread.start(); } // save the thread @Override public Object onRetainNonConfigurationInstance() { return downloadThread; } // dismiss dialog if activity is destroyed @Override protected void onDestroy() { if (dialog != null && dialog.isShowing()) { dialog.dismiss(); dialog = null; } super.onDestroy(); } // Utiliy method to download image from the internet static private Bitmap downloadBitmap(String url) throws IOException { HttpUriRequest request = new HttpGet(url); HttpClient httpClient = new DefaultHttpClient(); HttpResponse response = httpClient.execute(request); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); byte[] bytes = EntityUtils.toByteArray(entity); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return bitmap; } else { throw new IOException("Download failed, HTTP response code " + statusCode + " - " + statusLine.getReasonPhrase()); } } static public class MyThread extends Thread { @Override public void run() { try { // Simulate a slow network try { new Thread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } downloadBitmap = downloadBitmap("http://www.devoxx.com/download/attachments/4751369/DV11"); // Updates the user interface handler.sendEmptyMessage(0); } catch (IOException e) { e.printStackTrace(); } finally { } } } }