Android线程总结

#1. 为什么使用线程

android 的多线程实际上就是java的多线程。android的UI线程又称为主线程。

我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程。但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决。android 的多线程实际上就是java的多线程。android的UI线程又称为主线程。

我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程。但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决。

2. Thread 和 Runnable

在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口.

Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限。所以,通常会实现Runnable接口方式。

Thread才是一个线程,而Runnable可以理解为一个任务。这个任务只是一个接口。具体的任务执行是在 run()方法执行,那么就是把一个Runnable任务放到线程里面。当调用thread.start() 的时候,系统新开一个线程去执行,这个runnable任务是在多线程执行的。是在新开的线程执行的。

注意以下两种情况不能实现在工作线程执行,还是在UI线程执行。

  • thread.run() ,这样子实际上只是在UI线程执行了Runnable,并没有实现多线程。系统也没有新开一个线程。
  • new Runnable().run(),那么实际上是直接在UI线程执行了这个任务没有进行一个多线程的操作。

Thread使用的例子

Boolean isRun = true;
public void start_clicked(View v){
    MyTread thread = new MyTread();
    thread.start();
}
public void stop_clicked(View v){
    isRun = false;
}

public  class MyTread extends  Thread{

    @Override
    public  void run() {
        int counter = 0;
        while (isRun){
            try {
                Log.v("thread sample", String.valueOf(counter));
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter++;
        }
    }
}
复制代码

Runnable使用的例子

Boolean isRun = true;
public void start_clicked(View v){
    MyTask task = new MyTask();
    Thread thread = new Thread(task,"my thread");
    thread.start();
}
public void stop_clicked(View v){
    isRun = false;
}
public class MyTask implements Runnable {

    @Override
    public void run() {
        int counter = 0;
        while (isRun){
            try {
                Log.v("thread sample", String.valueOf(counter));
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counter++;
        }
    }
}
复制代码

3. Handler

一、Handler的定义:

Handler主要接收子线程发送的数据, 并用此数据配合主线程更新UI,用来跟UI主线程交互用。比如可以用handler发送一个message,然后在handler的线程中来接收、处理该消息,以避免直接在UI主线程中处理事务导致影响UI主线程的其他处理工作,Android提供了Handler作为主线程和子线程的纽带;也可以将handler对象传给其他进程,以便在其他进程中通过handler给你发送事件;还可以通过handler的延时发送message,可以延时处理一些事务的处理。

通常情况下,当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件,进行事件分发。如果此时需要一个耗时的操作,例如:联网读取数据,或者读取本地较大的一个文件的时候,你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象,如果5秒钟还没有完成的话,会收到Android系统的一个错误提示"强制关闭".

这个时候我们需要把这些耗时的操作,放在一个子线程中,因为子线程涉及到UI更新,但是当子线程中有涉及到操作UI的操作时,就会对主线程产生危险,也就是说,更新UI只能在主线程中更新,在子线程中操作是危险的. 这个时候,Handler就出现了来解决这个复杂的问题,由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据,这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象,(里面包含数据), 把这些消息放入主线程队列中,配合主线程进行更新UI。

二、Handler一些特点

handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程), 也就是说Handler对象初始化后,就默认与对它初始化的进程的消息队列绑定,因此可以利用Handler所包含的消息队列,制定一些操作的顺序。

三、Handler中分发消息的一些方法

post(Runnable)

postAtTime(Runnable,long)

postDelayed(Runnable long)
复制代码

post类方法允许你排列一个Runnable对象到线程队列中

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message,long)

sendMessageDelayed(Message,long)
复制代码

sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新.

四、应用实例:

1,sendMessage

一、用于接受子线程发送的数据, 并用此数据配合主线程更新UI

在Android中,对于UI的操作通常需要放在主线程中进行操作。如果在子线程中有关于UI的操作,那么就需要把数据消息作为一个Message对象发送到消息队列中,然后,用Handler中的handlerMessge方法处理传过来的数据信息,并操作UI。类sendMessage(Message msg)方法实现发送消息的操作。 在初始化Handler对象时重写的handleMessage方法来接收Messgae并进行相关操作。

