参考来源:
郭霖.第一行代码(Android)
https://www.gitbook.com/book/hzj163/android-thread/details
一.进程
进程是正在运行的程序的实例,操作系统中资源分配和保护的基本单位
二.线程
线程是进程中能够并发执行的实体,是进程的组成部分,也是处理器调度和分派的基本单位,一个进程可以同时包含多个线程,这些线程共享进程所获得的内存空间和资源,可以为完成某一项任务而协同工作,提高完成任务的速度和效率
三.创建线程
1. 继承Thread类
创建
class TestThread extends Thread{ @Override public void run() { /*要在多线程里运行的代码*/ } }
启动
TestThread myThread=new TestThread; myThread.start();//调用继承于Thread的start方法
2. 实现Runnable接口
使用继承的方式耦合性有点高,而且只能继承一个父类,而接口可以实现多个。所以我们可以使用接口的方式。
TestThread myTestThread=new TestThread();//生成一个Runnable接口实现对象 Thread ti=new Thread(myTestThread);//将这个对象作为构造函数参数传递给Thread对象 ti.start();
3. 使用匿名类
如过你不想麻烦的创建一个新类,可以使用匿名类的方法
new Thread(new Runnable() { @Override public void run() { // 处理具体的逻辑 } }).start();
四.线程池//TODO
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。
使用线程池减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。而且可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
五.线程同步(线程安全)
多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
在Java里面,通过synchronized关键字保证线程同步。
Java中的每一个对象都有一个内部锁,如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。
public synchronized void method(){ // method body }
线程不安全
不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
六.Android中的UI主线程
当应用启动,系统会创建一个主线程(main thread)。这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的UI组件发生交互。所以main thread也叫UI thread也即UI线程。
如果所有的工作都在UI线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞UI线程,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。所有这部份工作最后用异步线程来完成。
Andoid UI toolkit并不是线程安全的,所以你不能从非UI线程来操纵UI组件。你必须把所有的UI操作放在UI线程里,所有View和ViewGroup都只能在UI主线程中运行。如果View或者ViewGroup在次线程中运行,将会抛出【Only the original threadthat created a view hierarchy can touch its views】。
所以Android的单线程模型有两条原则:
1. 不要阻塞UI线程。
2. 不要在UI线程之外访问Android UI toolkit(主要是这两个包中的组件:android.widget and android.view)。
七.线程和线程之间的通信:Android消息机制
但UI需要根据线程的执行情况来改变时(比如连接上网络后对UI内容加载),由于次线程不能直接修改UI,,它通过消息机制告诉主线程中的UI什么时候该修改。
Android中消息机制由以下部分组成:
1. Message
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。
Message 使用 what字段携带String类型,使用 arg1和 arg2字段来携带一些整型数据,使用 obj字段携带一个 Object对象。
2. Handler
Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用 Handler的 sendMessage()方法,而发出的消息经过一系列地处理后,最终会传递到 Handler的 handleMessage()方法中。
食用方法
首先需要在主线程当中创建一个 Handler 对象,并重写handleMessage()方法。
然后当子线程中需要进行 UI 操作时,就创建一个 Message 对象,通过 Handler 将这条消息发送出去。
之后这条消息会被添加到 MessageQueue 的队列中等待被处理,而 Looper 则会一直尝试从 MessageQueue 中取出待处理消息,最后分发回 Handler的 handleMessage()方法中。
由于 Handler 是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行 UI 操作了。
//在主线程中,创建Handler对象,重写handleMessage方法 private Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case UPDATE_TEXT: // 在这里可以进行UI 操作 text.setText("Nice to meet you"); break; default: break; } } //在子线程中 new Thread(new Runnable() { @Override public void run() { Message message = new Message();//创建Message对象 message.what = UPDATE_TEXT; handler.sendMessage(message); // 将Message 对象发送出去 } }).start();
3. MessageQueue
MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个 MessageQueue对象。
4. Looper
Looper 是每个线程中的 MessageQueue 的管家,调用 Looper 的 loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue 中存在一条消息,就会将它取出,并传递到 Handler的 handleMessage()方法中。每个线程中也只会有一个 Looper 对象。
八.多线程异步
异步
方法A对方法B调用后,不用管B是否运行,A继续运行。
AsyncTask
借助 AsyncTask,即使你对异步消息处理机制完全不了解,也可以十分简单地从子线程切换到主线程。当然,AsyncTask 背后的实现原理也是基于异步消息处理机制的,只是 Android 帮我们做了很好的封装而已。
基本用法
AsyncTask 是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下。
1. Params
初始化参数类型
2. Progress
进度参数类型
3. Result
返回值参数类型。
因此,一个最简单的自定义 AsyncTask 就可以写成如下方式:
class DownloadTask extends AsyncTask<Void, Integer, Boolean> { …… }
一个栗子
class DownloadTask extends AsyncTask<Void, Integer, Boolean> { //这个方法会在后台任务开始执行之前调用,用于进行一些界面上的初始化操作 @Override protected void onPreExecute() { progressDialog.show(); // 显示进度对话框 } /*这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。 任务一旦完成就可以通过 return 语句来将任务的执行结果返回,如果 AsyncTask的第三个泛型参数指定的是 Void,就可以不返回任务执行结果。 注意,在这个方法中是不可以进行 UI操作,如果需要更新 UI元素,比如说反馈当前任务的执行进度,可以调用 publishProgress(Progress...)方法来完成。*/ @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; } /* 当在后台任务中调用了 publishProgress(Progress...)方法后,这个方法就会很快被调用, 方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对 UI 进行操作,利用参数中的数值就可以对界面元素进行相应地更新。*/ @Override protected void onProgressUpdate(Integer... values) { // 在这里更新下载进度 progressDialog.setMessage("Downloaded " + values[0] + "%"); } /* 当后台任务执行完毕并通过 return语句进行返回时,这个方法就很快会被调用。 返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些 UI 操作,比如 说提醒任务执行的结果,以及关闭掉进度条对话框等。*/ @Override protected void onPostExecute(Boolean result) { progressDialog.dismiss(); // 关闭进度对话框 // 在这里提示下载结果 if (result) { Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, " Download failed", Toast.LENGTH_SHORT).show(); } } }