专栏首页指点的专栏Android多线程的使用

Android多线程的使用

在很多编程语言中,线程都是一个重要的组成部分,多线程的支持可以给程序员更加灵活的程序功能实现代码编写方式,线程一般用于处理一些比较耗时的任务(下载文件、复制或者移动文件。。。)。那么Android作为一个最热门的移动操作系统,当然支持多线程编程(严格来说应该是java支持多线程编程,Android使用的是java编程语言)。下面来看一下怎么去使用Android多线程:

Android的线程和java的线程使用的都是相同的语法,如果你熟悉java,那么一定不会感到难,新建一个子线程:

Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
           // 这里加入我们要做的事情的代码
        }
    });
thread.start();

开启一个子线程的标准写法就是这样,在子线程的run方法里面我们可以加入我们想要做的事情的代码逻辑,但是值得注意的是:子线程里面是不可以更新UI的,如果要更新UI必须在UI线程(主线程)中完成。例:

import android.sax.StartElementListener;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private TextView textView = null;
    private Button button = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.start();
            }
        });
    }

    Thread thread = new Thread(new Runnable() {
       @Override
       public void run() {
            textView.setText("子线程更新UI测试");
        }
    });
}

布局文件就不贴了,比较简单,一个TextView控件和一个Button按钮控件 如果采用以上写法,程序运行单击按钮的时候会崩溃退出,如图:

看看LogCat打印的日志:

大致意思就是只有创建了这个View对象的才能够对这个View的UI进行操作(即只有UI线程才能更新UI)。那么我们怎么才能通过子线程来更新UI呢?直接更新肯定是不行的,Android为我们提供了一个类:Handler,这个类可以对子线程发出的消息进行处理,那么我们就能通过将Handler类对象定义在主线程中然后对子线程发来的消息进行处理(更新UI)来达到子线程更新UI的目的,我们仍然以上面那个例子来看,我们把MainActivity.java改一下:

import android.os.Handler;
import android.os.Message;
import android.sax.StartElementListener;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private static final int UPDATE_UI = 1;
    private TextView textView = null;
    private Button button = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.start();
            }
        });
    }

    /*
     * 新建一个Handler 对象,重写handleMessage方法用于处理 子线程发送过来的消息
     */
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            /*
             * 对发送过来的消息进行处理
             */
            switch (msg.what) {
                case UPDATE_UI:
                    textView.setText("子线程更新UI测试");
                    break;
            }
        }
    };

    Thread thread = new Thread(new Runnable() {
       @Override
       public void run() {
           /*
            * 新建一个 Message 对象做为消息发送给Handler对象去处理
            */
           Message message = new Message();
           message.what = UPDATE_UI;
           handler.sendMessage(message);
        }
    });
}

可以看到,我们在MainActivity.java里面加了一个新的全局对象:handler,这个对象就是用于处理子线程发送过来的消息的(在handleMessage方法里面)。而在子线程中,我们加了几行代码: 其实就是发送消息的代码,将消息发送到消息队列然后让Handler对象去处理。ok,再运行一下:

成功更新了UI,而且并没有报错。这就是典型的异步通信的例子:子线程执行的结果返回给主线程然后由主线程进行对应的处理。我们来看一下这里面涉及到的一些东西:

Message:其实就是一个普通的类,保存了一些数据,可以储存少量信息数据,在不同的线程中作为“通信者”的角色,可以用于不同的线程交换数据。

Handler:处理者的意思,它本身也用于在子线程中发送消息进入消息队列中,它也对对消息队列中通过它自己发送的Message对象取出并且进行一些处理(通过handleMessage方法),对于Handler对象来说,它不分线程,只要是它发送的消息,它都会接收到

消息队列(MessageQueue):储存通过Handler对象发送的消息,这些消息在被Handler对象取出处理之前会一直存在消息队列中,每个线程只会有一个MessageQueue对象,Android已经帮我们封装起来了,我们并不需要管它

