Android 中的主线程和子线程
主线程(UI线程)主要用来处理四大组件间的交互,子线程用来做耗时操作(网络请求,I/O操作,sqlite操作等)
由于主线程比较特殊,因为本身主线程在处理界面上,用了大部分的消耗,所以主线程不能再处理过于耗时的操作(IO操作,网络请求,大量的数据操作),否则就会造成ANR现象(程序卡死)。
Activity响应时间超过5s
Broadcast在处理时间超过10s
Service处理时间超过20s
Android中的线程表现形式:
Thread、HanderThread、IntentService、AsyncTask、
HanderThread
HandlerThread 是一个包含 Looper 的 Thread,我们可以直接使用这个 Looper 创建 Handler。
HandlerThread 继承了Thread,它是一种可以使用 Handler 的 Thread,在 run 方法中通过 Looper.prepare() 来创建消息队列,并通过 Looper.loop() 来开启消息循环。
// 子线程中创建新的Handler 没有使用HandlerThread
new Thread () {
@Override
public void run() {
Looper.prepare();
Hnadler handler = new Handler();
Looper.loop();
}
}
HandlerThread 的使用场景
我们知道,HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作。
比如说多个网络请求操作,或者多文件 I/O 等等。
###使用流程
1.实例对象,参数为线程名字
HandlerThread handlerThread = new HandlerThread("handlerThread");
2.启动线程
handlerThread.start();
3.实例主线程的Handler,参数为HandlerThread内部的一个looper.
Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
这里模拟的情况是我们在子线程下载东西,然后和主线程之间进行通信。主线程知道了下载开始和下载结束的时间,也就能及时改变界面UI。
首先是DownloadThread类,继承于HandlerThread,用于下载。
public class DownloadThread extends HandlerThread{
private static final String TAG = "DownloadThread";
public static final int TYPE_START = 2;//通知主线程任务开始
public static final int TYPE_FINISHED = 3;//通知主线程任务结束
private Handler mUIHandler;//主线程的Handler
public DownloadThread(String name) {
super(name);
}
/*
* 执行初始化任务
* */
@Override
protected void onLooperPrepared() {
Log.e(TAG, "onLooperPrepared: 1.Download线程开始准备");
super.onLooperPrepared();
}
//注入主线程Handler
public void setUIHandler(Handler UIhandler) {
mUIHandler = UIhandler;
Log.e(TAG, "setUIHandler: 2.主线程的handler传入到Download线程");
}
//Download线程开始下载
public void startDownload() {
Log.e(TAG, "startDownload: 3.通知主线程,此时Download线程开始下载");
mUIHandler.sendEmptyMessage(TYPE_START);
//模拟下载
Log.e(TAG, "startDownload: 5.Download线程下载中...");
SystemClock.sleep(2000);
Log.e(TAG, "startDownload: 6.通知主线程,此时Download线程下载完成");
mUIHandler.sendEmptyMessage(TYPE_FINISHED);
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private DownloadThread mHandlerThread;//子线程
private Handler mUIhandler;//主线程的Handler
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化,参数为线程的名字
mHandlerThread = new DownloadThread("mHandlerThread");
//调用start方法启动线程
mHandlerThread.start();
//初始化Handler,传递mHandlerThread内部的一个looper
mUIhandler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
//判断mHandlerThread里传来的msg,根据msg进行主页面的UI更改
switch (msg.what) {
case DownloadThread.TYPE_START:
Log.e(TAG, "4.主线程知道Download线程开始下载了...这时候可以更改主界面UI");
break;
case DownloadThread.TYPE_FINISHED:
Log.e(TAG, "7.主线程知道Download线程下载完成了...这时候可以更改主界面UI,收工");
break;
default:
break;
}
super.handleMessage(msg);
}
};
//子线程注入主线程的mUIhandler,可以在子线程执行任务的时候,随时发送消息回来主线程
mHandlerThread.setUIHandler(mUIhandler);
//子线程开始下载
mHandlerThread.startDownload();
}
@Override
protected void onDestroy() {
//有2种退出方式
mHandlerThread.quit();
//mHandlerThread.quitSafely(); 需要API>=18
super.onDestroy();
}
}
运行log如下
总结如下:
- Handler是Android消息机制的上层接口,通过它可以轻松地将一个任务切换到Handler所在的线程中去执行,该线程既可以是主线程,也可以是子线程,要看构造Handler时使用的构造方法中传入的Looper位于哪里;
- Handler的运行需要底层的MessageQueue和Looper的支撑,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,而线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper;
ActivityThread被创建时就会初始化Looper,这就是主线程中默认可以直接使用Handler的原因; - Looper的工作原理:Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。注意关注一些重要的Looper的方法:
Looper.prepare()-为当前线程创建一个Looper;
Looper.loop()-开启消息循环,只有调用该方法,消息循环系统才会开始循环;
Looper.prepareMainLooper()-为主线程也就是ActivityThread创建Looper使用;
Looper.getMainLooper()-通过该方法可以在任意地方获取到主线程的Looper;
Looper.quit() Looper.quitSafely()-退出Looper,自主创建的Looper建议在不使用的时候退出 - ActivityThread主线程通过ApplicationThread和AMS进行进程间通信
如果有耗时操作可以考虑使用HandlerThread机制。
创建HandlerThread后必须首先调用start方法,以此来准备子线程需要的Looper对象,之后再创建Handler对象时,才能在构造方法中调用new Handler(handlerThread.getLooper())来使用Looper对象,注意创建的先后顺序。
如果要看源码分析 请参考
https://blog.csdn.net/franky814/article/details/81387000 HandlerThread的使用及原理浅析
IntentService
IntentService 使用工作线程逐一处理所有启动请求。如果你不需要在 Service 中执行并发任务,IntentService 是最好的选择。
IntentService 是一种特殊的 Service ,它继承了 Service 并且它是一个抽象类,因此必须创建它的子类才能使用 IntentService。
IntentService 可用于执行后台耗时的任务,当任务执行后它会自动停止,同时由于 IntentService 是服务的原因,这导致它的优先级比单纯的线程要高很多,比较适合一些高优先级的后台任务。
- IntentService 是一种特殊的 Service,它继承了 Service 并且它是一个抽象类,因此必须创建它的子类才能够使用 IntentService。
- 它可用于执行后台耗时任务,当任务执行完成后它会自动停止,在资源不足时,系统会对一些被认为时无用的资源给清理掉,
- 由于它是 Service 的原因,它的优先级比单纯的线程的优先级高很多,不容易被系统杀死(清理掉),所以它适合执行一些高优先级的后台任务。
- 在实现上,IntentService 封装了 Handlerhread 和 Handler。
- 另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,
- 并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。
IntentService 的使用
通过前面的源码分析,我们可以看到,最终每个任务的处理都会调用 onHandleIntent(),因此使用 IntentService 也很简单,只需实现 onHandleIntent() 方法,在这里执行对应的后台工作即可。
举个例子:
我们写一个使用 IntentService 实现在子线程下载多张 美女图片 的效果。
/**
* Description:
* <br> 使用 IntentService 实现下载
* <p>
* <br> Created by shixinzhang on 17/6/8.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class DownloadService extends IntentService {
private static final String TAG = "DownloadService";
public static final String DOWNLOAD_URL = "down_load_url";
public static final int WHAT_DOWNLOAD_FINISHED = 1;
public static final int WHAT_DOWNLOAD_STARTED = 2;
public DownloadService() {
super(TAG);
}
private static Handler mUIHandler;
public static void setUIHandler(final Handler UIHandler) {
mUIHandler = UIHandler;
}
/**
* 这个方法运行在子线程
*
* @param intent
*/
@Override
protected void onHandleIntent(final Intent intent) {
String url = intent.getStringExtra(DOWNLOAD_URL);
if (!TextUtils.isEmpty(url)) {
sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 开始下载任务:\n" + url);
try {
Bitmap bitmap = downloadUrlToBitmap(url);
SystemClock.sleep(1000); //延迟一秒发送消息
sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 发送消息到主线程
*
* @param id
* @param o
*/
private void sendMessageToMainThread(final int id, final Object o) {
if (mUIHandler != null) {
mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
}
}
/**
* 下载图片
*
* @param url
* @return
* @throws Exception
*/
private Bitmap downloadUrlToBitmap(String url) throws Exception {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
Bitmap bitmap = BitmapFactory.decodeStream(in);
urlConnection.disconnect();
in.close();
return bitmap;
}
}
在上面的代码中,我们做了以下几件事:
在 onHandleIntent() 中接收任务,开始下载,同时将状态返回给主线程
下载完成后将得到的 Bitmap 通过 Handler 发送到主线程
/**
* Description:
* <br> IntentService 调用实例
* <p>
* <br> Created by shixinzhang on 17/6/9.
* <p>
* <br> Email: shixinzhang2016@gmail.com
* <p>
* <a href="https://about.me/shixinzhang">About me</a>
*/
public class IntentServiceActivity extends AppCompatActivity implements Handler.Callback {
@BindView(R.id.iv_display)
ImageView mIvDisplay;
@BindView(R.id.btn_download)
Button mBtnDownload;
@BindView(R.id.tv_status)
TextView mTvStatus;
private List<String> urlList = Arrays.asList("https://ws1.sinaimg.cn/large/610dc034ly1fgepc1lpvfj20u011i0wv.jpg",
"https://ws1.sinaimg.cn/large/d23c7564ly1fg6qckyqxkj20u00zmaf1.jpg",
"https://ws1.sinaimg.cn/large/610dc034ly1fgchgnfn7dj20u00uvgnj.jpg"); //美女图片地址
int mFinishCount; //完成的任务个数
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_intent_service);
ButterKnife.bind(this);
DownloadService.setUIHandler(new Handler(this));
}
@OnClick(R.id.btn_download)
public void downloadImage() {
Intent intent = new Intent(this, DownloadService.class);
for (String url : urlList) {
intent.putExtra(DownloadService.DOWNLOAD_URL, url);
startService(intent);
}
mBtnDownload.setEnabled(false);
}
@Override
public boolean handleMessage(final Message msg) {
if (msg != null) {
switch (msg.what) {
case DownloadService.WHAT_DOWNLOAD_FINISHED:
mIvDisplay.setImageBitmap((Bitmap) msg.obj);
mBtnDownload.setText("完成 " + (++mFinishCount) + "个任务");
break;
case DownloadService.WHAT_DOWNLOAD_STARTED:
mTvStatus.setText(mTvStatus.getText() + (String) msg.obj);
break;
}
}
return true;
}
}
Activity 中做了以下几件事:
设置 UI 线程的 Handler 给 IntentService
使用 startService(intent) 启动 IntentService 执行图片下载任务
在 Handler 的 handleMessage 中根据消息类型进行相应处理
一句话总结 IntentService:
优先级比较高的、用于串行执行异步任务、会自尽的 Service。
例子参考
https://blog.csdn.net/u011240877/article/details/72972610
AsyncTask
构建AsyncTask子类的泛型参数
AsyncTask<Params,Progress,Result>是一个抽象类,通常用于被继承.继承AsyncTask需要指定如下三个泛型参数:
Params:启动任务时输入的参数类型.
Progress:后台任务执行中返回进度值的类型.
Result:后台任务执行完成后返回结果的类型.
.构建AsyncTask子类的回调方法
AsyncTask主要有如下几个方法:
doInBackground:必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成.
onPreExecute:执行后台耗时操作前被调用,通常用于进行初始化操作.
onPostExecute:当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法.通过此方法进行UI的更新.
onProgressUpdate:当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法.通过此方法我们可以知晓任务的完成进度.
.使用AsyncTask的注意事项
① 必须在UI线程中创建AsyncTask的实例.
② 只能在UI线程中调用AsyncTask的execute方法.
③ AsyncTask被重写的四个方法是系统自动调用的,不应手动调用.
④ 每个AsyncTask只能被执行(execute方法)一次,多次执行将会引发异常.
⑤ AsyncTask的四个方法,只有doInBackground方法是运行在其他线程中,其他三个方法都运行在UI线程中,也就说其他三个方法都可以进行UI的更新操作.
AsyncTask 有两个线程池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和一个 Handler(IntentHandler),其中线程池 SerialExecutor 用于任务的排队,而线程池 THREAD_POOL_EXECUTOR 用于真正的执行任务,InternalHandler 用于将执行环境从线程池切换到主线程。
AsyncTask封装了 Thread 和 Handler,通过 AsyncTask 可以更加方便的执行后台任务以及在主线程中访问UI,但是不适合执行特别耗时的后台任务,对于特别耗时的任务来说,还是使用线程池比较好。
Android线程池的使用
线程池的优点
(1)复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
线程池的分类:
最后该介绍一下主角线程池了。它分为四种:
1.FixedThreadPool
定长线程池特点:
①可控制线程最大并发数(线程数固定)
②超出的线程会在队列中等待
使用优势:能够更快的响应外界的请求。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
2.CachedThreadPool
缓存线程池的特点:
① 线程数无限制
② 没有核心线程,都是非核心线程
优势:比较适合用来执行大量的但是耗时较少的任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3.ScheduledThreadPool
定时线程池特点:核心线程数量固定、非核心线程数量无限制(闲置时马上回收)
使用场景:用于执行定时任务和具有固定周期的重复任务。
// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为4
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("开始执行任务");
}
};
// 3. 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后每隔1000ms执任务
// 4. 关闭线程池
scheduledThreadPool.shutdown();
4.SingleTreadExecutor
单线程化线程池特点:只有一个核心线程(保证所有任务按照指定顺序在一个线程中执行,不需要处理线程同步的问题)
使用场景:按顺序执行,不需要处理同步的问题。不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等
// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
public void run(){
System.out.println("开始执行任务");
}
};
// 3. 向线程池提交任务:execute()
singleThreadExecutor.execute(task);
// 4. 关闭线程池
singleThreadExecutor.shutdown();
Android 中的线程池来源于 Java 中的 Executor,Executor 是一个接口,真正的线程池实现为 ThreadPOOLExecutor。
真正的线程的实现为ThreadPoolExecutor。(ThreadPoolExecutor继承了AbstractExecutorService,AbstractExecutorService是ExecutorService的实现类,ExecutorService继承了Executor接口)。
ThreadPoolExecutor extends AbstractExecutorService implements ExecutorService extends Executor
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
-
corePoolSie
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个事件间隔由keepAliveTime 所指定,当等待事件超过 keepAliveTime 所指定的时间,核心线程会被终止。 -
maximumPoolSize
线程池中所能容纳的最大线程数,当活动线程数达到这个数值后,后续的任务会被阻塞。 -
keepAliveTime
非核心线程闲置是的超时时长,超过这个时长,非核心线程就会被回收。如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为 true,keepAliveTime 同样会作用于核心线程。 -
unit
用于指定 keepAliveTime 参数的时间单位,这是一个枚举,常用的有 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。 -
workQueue
线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。 -
threadFactory
线程工厂,为线程池创建新线程的功能。ThreadFactory 是一个接口,它只有一个方法: Thread newThread(Runnable r).
ThreadPoolExecutor 执行任务时大致遵循如下规则:
- 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
- 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会直接插入到任务队列中排队等待执行。
- 如果在步骤2中无法将任务插入到任务队列里,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
- 如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor 会调用
RejectedExecutionHandler 的 rejectedExecution 方法来通知调用者。
线程池有两个执行的方法,分别是submit()和execute(),这两个方法本质的含义是一样的.
从图上可以看出的,submit()其实还是需要调用execute()去执行任务,而submit()和execute()本质上的不同是submit()将包装好的任务进行了返回
线程池的使用
/**
* 线程池封装
*
* @author jun.yang
* created at 2018/2/26 10:21
*/
public class ThreadPoolManager {
private static ThreadPoolManager mInstance;
public static ThreadPoolManager getInstance() {
if (mInstance == null) {
synchronized (ThreadPoolManager.class) {
if (mInstance == null) {
mInstance = new ThreadPoolManager();
}
}
}
return mInstance;
}
/**
* 核心线程池的数量,同时能够执行的线程数量
*/
private int corePoolSize;
/**
* 最大线程池数量,表示当缓冲队列满的时候能继续容纳的等待任务的数量
*/
private int maximumPoolSize;
/**
* 存活时间
*/
private long keepAliveTime = 1;
private TimeUnit unit = TimeUnit.HOURS;
private ThreadPoolExecutor executor;
private ThreadPoolManager() {
/**
* 给corePoolSize赋值:当前设备可用处理器核心数*2 + 1,能够让cpu的效率得到最大程度执行(有研究论证的)
*/
corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;
//虽然maximumPoolSize用不到,但是需要赋值,否则报错
maximumPoolSize = corePoolSize;
executor = new ThreadPoolExecutor(
//当某个核心任务执行完毕,会依次从缓冲队列中取出等待任务
corePoolSize,
//5,先corePoolSize,然后new LinkedBlockingQueue<Runnable>(),然后maximumPoolSize,但是它的数量是包含了corePoolSize的
maximumPoolSize,
//表示的是maximumPoolSize当中等待任务的存活时间
keepAliveTime,
unit,
//缓冲队列,用于存放等待任务,Linked的先进先出
new LinkedBlockingQueue<Runnable>(),
//创建线程的工厂
// Executors.defaultThreadFactory(),
new DefaultThreadFactory(Thread.NORM_PRIORITY, "tiaoba-pool-"),
//用来对超出maximumPoolSize的任务的处理策略
new ThreadPoolExecutor.AbortPolicy()
);
}
/**
* 执行任务
*
* @param runnable
*/
public void execute(Runnable runnable) {
if (executor == null) {
//线程池执行者。
//参1:核心线程数;参2:最大线程数;参3:线程休眠时间;参4:时间单位;参5:线程队列;参6:生产线程的工厂;参7:线程异常处理策略
executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
// Executors.defaultThreadFactory(),
new DefaultThreadFactory(Thread.NORM_PRIORITY, "tiaoba-pool-"),
new ThreadPoolExecutor.AbortPolicy());
}
if (runnable != null) {
executor.execute(runnable);
}
}
/**
* 移除任务
*/
public void remove(Runnable runnable) {
if (runnable != null) {
executor.remove(runnable);
}
}
/**
* 创建线程的工厂,设置线程的优先级,group,以及命名
*/
private static class DefaultThreadFactory implements ThreadFactory {
/**
* 线程池的计数
*/
private static final AtomicInteger poolNumber = new AtomicInteger(1);
/**
* 线程的计数
*/
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final String namePrefix;
private final int threadPriority;
DefaultThreadFactory(int threadPriority, String threadNamePrefix) {
this.threadPriority = threadPriority;
this.group = Thread.currentThread().getThreadGroup();
namePrefix = threadNamePrefix + poolNumber.getAndIncrement() + "-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon()) {
t.setDaemon(false);
}
t.setPriority(threadPriority);
return t;
}
}
}
调用方法
ThreadPoolManager.getInstance().execute(command));
Runnable command = new Runnable(){
@Override
public void run(){
SystemClock.sleep(2000)
}
}