知识养成了思想,思想同时又在融化知识。这篇文章主要讲述Android 线程交互相关的知识,希望能为你提供帮助。
在android开发过程中,耗时操作是不允许写在主线程(UI线程)中的,以免由于等待时间过长而发生ANR。所以耗时操作需要创建子线程来完成,然而往往这些操作都需要与主线程进行通讯交互(例如更新主线程的UI),但android规定除了UI线程外,其他线程都不可以对UI控件进行访问或操控,所以我们需要通过一些方法来实现这些功能。
1. Handler:
handler是android中专门用来在线程之间传递信息类的工具。API参考:https://developer.android.google.cn/reference/android/os/Handler
1、在B线程中调用Looper.prepare和Looper.loop。(主线程不需要)
2、编写Handler类,重写其中的handleMessage方法。
3、创建Handler类的实例,并绑定looper
4、调用handler的sentMessage方法发送消息。
- 子线程更新主线程(UI)
Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 1: //do something,refresh UI; break; default: break; } }};
然后开启一个子线程,在子线程里直接使用Handler发送消息:
new Thread() { public void run() { Message message = new Message();
message.what = 1; message.obj = "子线程发送的消息Hi~Hi"; mHandler .sendMessage(message);
}; }.start();
- 子线程之间交互
private final String TAG = "ThreadActivity";
// 子线程Handler
private Handler mSubHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread1);
new MyThread().start();
createThread();
}
/**
* 创建子线程,用于发送消息
*/
private void createThread() {
new Thread() {
@Override
public void run() {
int count = 0;
while (count < 10) {
Message msg = new Message();
msg.obj = "子线程计时器:" + count;
msg.what = 1;
// 使用子线程Handler发送消息
mSubHandler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}.start();
}
/**
* 用于接收子线程发送过来的消息
*/
class MyThread extends Thread {
@Override
public void run() {
Looper.prepare();
mSubHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i(TAG, (String) msg.obj);
break;
}
}
};
Looper.loop();
}
}
}
2. HandlerThread:HandlerThread是一个包含Looper的Thread,我们可以直接使用这个 Looper 创建 Handler。API参考:https://developer.android.google.cn/reference/android/os/HandlerThread
【Android 线程交互】HandlerThread适用于单线程+异步队列模型场景,相对Handler + Thread简洁。
// 也可实现run方法
HandlerThread mHandlerThread = new HandlerThread("HandlerThread_Test"); mHandlerThread.start(); Handler mThreadHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: Log.i(TAG, "threadName--" + Thread.currentThread().getName() + (String) msg.obj); break; } } }; // 发送消息至HanderThread mThreadHandler.sendMessage(msg);
3. runOnUiThreadActivity 里面的runOnUiThread( Runnable )方法便子线程更新UI更为简洁。另外还有View.Post(Runnable)和View.PostDelayed(Runnabe,long)方法,用法与runOnUiThread基本相同。
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { // 执行UI操作 Toast.makeText(MainActivity.this, "Test", Toast.LENGTH_SHORT).show(); } }); } }).start();
4. AsyncTaskAPI参考:https://developer.android.google.cn/reference/android/os/AsyncTask
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类。它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。AsyncTasks应该用于短操作(最多几秒)。如果您需要保持线程长时间运行,强烈建议您使用java.util.concurrent包提供的各种API,例如Executor,ThreadPoolExecutor和FutureTask。 异步任务由3个泛型类型定义:Params,Progress和Result,以及4个步骤:onPreExecute,doInBackground,onProgressUpdate和onPostExecute。
使用AsyncTask时需要继承AsyncTask类并必须实现doInBackground(params...)方法,大多数情况下学需要实现onPostExecute(Result)方法。
private class DownloadFilesTask extends AsyncTask< URL, Integer, Long> { protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; }protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); }protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
创建成功后执行任务非常简单:
new DownloadFilesTask().execute(url1, url2, url3); // Params
AsyncTask的泛型类型:
- Params:在执行时传递给任务的参数类型;
- Progress:异步任务执行过程中,返回执行进度值的类型;
- Result:后台任务执行结果类型。
rivate class MyTask extends AsyncTask< Void, Void, Void> { ... }
当执行一个异步任务时,需要经历4个步骤:
- onPreExecute():在异步任务执行前,在UI主线和中调用。此步骤通常用于设置任务,例如在用记界面中显示进度条。
- doInBackground(Params...):onPreExecute()执行完后,立即在后台线程中调用此方法。此步骤用于执行运行时间可能较长的后台任务,参数由execute(params...)传入。通过此步骤得到结果并返回到最后一步。在计算过程中,可通过调用publishProgress(Progress...)方法,并通过onProgressUpdate(Progress...)更新UI显示任务执行进度。
- onProgressUpdate(Progress...):当publishProgress(Progress...)方法执行后,此步骤在UI主线程中被调用,并更新UI当前任务进度。
- onPostExecute(Result):在后台线程计算完成后在UI线程上调用。 后台线程计算的结果作为参数传递给此步骤。
使用AsyncTask还需要注意以下问题:
- 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。
- execute(Params...) 必须在UI线程上执行。
- 不要手动调用onPreExecute(), onPostExecute(Result), doInBackground(Params...), onProgressUpdate(Progress...)方法。
- 该任务只能执行一次(如果尝试执行第二次执行,则会引发异常)。
- 不能在doInBackground(Params... params)中更改UI组件的信息。
- AsyncTask不与任何组件绑定生命周期,所以在Activity/或者Fragment中创建执行AsyncTask时,最好在Activity/Fragment的onDestory()调用 cancel(boolean);
- 如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
- 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask(非静态的内部类)会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。
- THREAD_POOL_EXECUTOR, 异步线程池。
- SERIAL_EXECUTOR,同步线程池。
一个简单的AsyncTask例子:自定义AsyncTask:
class TestAsyncTask extends AsyncTask< Integer, Integer, Integer> {@Override protected Integer doInBackground(Integer... integers) { int count = integers[0]; int len = integers[1]; while (count < len) { int pro = 100 * count / len; Log.i(TAG, "----------" + pro + ":" + count + ":" + len); publishProgress(pro); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count++; } return count; }@Override protected void onProgressUpdate(Integer... values) { mText.setText("Progress:" + values[0] + "%"); }@Override protected void onPostExecute(Integer integer) { mText.setText("Finished:" + integer + "%"); } }
创建TestAsyncTask实例:
mAsyncTask.execute(0, 100);
推荐阅读
- Java内存模型之happens-before
- 如何在Unity中开发Leap Motion桌面版(Non-VR)APP
- app.honeycomb.Shell$HomeActivity failed to start
- 消灭选择困难APP
- test_app.sh
- android-读取MediaProvider
- 关于进程线程应用程序域(AppDomain)
- 聊聊Java happens-before原则
- App功能测试的注意点