Looper:管理每个线程中的MessageQueue,负责把消息队列中的数据取出,然后交给Handler对象去处理这条消息,每个线程也只会有一个Looper对象

整个过程:

画的不好,理解原理就行了。

其实为了方便我们在子线程更新UI操作,Android提供了一个更加好用的类:AsyncTask,下面来看一下这个类的用法: 首先,它是一个抽象类,我们必须继承它,并且要为它提供3个泛型参数,一般的写法:

public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {
    ....
}

之后必须重写里面的一个抽象方法:doInBackground方法,这个方法即为进行我们要执行的耗时操作的方法,我们在这个方法里面进行我们需要的耗时操作,其实,我们一般会重写里面的4个方法:

onPreExecute() : 这个方法会在任务(doInBackground方法)开始之前调用,用于一些初始化的操作

doInBackground(Void…) : 这个方法就是在后台进行的耗时操作的方法,里面的所有代码都会在Android新建的一个子线程中运行,并且这个方法不可以进行UI操作(这个方法是在子线程中执行的),我们可以调用publishProgress(Intger…)方法来调用专门的UI更新方法来进行UI的更新。这个方法的返回值会传递给onPostExecute方法用于收尾

onProgressUpdate(Interge…) : 这个方法里进行UI的更新,当在doInBackground方法中调用了publishProgress方法之后,就会调用这个方法来及时的进行UI更新

onPostExecute(Boolean result) : 这个方法用于收尾,当doInBackground方法执行完成之后就会调用这个方法,主要是对于操作进行判断是否成功…

可以看到,我们在继承AsyncTask传入的三个泛型参数:一个参数类型是doInBackGround方法的参数类型,第二个参数的参数类型是onProgressUpdate方法的参数类型,(这里传入Integer用于进度的UI更新),第三个参数的参数类型为onPostExecute方法的参数类型,并且也是doInBackground方法返回值类型(这里传入Boolean用于判断执行的结果),当然,我们可以根据自己的需要来传入对应的参数类型。那么一个基本的自定义AsyncTask就成了这样:

import android.os.AsyncTask;

/**
 * Created by Administrator on 2017/2/26.
 */

