fred-ye / summary

my blog
43 stars 9 forks source link

[Android] 线程,线程池,Android消息机制 #58

Open fred-ye opened 8 years ago

fred-ye commented 8 years ago

[写在前面]以下内容是看《Android 开发艺术探索》时做的笔记,其中也会有自己的一些思考和代码验证。

主线程和子线程

主线程: Android主的主线程也是UI线程,用来运行四大组件和处理它们与用户之间的操作。 子线程: 执行耗时的操作,如网络请求或IO操作。Android 3.0开始就要求所有的网络请求都必须在子线程中,否则会报NetworkOnMainThreadException

线程形态

AsyncTask

在后台执行耗时作务,并将执行的进度和最终结果传递给主线程,并在主线程中更新UI

public abstract class AsyncTask<Params, Progress, Result>

4个核心方法如下:

需要注意的

继承自Thread, 是一种可以使用Handler的Thread。其内部会有一个Looper,其run方法开启Looper的消息循环. 它的使用:

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "thread start");
    }
});
HandlerThread handlerThread = new HandlerThread("Handler Thread");
Handler handler = new Handler(handlerThread.getLooper());
handler.post(t);

IntentService

IntentService继承自Service并且是一个抽象类。IntentService可以用于执行后台耗时的作务,当作务执行后会自动停止,由于它是服务的原因,因此它的优先级比单纯的线程要高很多,也就意味着不容易被系统杀死。IntentService封装了HandlerHandlerThread

一个Demo

button2.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_1"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_2"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_3"));
            startService(new Intent(LearnThreadActivity.this, MyIntentService.class).putExtra("action", "ACTION_4"));
        }
    }
});
public class MyIntentService extends IntentService {
    private final String TAG = "MyIntentService";

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String action = intent.getStringExtra("action");
        Log.i(TAG, "task:" + action + "   Thread:" + Thread.currentThread());
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.i(TAG, "task:" + action + "   handled");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "service destory");
    }
}

执行结果

10-22 09:51:10.540 15243-15470/com.fred I/MyIntentService: task:ACTION_1   Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:11.542 15243-15470/com.fred I/MyIntentService: task:ACTION_1   handled
10-22 09:51:11.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2   Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:12.544 15243-15470/com.fred I/MyIntentService: task:ACTION_2   handled
10-22 09:51:12.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3   Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:13.545 15243-15470/com.fred I/MyIntentService: task:ACTION_3   handled
10-22 09:51:13.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4   Thread:Thread[IntentService[MyIntentService],5,main]
10-22 09:51:14.546 15243-15470/com.fred I/MyIntentService: task:ACTION_4   handled
10-22 09:51:14.546 15243-15243/com.fred I/MyIntentService: service destory

由此可以看出IntentService中的任务是依次排队执行。

线程池

为什么使用线程池:

  1. 重用线程,避免因为线程的创建和销毁所带来的性能开销
  2. 有效控制线程池的最大并发数
  3. 对线程进行管理,提供定时执行和间隔执行等功能

    ThreadPoolExecutor

