Android 多线程

在学习java的时候,你们一定对多线程这个词不陌生,而android多线程跟java相比并没有什么特殊的地方,基本都是相同的语法。
而线程的使用就是在执行一些耗时的操作,比如发起一条网络请求时,考虑到网速等原因,服务器不一定会立刻响应我们的请求,如果不把这些操作放在子线程中,会导致主线程被阻塞,从而影响用户对软件的正常使用。

线程的基本用法

下面先看一下android中,三种启用线程的方法

1.只需要新建一个类继承自Thread,然后重写父类的run()方法来实现一个线程:

public class MyThread extends Thread {  

    //继承Thread类,并改写其run方法        
    private final static String TAG = "My Thread ===> ";      
    public void run(){  
        //具体的实现
        Log.d(TAG, "run");  
        for(int i = 0; i<100; i++)  
        {  
            Log.e(TAG, Thread.currentThread().getName() + "i =  " + i);  
        }  
    }  
} 

启动:

new MyThread().start();

2.创建一个实现Runnable接口的对象

public class MyRunnable implements Runnable{  
    private final static String TAG = "My Runnable ===> ";  

    @Override  
    public void run() {  
         //具体的逻辑
        // TODO Auto-generated method stub  
        Log.d(TAG, "run");  
        for(int i = 0; i<1000; i++)  
        {  
            Log.e(TAG, Thread.currentThread().getName() + "i =  " + i);  
        }  
    }  
} 

启动:

new Thread(new MyRunnable()).start(); 

可以看到,Thread的构造函数接受一个Runnable参数,而我们new出的MyRunnable正是一个实现了Runnable接口的对象,所以可以直接将他传入到Thread的构造函数里。接着调用Threadstart()方法,run()方法中的代码就会在子线程中运行了。

3.另外一种启用方式:匿名类(常见)

btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                           //具体逻辑
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });

#异步消息处理机制

1.尝试在子线程中更新UI

看代码

和其他许多GUI库一样,Android的UI也是线程不安全的。也就是说,如果想要更新应用程序里的UI元素,则必须在主线程中进行,否则就会出现异常。
上面这个例子就恰好证明了android确实不能在子线程中进行UI操作的,但是有些时候,我们必须在子线程中执行一些耗时任务,然后再根据执行结果来更新UI,这时候就要用到异步消息处理机制。

2.Message,Handler,MessageQueue和Looper

看代码

Looper,Handler,message三者关系图

3.进一步探究Handler Looper机制

要分析Handler Looper机制,自然想到去看Handler类和Looper类的源码(分别位于Handler.java和Looper.java两个文件中)。简单阅读两个类的描述后,在Looper类的描述中能找到以下一段示例代码。

<p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
*  class LooperThread extends Thread {
*      public Handler mHandler;
*
*      public void run() {
*          Looper.prepare();
*
*          mHandler = new Handler() {
*              public void handleMessage(Message msg) {
*                  // process incoming messages here
*              }
*          };
*
*          Looper.loop();
*      }
*  }</pre>
*/

这段代码给出了Handler Looper机制实现进程间通信的三大基本步骤,包括Looper的两个函数prepare()和loop(),以及Handler的handleMessage函数。
Handler Looper关系总结
由图可见,基于Handler Looper机制传递消息主要包括以下几个步骤。

(1)目标线程调用Looper.prepare()创建Looper对象和消息队列。

(2)目标线程通过new Handler()创建handler对象,将Handler,Looper,消息队列三者关联起来。并覆盖其handleMessage函数。

(3)目标线程调用Looper.loop()监听消息队列。

(4)消息源线程调用Handler.sendMessage发送消息。

(5)消息源线程调用MessageQueue.enqueueMessage将待发消息插入消息队列。

(6)目标线程的loop()检测到消息队列有消息插入,将其取出。

(7)目标线程将取出的消息通过Handler.dispatchMessage派发给Handler.handleMessage进行消息处理。

4.使用AsyncTask

android提供了一个很好的工具,就比如AsyncTask。借助AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单的从子线程切换到主线程。
首先来看一下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

1. Params
在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。
2. Progress
后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。
3. Result
当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。

因此,一个最简单的自定义AsyncTask就可以写成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  
    ……  
}

这里我们把AsyncTask的第一个泛型参数指定为Void,表示在执行AsyncTask的时候不需要传入参数给后台任务。第二个泛型参数指定为Integer,表示使用整型数据来作为进度显示单位。第三个泛型参数指定为Boolean,则表示使用布尔型数据来反馈执行结果。

当然,目前我们自定义的DownloadTask还是一个空任务,并不能进行任何实际的操作,我们还需要去重写AsyncTask中的几个方法才能完成对任务的定制。经常需要去重写的方法有以下四个:

1. onPreExecute()
这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。
2. doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。
3. onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
4. onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。

因此,一个比较完整的自定义AsyncTask就可以写成如下方式:

class DownloadTask extends AsyncTask<Void, Integer, Boolean> {  

    @Override  
    protected void onPreExecute() {  
        progressDialog.show();  
    }  

    @Override  
    protected Boolean doInBackground(Void... params) {  
        try {  
            while (true) {  
                int downloadPercent = doDownload();  
                publishProgress(downloadPercent);  
                if (downloadPercent >= 100) {  
                    break;  
                }  
            }  
        } catch (Exception e) {  
            return false;  
        }  
        return true;  
    }  

    @Override  
    protected void onProgressUpdate(Integer... values) {  
        progressDialog.setMessage("当前下载进度:" + values[0] + "%");  
    }  

    @Override  
    protected void onPostExecute(Boolean result) {  
        progressDialog.dismiss();  
        if (result) {  
            Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();  
        } else {  
            Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();  
        }  
    }  
}  

这里我们模拟了一个下载任务,在doInBackground()方法中去执行具体的下载逻辑,在onProgressUpdate()方法中显示当前的下载进度,在onPostExecute()方法中来提示任务的执行结果。如果想要启动这个任务,只需要简单地调用以下代码即可:

new DownloadTask().execute(); 

如果想深入学习还是推荐研究一下源码。

#浅谈服务
Service是Android中的四大组件之一,和windows中的服务是类似,服务一般没有用户操作界面,它运行于系统中不容易被用户发觉,可以使用它开发如监控之类的程序Service,手机中有的程序的更新,服务的推送。Android系统中,Service与Activity类似,都需要AndroidManifest.xml文件中配置,而且生命周期有点类似。Service不像Activity在前台运行,而是默默的在后台默默的工作

浅谈Android组件之Service

大胖倪的慢灵魂 wechat
感觉有用打赏一个呗~