public void start_clicked(View v){

    new Thread(new Runnable() {
        @Override
        public void run() {
            int counter = 0;
            while (isRun){
                try {
                    Log.v("thread sample", String.valueOf(counter));

                    // send message to UI thread
                    Message msg = mHandler.obtainMessage();
                    Bundle b = new Bundle();
                    b.putInt("counter", counter);
                    msg.setData(b);
                    
                    
                    mHandler.sendMessage(msg);

                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                counter++;
            }
        }
    }).start();
}
public void stop_clicked(View v){
    isRun = false;
}

Handler mHandler = new Handler(){
    // handler 在UI线程创建,所以可以更新UI
    public void handleMessage(android.os.Message msg) {
        super.handleMessage(msg);
        Bundle b = msg.getData();
        int c = b.getInt("counter");
        mInfo.setText(String.valueOf(c));
    }
};
复制代码

有一点需要注意,在获得message实例有两种办法

//第一种
Message msg = new Message();
msg.what = xxx;
msg.arg1  = xxx;
msg.arg2  = xxx;
handler.sendMessage(msg);

//第一种
Message msg = handler.obtainMessage();
msg.what = xxx;
msg.arg1  = xxx;
msg.arg2  = xxx;
msg.obj    = xxx;
msg.sendToTarget();
复制代码

尽量使用第二种方法 原因:

  • Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
  • creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
  • If you don't want that facility, just call Message.obtain() instead.

这里我们的Message 已经不是 自己创建的了,而是从MessagePool 拿的,省去了创建对象申请内存的开销。。。。。

二、传递Message。用户UI线程向工作线程发送消息或者线程之间通信

Handler mThreadHandler;
public void start_clicked(View v){ //启动线程
new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();// 创建该线程的Looper对象,用于接收消息,在非主线程中是没有looper的所以在创建handler前一定要使用prepare()创建一个Looper
        mThreadHandler = new Handler() { // mThreadHandler 在工作线程创建,不可以更新UI
            public void handleMessage(Message msg) {

                // 耗时操作
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 结束完耗时操作 通知UI线程更新UI
                Message msgToUI = mHandler.obtainMessage();
                Bundle b = new Bundle();
                b.putInt("counter", 5000);
                msgToUI.setData(b);
                mHandler.sendMessage(msgToUI);
            }
        };
        Looper.loop();//建立一个消息循环,该线程不会退出
		}
	}).start();
}
public void stop_clicked(View v){
	mThreadHandler.getLooper().quit(); //使用完成要停止looper,释放线程
}

public void sendMessageToThread_clicked(View v) {
	// UI线程向工作线程通过 mThreadHandler向工作线程发送sendMessage
	mThreadHandler.sendEmptyMessage(0);
}

Handler mHandler = new Handler(){
	// handler 在UI线程创建,所以可以更新UI
	public void handleMessage(android.os.Message msg) {
		//super.handleMessage(msg);

		Bundle b = msg.getData();
		int c = b.getInt("counter");
		mInfo.setText(String.valueOf(c));
	}
};
复制代码

Looper类说明

  Looper 类用来为一个线程跑一个消息循环。

  线程在默认情况下是没有消息循环与之关联的,Thread类在run()方法中的内容执行完之后就退出了,即线程做完自己的工作之后就结束了,没有循环的概念。

  调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用loop() 方法使之处理信息,直到循环结束。

Looper:

public void startThread_clicked(View v){

    //简写Runnable方式
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();// 创建该线程的Looper对象,用于接收消息,在非主线程中是没有looper的所以在创建handler前一定要使用prepare()创建一个Looper
            mThreadHandler = new Handler() { // mThreadHandler 在工作线程创建
                public void handleMessage(Message msg) {

                    // 耗时操作
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 结束完耗时操作 通知UI线程更新UI
                    Message msgToUI = mHandler.obtainMessage();
                    Bundle b = new Bundle();
                    b.putInt("counter", 5000);
                    msgToUI.setData(b);
                    mHandler.sendMessage(msgToUI);
                }
            };
            Looper.loop();//建立一个消息循环,该线程不会退出
        }
    }).start();
}

public void sendMessageToThread_clicked(View v) {
    // UI线程向工作线程通过 mThreadHandler向工作线程发送sendMessage
    mThreadHandler.sendEmptyMessage(0);
}
public void stopThread_clicked(View v){
    mThreadHandler.getLooper().quit(); //使用完成要停到looper,释放线程
}
复制代码

HandlerThread:

我们实现Looper有没有更加简单的方法呢?当然有,这就是我们的HandlerThread。我们来看下Android对HandlerThread的描述:

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

上面的工作线程代码可以简化成

HandlerThread handlerThread = new HandlerThread("xxx");
    handlerThread.start(); //创建HandlerThread后一定要记得start()
    mThreadHandler = new Handler(handlerThread.getLooper()){
        @Override
        public void handleMessage(Message msg) {

             //耗时操作
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 结束完耗时操作 通知UI线程更新UI
            Message msgToUI = mHandler.obtainMessage();
            Bundle b = new Bundle();
            b.putInt("counter", 5000);
            msgToUI.setData(b);
            mHandler.sendMessage(msgToUI);

            super.handleMessage(msg);
        }
    };
public void sendMessageToThread_clicked(View v) {
    // UI线程向工作线程通过 mThreadHandler向工作线程发送sendMessage
    mThreadHandler.sendEmptyMessage(0);
}
public void stopThread_clicked(View v){
    mThreadHandler.getLooper().quit(); //使用完成要停到looper,释放线程
}
复制代码

2,Post

