Handler
Handler,字面理解叫做操作者,实际上是一个可以管理线程、实现线程之间通讯的工具。Handler的使用解决了两个问题:
- 调度Android系统在某个时间点执行特定的任务
- 将需要执行的任务加入到用户创建线程的任务队列中去
由于一个Handler发送消息的特性,可以借助它利用消息队列的机制,实现线程之间的消息交换,非常实用,可以说有子线程的地方就有Handler。
Handler有两种使用方式,即发送Message消息和运行Runable任务。事实上运行Runable的内部实现也是进一步调用了发送消息到消息队列中,所以本质上没有区别。
一个Handler对象的作用是:往创建它的线程的消息队列中添加消息,并借助Looper对消息队列中的消息进行处理。也就是说,一个Handler一旦创建起来,就和创建它的线程绑定,即使在其他线程中发送消息,也会发送到创建它线程的消息队列中,就实现了线程之间的通信。另外,Looper的存在是创建Handler的先决条件,如果一个线程中没有事先准备好Looper,就不能在这个线程中创建Handler。而主线程中系统会提前创建好Looper,所以主线程的Handler不必担心这个;但是一旦要想在自己的子线程中创建Handler,就一定要先准备Looper。
在创建Handler的时候通常会重写的一个方法就是handleMessage,也就是处理、消费消息队列中的消息的方法,或者说接受到消息后要做的事情。
在使用创建好的Handler对象发送消息时,有以下这些方法可以调用:
// 立即发送消息
public final boolean sendMessage(Message msg)
public final boolean post(Runnable r);
// 延时发送消息
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean postDelayed(Runnable r, long delayMillis);
// 定时发送消息
public boolean sendMessageAtTime(Message msg, long uptimeMillis);
public final boolean postAtTime(Runnable r, long uptimeMillis);
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);
// 取消消息。可以将延时的消息提前取消掉防止生效。
public final void removeCallbacks(Runnable r);
public final void removeMessages(int what);
public final void removeCallbacksAndMessages(Object token);
使用post相关的方法可以方便地执行任务,而使用sendMessage系列方法可以方便地发送消息,使用的时候可以随机应变,但是内部的实现本质相同。
Message对象封装了一个消息的内容,其中值得关心的是what属性和obj属性。what是一个int,可以协助区分不同的消息,采取不同的处理措施;而obj是一个Object对象,是消息携带的内容。例如:
Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 根据msg.what来区分消息种类,采取不同的行动
switch (msg.what) {
case MSG_START_DOWNLOAD:
mText.setText("开始下载");
break;
case MSG_DOWNLOAD_SUCCESS:
mText.setText("下载成功");
break;
case MSG_DOWNLOAD_FAIL:
mText.setText("下载失败");
break;
}
}
};
如果在子线程(自定义的线程)中创建和使用Handler,则需要遵循一套先准备Looper的顺序:
//声明Handler;
Handler handler;
new Thread(new Runnable(){
@Override
public void run(){
//创建当前线程的Looper
Looper.prepare();
//在子线程创建handler对象
handler=new Handler(){
@Override
public void handleMessage(Message msg){
//这里是消息处理,它是运行在子线程的
}
};
//开启Looper的消息轮询
Looper.loop();
}
}).start();
mBanner.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
//在主线程发送一个消息到子线程
Message msg=new Message();
handler.sendMessage(msg);
}
});
当我们在子线程使用Handler时,如果Handler不再需要发送和处理消息,那么一定要退出子线程的消息轮询。因为Looper.loop()方法里是一个死循环,如果我们不主动结束它,那么它就会一直运行,子线程也会一直运行而不会结束。使用Looper.myLooper().quit();
和Looper.myLooper().quitSafely();
来结束Looper的轮询。
另外,不要new Message,而是使用Message.obtain()来获取消息进行定义,这样可以利用起来Message的缓存机制,提高效率。
多线程
Android中,UI的更新只能在主线程中进行,如果放在子线程中可能会出问题,不允许这样做。因此主线程也叫做UI线程。一些耗时的操作也应该放在子线程中异步执行,避免阻塞主线程导致程序卡死。下面介绍一些多线程的基础和进阶组件的使用方法。
Thread
来自Java,多线程的根基。直接拿来使用可以完成一些简单的任务。只需要重写run方法就可以给线程布置好任务,然后调用start方法可以让线程运行起来。使用比较简单,参考下图:
ThreadPool
提供了一个对线程集中管理、复用的地方。由于创建线程的代价比较大,如果能够事先准备一些线程,然后在需要的时候直接把任务丢入池子让线程池帮忙分配任务,可以最好地利用每一个线程,避免浪费。
通常,在单个任务处理时间比较短且任务数量很大的时候会使用线程池,避免频繁创建和销毁线程,比如网络请求和数据库读写等操作。定时任务可以使用定时线程池,而特定任务也可以使用单一线程池,如日志写入。
AsyncTask
如果使用Thread+Handler的处理机制,模板代码较多,代码臃肿。因此Google提供了一个工具类AsyncTask,帮助我们更好地封装了异步多线程的操作,使用起来更加方便。
通过继承AsyncTask<Params, Progress, Result>,使用以下核心方法定制自己的异步任务:
- onPreExecute 执行前的动作。这个方法会在UI线程中执行
- doInBackground 后台执行的任务。这个方法在子线程中进行。
- publishProgress 后台通知前台任务的进度。这个方法在子线程中执行。不需要重写,在子线程中调用即可。
- onProgressUpdate 接受后台的任务进度通知并更新UI。在UI线程中进行。
- onPostExecute 执行完成后的动作。在UI线程中执行。
请看一个后台下载任务的例子:
package com.bytedance.clockapplication;
import android.annotation.SuppressLint;
import android.os.AsyncTask;
import android.widget.TextView;
@SuppressLint("CI_StaticFieldLeak")
public class DownloadTask extends AsyncTask<String, Integer, String> {
TextView mTextView;
DownloadTask(TextView textView) {
mTextView = textView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
mTextView.setText("开始下载....");
}
@Override
protected String doInBackground(String... strings) {
String url = strings[0];
try {
return downlaod(url);
} catch (Exception e) {
return "Fail";
}
}
private String downlaod(String url) throws InterruptedException {
for (int i = 0; i < 100; i++) {
publishProgress(i);
Thread.sleep(50);
}
return "Success";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
mTextView.setText("下载进度:" + values[0]);
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mTextView.setText("下载完成:" + s);
}
}
这里AsyncTask<String, Integer, String>的三个类型分别对应执行时传入的参数Params、过程中通知时采用的消息类型和执行结果的类型。
可以通过如下的方式进行使用:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = findViewById(R.id.text);
// 执行时传入参数Params
new DownloadTask(mText).execute("http://www.xxx.mp4");
}
HandlerThread
顾名思义,本质是封装了Handler的一个Thread子类,也会自动创建Looper。重写核心方法handleMessage可以处理自己消息队列中的Message,做出响应。
下面是一个股票交易App的定时查询价格的例子:
IntentService
Service可以运行在后台提供服务而不提供用户界面,就像Linux中的守护程序。IntentService在使用的时候,可以通过传入一个Intent在后台启动一个服务,自动创建子线程执行一定的任务。IntentService封装良好,但是要知道,所有传入同一个IntentService对象的任务是串行执行的,也就是执行完了前一个才处理后一个。IntentService会在最后一项任务执行完之后自动销毁。
通常的应用是,在后台播放歌曲、下载文件等。例如:
package com.bytedance.clockapplication;
import android.app.IntentService;
import android.content.Intent;
import android.support.annotation.Nullable;
public class DownloadService extends IntentService {
// 必须有一个匹配父类的构造方法
public DownloadService(String name) {
super(name);
}
//处理意图,完成任务
@Override
protected void onHandleIntent(@Nullable Intent intent) {
if (intent!=null){
try{
String url=intent.getStringExtra("url");
// download file
}catch (Exception e){
e.printStackTrace();
}
}
}
}
在主线程中可以如下方式使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this,DownloadService.class);
intent.putExtra("url","http://...");
startService(intent);
Intent intent2 = new Intent(this,DownloadService.class);
intent2.putExtra("url","http://.../2");
startService(intent2);
}
}
请注意,主线程中并不会被阻塞,而是直接把两个intent都交给了DownloadService,如何完成两个任务是DownloadService自己的事了,而IntentService会串行执行,所以DownloadService只能一个完成后在处理另一个,在两个都处理完成后自动销毁。
总结
工具 | 描述 |
---|---|
Thread | 多线程的基础 |
ThreadPool | 对线程进行更好的管理 |
AsyncTask | Android中为了简化多线程的使用, 而设计的默认封装 |
HandlerThread | 开启一个线程,就可以处理多个耗时任务 |
IntentService | Android中无界面异步操作的默认实现 |
自定义View
比如要做一个时钟的应用程序,指针这种View组件是安卓没有自带的,需要我们自己定义出来。这个时候就需要自定义View了。
View绘制的三个步骤分别是先测量宽高,然后确定位置,最后绘制形状。自定义View需要继承View类,通常只需要重写第三步的onDraw(Canvas canvas)方法,自定义形状就可以了。
绘制形状就是在Canvas画布上用Paint画笔作图,先设置好画笔的颜色、粗细、风格等属性,然后把画笔对象当做参数传给Canvas的许多作画方法就可以完成作图了。这里不再赘述。