前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android多线程的使用

Android多线程的使用

作者头像
指点
发布2019-01-18 17:22:51
9480
发布2019-01-18 17:22:51
举报

在很多编程语言中,线程都是一个重要的组成部分,多线程的支持可以给程序员更加灵活的程序功能实现代码编写方式,线程一般用于处理一些比较耗时的任务(下载文件、复制或者移动文件。。。)。那么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的简单用法就完成了。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017年02月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档