除了上述的使用Handler发送以及处理消息外,handler还有一个作用就是处理传递给它的action对象,具体使用步骤示例:

1、在主线程中定义Handler对象

2、构造一个runnable对象,为该对象实现runnable方法。

3、在子线程中使用Handler对象post(runnable)对象.

handler.post这个函数的作用是把Runnable里面的run方法里面的这段代码发送到消息队列中,等待运行。 如果handler是以UI线程消息队列为参数构造的,那么是把run里面的代码发送到UI线程中,等待UI线程运行这段代码。 如果handler是以子线程线程消息队列为参数构造的,那么是把run里面的代码发送到子线程中,等待子线程运行这段代码。

//延迟5秒 向UI线程消息队列发送一条任务(Runnable)
 public void start_clicked(View v){
		Handler h = new Handler(this.getMainLooper());
		h.postDelayed(new Runnable() {
			@Override
			public void run() {
				mInfo.setText("Come from postDelayed");
			}
		}, 5000);
}
复制代码

#4. synchronized

一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

 public void start_clicked(View v){

		synchronizedTask task = new synchronizedTask();
		new Thread(task,"A").start();
		new Thread(task,"B").start();
	}

public class synchronizedTask implements Runnable {

	@Override
	public void run() {
		synchronized (this) {
			for (int i = 0; i < 5; i++) {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName());
			}
		}
	}
}

不加synchronized (this) 输出为
System.out﹕ A
System.out﹕ B
System.out﹕ A
System.out﹕ B
System.out﹕ A
System.out﹕ B
System.out﹕ A
System.out﹕ B
System.out﹕ A
System.out﹕ B



synchronized (this) 输出为
System.out﹕ A
System.out﹕ A
System.out﹕ A
System.out﹕ A
System.out﹕ A
System.out﹕ B
System.out﹕ B
System.out﹕ B
System.out﹕ B
System.out﹕ B
复制代码

wait、notify、notifyall、synchronized的使用机制:

synchronized(obj) {
		while(!condition) {
		obj.wait();
	}
	obj.doSomething();
}
复制代码

当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait() , 放弃对象锁.

之后在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

synchronized(obj) {
	condition = true;
	obj.notify();
}
复制代码

需要注意的概念是:

调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {…} 代码段内。

调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。

当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。

如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。

obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。

当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行

#5. AsyncTask 在Android中实现异步任务机制有两种方式,Handler和AsyncTask。

Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。关于Handler的相关知识,前面也有所介绍。

为了简化操作,Android1.5提供了工具类android.os.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。

先来看看AsyncTask的定义:

public abstract class AsyncTask<Params, Progress, Result> {  
复制代码

三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。

一个异步任务的执行一般包括以下几个步骤:

1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。

2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。

3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。

4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。

5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。

在使用的时候,有几点需要格外注意:

1.异步任务的实例必须在UI线程中创建。

2.execute(Params... params)方法必须在UI线程中调用。

3.不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。

4.不能在doInBackground(Params... params)中更改UI组件的信息。

5.一个任务实例只能执行一次,如果执行第二次将会抛出异常。

private TextView mAsyncTaskInfo;
private ProgressBar mProgress;
private MyAsyncTask mTask;
	
public void asyncTask_clicked(View v){
	mTask = new MyAsyncTask();
	mTask.execute("demo");
}

public void cancel_asyncTask_clicked(View v){
	mTask.cancel(true);
}

public class MyAsyncTask extends AsyncTask<String, Integer, String>{

	//在execute(Params... params)被调用后立即执行
	//onPreExecute方法用于在执行后台任务前做一些UI操作
	@Override
	protected void onPreExecute() {
		mAsyncTaskInfo.setText("loading...");
	}

	//onPreExecute()完成后立即执行
	//doInBackground方法内部执行后台任务,不可在此方法内修改UI
	@Override
	protected String doInBackground(String... params) {

		try {
			if(params[0].equals("demo")){
				Thread.sleep(2000);

				while (counter < 100){
					publishProgress(counter);

					Thread.sleep(50);
					counter++;
				}
				return  "finished : " + String.valueOf(counter);
			}

		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return null;
	}

	//在调用publishProgress(Progress... values)时,此方法被执行
	//onProgressUpdate方法用于更新进度信息
	@Override
	protected void onProgressUpdate(Integer... progresses) {
		mProgress.setProgress(progresses[0]);
		mAsyncTaskInfo.setText("loading..." + progresses[0] + "%");
	}

	//onPostExecute方法用于在执行完后台任务后更新UI,显示结果
	@Override
	protected void onPostExecute(String result) {
		mAsyncTaskInfo.setText(result);
	}

	//onCancelled方法用于在取消执行中的任务时更改UI
	@Override
	protected void onCancelled() {
		mAsyncTaskInfo.setText("cancelled");
		mProgress.setProgress(0);
	}
}
复制代码

完!

样例代码 git 地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值