提供了一系列的参数来配置线程池,从而可以获得不同类型的线程池。当我们在创建一个线程池时,可以采用Executors.newCachedThreadPool()Executors.newFixedThreadPool(5)等方式来创建不同的线程池,但是这些方法最终都会调用ThreadPoolExecutor来进行配置。 ThreadPoolExecutor常用的构造方法

 /**
 * Creates a new {@code ThreadPoolExecutor} with the given initial
 * parameters.
 *
 * @param corePoolSize the number of threads to keep in the pool, even
 *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
 * @param maximumPoolSize the maximum number of threads to allow in the
 *        pool
 * @param keepAliveTime when the number of threads is greater than
 *        the core, this is the maximum time that excess idle threads
 *        will wait for new tasks before terminating.
 * @param unit the time unit for the {@code keepAliveTime} argument
 * @param workQueue the queue to use for holding tasks before they are
 *        executed.  This queue will hold only the {@code Runnable}
 *        tasks submitted by the {@code execute} method.
 * @param threadFactory the factory to use when the executor
 *        creates a new thread
 * @param handler the handler to use when execution is blocked
 *        because the thread bounds and queue capacities are reached
 * @throws IllegalArgumentException if one of the following holds:<br>
 *         {@code corePoolSize < 0}<br>
 *         {@code keepAliveTime < 0}<br>
 *         {@code maximumPoolSize <= 0}<br>
 *         {@code maximumPoolSize < corePoolSize}
 * @throws NullPointerException if {@code workQueue}
 *         or {@code threadFactory} or {@code handler} is null
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

关于参数,注释中已经有了,特别说明一点。第一个参数corePoolSize指的是线程池中核心线程的数量。第二个参数maximumPoolSize是线程池中最大的线程数。

ThreadPoolExecutor在执行时的特点:

  1. 如果线程池中的线程数量没有达到核心线程数量,那么会直接启动一个核心线程来执行任务
  2. 如果线程池中的线程数量已达到或超过核心线程的数量,那么任务就会被插入到任务队列中排队等待执行。 关于以上两点,我们可以用代码来验证:
class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        super.run();
        Log.i(getName(), "[thread start]");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
 button3.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            executor.execute(new MyThread("thread1"));
            executor.execute(new MyThread("thread2"));
            executor.execute(new MyThread("thread3"));
            executor.execute(new MyThread("thread4"));
            executor.execute(new MyThread("thread5"));
        }
    }
});

执行结果:

10-22 10:31:56.350 20071-23401/com.fred I/thread2: [thread start]
10-22 10:31:56.351 20071-23400/com.fred I/thread1: [thread start]
10-22 10:31:57.351 20071-23401/com.fred I/thread3: [thread start]
10-22 10:31:57.352 20071-23400/com.fred I/thread4: [thread start]
10-22 10:31:58.352 20071-23401/com.fred I/thread5: [thread start]

3.如果步骤2中无法将任务插入到队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,会立即启动一个非核心线程来执行任务。如果达到了,就拒绝执行此任务[这一句没有理解?]。

线程池的分类

FixedThreadPool

一种线程数量固定的线程池,当里面的线程处于空闲状态时,它们也不会被回收,除非线程池关闭了。它只有核心线程

CachedThreadPool

一种线程数量不定的线程池, 它只有非核心线程,它的空闲线程有超时机制,超时时间为60s, 适合执行大量耗时较少的任务,当整个线程池都处于闲置状态时,线程池中的线程都会因超时而被停止,此时就几乎不占系统资源。

ScheduledThreadPool

核心线程固定,非核心线程没有限制。用于执行定时任务或具有周期性的重复任务。 如

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// 2 秒后执行
scheduledExecutorService.schedule(new MyThread("SESThread"), 2, TimeUnit.SECONDS);
//10 秒后以间隔10s的频率重复执行
scheduledExecutorService.scheduleAtFixedRate(new MyThread("SESThread"), 10, 1000, TimeUnit.MILLISECONDS);

SingleThreadExecutor

只有一个线程的线程池,可以统一外部的任务到一个线程中,任务间不需要处理线程同步的问题。

消息机制

Android UI操作的单线程模型: 之所以对UI操作采用单线程模型,是因为如是不这样做,对UI控件的操作肯定要加锁的机制,如果这样,就使得操作UI更加的复杂,同时也降低UI的访问效率,因为锁机制会有线程的阻塞。

Android 中的消息机制,实际上就是指的Handler的运行机制。Handler的运行离不开LooperMessageQueue, Looper会不断的去查找MessageQueue中有没有新的消息,如果有就会去处理。

Handler的使用

场景:在子线程中访问网络,拿到数据后更新UI。

sendMessage

  • 为某个Activity中的控件绑定事件
final MyHandler handler = new MyHandler();
button4.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                Log.i(TAG, "------>execute on subThread");
                handler.sendEmptyMessage(1);

            }
        }.start();
    }
});
class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.i("MyHandler", msg.toString());
        button4.setText("handled");
    }
}

由于Handler实例的构造是和当前线程绑定的,此处的new MyHandler发生在UI线程,因此后面的handleMessage便也是发生在UI线程.

post

handler.post(Runnable);

如果handler是在UI线程中初始化的,执行其post方法,最终参数Runnable会运行在UI线程,因此上面的更新UI我们也可以这么做

button4.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        new Thread() {
            @Override
            public void run() {
                super.run();
                Log.i(TAG, "------>execute on subThread");
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        button4.setText("handled");
                    }
                });
            }
        }.start();
    }
});

关于内存泄露的问题

上面的写法MyHandler是作为Activity的成员内部类的方式存在的。由于Java中非静态内部类会持有外部类的引用(相当于是需要一个上下文),因此需要将MyHandler生名成静态内部类,同时持有外层Activity的弱引用,具体的细节这里不写了,可以参看这里 #17