public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {

    @Override
    public void onPreExecute() {

    }

    @Override
    protected Boolean doInBackground(Void... params) {
        int percent = 0;
        try {
            while (true) {
                // 执行一些操作,
                publishProgress(percent); // 调用onProgressUpdate方法更新UI
                if (percent >= 100) {
                    return true;
                }
            }
        } catch(Exception e) {
            return false;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // Integer... progress 这种形式的写法相当于一个int类型的数组,其他同理
        // 在这里更新UI
    }

    @Override
    protected void onPostExecute(Boolean result) {
        // 这里做一些操作的结果提示
    }
}

OK,如果我们要调用这个类的对象,我们只需编写:

MyAsyncTask myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();

因为我们定义的MyAsyncTask中第一个参数为Void,所以这里调用execute方法不需要传入参数,但是如果换成别的类型那就需要了,这里根据具体需求来运用。 接下来仍然以上面那个例子,我们用AsyncTask来实现它: 在原来的工程基础上新建一个类MyAsyncTask.java:

import android.content.Context;
import android.os.AsyncTask;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

/**
 * Created by Administrator on 2017/2/26.
 */

public class MyAsyncTask extends AsyncTask<Void, Integer, Boolean> {

    private Context myContext = null;
    private TextView myTextView = null;
    private String string;

    public MyAsyncTask(Context context, TextView textView, String str) {
        myContext = context;
        myTextView = textView;
        string = str;
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        Log.i("ThreadTest", "MyAsyncTask thread: " + Thread.currentThread().getId()); // 打印出当前线程的 id 信息
        int percent = 100;
        try {
            publishProgress(percent);
            return true;
        } catch(Exception e) {
            return false;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        // Integer... progress 这种形式的写法相当于一个int类型的数组,其他同理
        // 在这里更新UI
        if(100 == progress[0]) {
            myTextView.setText(string);
        }
    }

    @Override
    protected void onPostExecute(Boolean result) {
        // 这里做一些操作的结果提示
        String str = "操作";
        if(result) {
            str += "完成";
        } else {
            str += "失败";
        }
        Toast.makeText(myContext, str, Toast.LENGTH_SHORT).show();
    }
}

我们还要对MainActivity.java进行小小的改动:

就是将按钮的点击事件成了执行MyAsyncTask对象中的方法,并且用一个LogCat信息打印出 MainActivity 所在的线程 Id,对于MyAsyncTask来说,基本的和上面的用法差不多,只不过用AsyncTask来进行这么简单的处理实在是有点大材小用,下面看结果:

开始运行的界面和上面没多大区别,点击按钮之后出现操作成功的提示并且TextView的文字也更新了。并且LogCat 中打印的信息中我们可以发现 MyAsyncTask 和 MainActivity 确实不是在同一个线程之中(MyAsyncTask 的 doInBackground 方法在子线程中执行), 那么一个AsyncTask的简单用法就完成了。

如果博客中有什么不正确的地方还请多多指点。 谢谢观看。。。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 多线程(4)---- 线程的同步(中)

    在前一篇文章: Java 多线程(3)— 线程的同步(上) 中,我们看了一下 Java 中的内存模型、Java 中的代码对应的字节码(包括如何生成 Java 代...

    指点
  • Java 多线程(8)---- 线程组和 ThreadLocal

    在上面文章中,我们从源码的角度上解析了一下线程池,并且从其 execute 方法开始把线程池中的相关执行流程过了一遍。那么接下来,我们来看一个新的关于线程的知识...

    指点
  • Java 多线程(1)---- 初识线程

    多线程想必大家都不会陌生。因为在日常使用和开发中,多线程的使用实在是太常见了。我们都知道,发明多线程的目的是为了更好的利用计算机的 CPU 资源。比如在一个进程...

    指点
  • 【原创】Java并发编程系列16 | 公平锁与非公平锁

    上一篇提到重入锁 ReentrantLock 支持两种锁,公平锁与非公平锁。那么这篇文章就来介绍一下公平锁与非公平锁。

    java进阶架构师
  • Java的重入锁ReentrantLock

    ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该...

    用户3467126
  • (三)一个服务器程序的架构介绍

    本文将介绍我曾经做过的一个项目的服务器架构和服务器编程的一些重要细节。 一、程序运行环境 操作系统:centos 7.0 编译器:gcc/g++ 4.8.3 c...

    范蠡
  • Java源码之ThreadPoolExecutor

    “ ThreadPoolExecutor类是线程池的基础,线程池的目的是为了减少了每个任务调用的开销,在拥有大量异步任务时可以增强的性能,并且还可以提供绑定和管...

    每天学Java
  • JAVA线程之ThreadLocal与栈封闭(六)

    PS:这次说了线程封闭的概念,其实很容易理解只要知道在ThreadLocal是JVM内部维护了一个Map就可以了。栈封闭没有纤细概述,跟局部变量是一个概念。

    IT故事会
  • 面试官:java基础怎么样?多线程一定会引发多线程安全问题吗?说说你的理解

    java基础对于学习安卓是很重要的,比如说线程,多线程。我们做安卓开发可能不太需要去研究高并发这些高深的问题,但是基础的知识要掌握,特别是要理解为什么会这样?以...

    Android技术干货分享
  • 分享一款JVM线程堆栈在线分析工具

    JVM大家可能都知道是个什么玩意-Java虚拟机,但是到底是个什么鬼?相信即使工作3-5年的程序员可能也不大了解。

    小柒2012

扫码关注云+社区

领取腾讯云代金券