目录
3.1 示例:启动今日头条app的时候,展示了一个开屏广告,默认播放3秒;在3秒后,需跳转到主界面。
3.2 示例:用户在抖音App中,点击下载视频,下载过程中需要弹出Loading窗,下载结束后提示用户下载成功/失败。
一、Handler机制(Android的消息队列机制)
1.Handler应用场景
- 启动今日头条app的时候,展示了一个开屏广告,默认播放x秒;在x秒后,需跳转到主界面。
- 用户在抖音App中,点击下载视频,下载过程中需要弹出Loading弹窗,下载结束后提示用户下载成功/失败。
Handler为Android系统解决以下两个问题:
①调度(Schedule)Android系统在某个时间点执行特定的任务,可以使用以下两种方式表示特定的任务:
a. Message(android.os.Message) b. Runnable(java.lang.Runnable)
Runnable会被打包成Message,所以实际上Runnable也是Message,但是当运行多个任务时,Message更明晰。
②将需要执行的任务加入到用户创建的线程的任务队列中,即Handler可以进行线程间的通信。
2.Handler常用方法
// 立即发送消息
public final boolean sendMessage(Message msg)
public final boolean post(Runnable r);
// 延时发送消息,delayMillis为时间差值,比如3000ms.
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean postDelayed(Runnable r, long delayMillis);
// 定时发送消息,uptimeMillis为时间戳,即某一个具体的时间。
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);// remove一个runnable
public final void removeMessages(int what);// 参数what指定了唯一一个message
public final void removeCallbacksAndMessages(Object token);// 当token为null时表示remove全部任务
3.Handler的使用示例
- 调度Message 新建一个Handler,实现handleMessage()方法,在适当的时候给Handler发送消息;
- 调度Runnable 新建一个Handler,然后直接调度Runnable即可取消调度;(Runnable本身就是一个可执行的任务,不需要像Message一样handleMessage去执行)
- 通过Handler取消已经发送过的Message/Runnable;
3.1 示例:启动今日头条app的时候,展示了一个开屏广告,默认播放3秒;在3秒后,需跳转到主界面。(某个时间点执行某一任务)
①编写开屏广告布局
②编写代码
package com.bytedance.clockapplication;
import android.content.Intent;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.byted.clockapplication.R;
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {// post一个Runnable任务
@Override
public void run() {
jumpToMainActivity();
}// 在该任务中,跳转至MainActivity
}, 3000);
}
private void jumpToMainActivity() {
startActivity(new Intent(this, MainActivity.class));
}
}
3.2 示例:用户在抖音App中,点击下载视频,下载过程中需要弹出Loading窗,下载结束后提示用户下载成功/失败。
注意:Android中,UI控件并非是线程安全的,只能在主线程内调用,所以所有对于 UI控件的调用,必须在主线程。 因此,通常我们也把主线程也叫做UI线程。
类似下载的后台任务不能在主线程中进行(下载一般较为费时),一般是另起一个线程,下载结束后会通知主线程,主线程再反馈下载成功/失败给界面。
①编写主页的布局
②编写代码实现下载功能
package com.bytedance.clockapplication;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import com.byted.clockapplication.R;
@SuppressLint("CI_HandlerLeak")
public class MainActivity extends AppCompatActivity {
public static final int MSG_START_DOWNLOAD = 0;
public static final int MSG_DOWNLOAD_SUCCESS = 1;
public static final int MSG_DOWNLOAD_FAIL = 2;
private TextView mText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mText = findViewById(R.id.text);
// 新建download线程,下载了一个虚拟视频地址。
new DownloadTask(mText).execute("http://www.xxx.mp4");
}
// 主线程接收并处理消息,进行了UI的更新
Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {// 判断所发送过来的what,在这里使用Handler进行了DownloadTask线程和主之间的通信。
// 只能在主线程中更新UI!!!
case MSG_START_DOWNLOAD:
mText.setText("开始下载");
break;
case MSG_DOWNLOAD_SUCCESS:
mText.setText("下载成功");
break;
case MSG_DOWNLOAD_FAIL:
mText.setText("下载失败");
break;
}
}
};
private class DownloadThread extends Thread {
private String videoId;
public DownloadThread(String videoId) {
this.videoId = videoId;
}
@Override
public void run() {
// 使用handler发送一个Message任务
// MSG_START_DOWNLOAD为int类型,就是参数int what,其实是message的一个属性代表了当前的message。
// MSG_START_DOWNLOAD为0.
mHandler.sendMessage(Message.obtain(mHandler, MSG_START_DOWNLOAD));
try {
// 下载成功,Handler发送success,MSG_DOWNLOAD_SUCCESS为1.
download(videoId);
mHandler.sendMessage(Message.obtain(mHandler, MSG_DOWNLOAD_SUCCESS));
} catch (Exception e) {
// 失败
mHandler.sendMessage(Message.obtain(mHandler, MSG_DOWNLOAD_FAIL));
}
}
}
private void download(String videoId) {
// 后台下载...
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. Handler原理:UI线程与消息队列机制
5.Handler总结
①Handler就是Android中的消息队列机制的一个应用,可理解为是一种生产者消费者的模型,解决了Android中的线程内&线程间的任务调度问题;
②Handler的本质就是一个死循环,待处理的Message加到队列里面,Looper负责轮询执行;
③掌握Handler的基本用法:立即/延时/定时发送消息、取消消息;
二、Android多线程
1.概念
- 进程(Process)是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配(资源)单元,也是基本的执行(调度)单元。一般情况下,android中的一个app是一个进程,如果需要使用多进程,需要手动开启。
- 线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
2.Android常用线程
2.1.Thread(Java)
2.2.ThreadPool(Java)
概念:
包含了多个线程,表述了异步执行的机制,并且可以让任务在一组线程内执行。
使用线程池优点:
如果不使用线程池,新建和销毁线程,如此一来会大大降低系统的效率,但是线程池可以重用,可以大大减少新建和销毁线程的开销。
重要函数:
execute(Runnable)
submit(Runnbale): 有返回值(Future),可以cancel,更方便进行错误处理。
shutdown()
以上两个线程都是Java中的线程,Android中不常用,下面三个是Android封装好的线程,常用。
2.3.AsyncTask(Android)
概念:
Android提供的工具类AsyncTask,使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务。
示例:
在Activity中新建DownloadTask类。
// AsyncTask线程
new DownloadTask(mText).execute("http://www.xxx.mp4");
其中DownloadTask继承自AsyncTask,在DownloadTask中重写线程,使用封装好的AsyncTask线程,代码结构会非常清晰。
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;
}
// UI线程,即会改变UI,必须运行在主线程中,该线程执行下载前的动作。
@Override
protected void onPreExecute() {
super.onPreExecute();
mTextView.setText("开始下载....");
}
// 非UI线程,即不会改变UI,该线程执行下载任务,一般时间长,不会运行在主线程中,自启线程运行。
@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);// 非UI线程,向onProgressUpdate线程传递参数
Thread.sleep(50);
}
return "Success";
}
// UI线程,该线程在UI中显示下载进度,接收来自publishProgress的参数显示在UI。
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
mTextView.setText("下载进度:" + values[0]);
}
// UI线程,参数s来自doInBackground函数,成功则返回了success,失败则返回fail.
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mTextView.setText("下载完成:" + s);
}
}
2.4.HandlerThread
概念:
HandlerThread的本质:继承Thread类和封装Handler类。
应用场景:
一款股票交易App,由于因为股票的行情数据都是实时变化的,所以我们软件需要每隔一定时间(Thread)向服务器请求行情数据(Handler),因此Android为了便于程序员们开发,提供了HandlerThread类。
示例代码:
编写StockHandlerThread,继承自HandlerThread,实现轮询的请求的调度后台数据。
2.5.IntentService
Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。常见Service: 音乐播放等。Android的四大组件都是运行在主进程上的,但是我们难免不会在Service上运行一些耗时的操作,比如:用Service下载文件。我们可以选择自己在Service中创建子进程,但是较为麻烦,因此诞生了IntentService可以帮助我们在Service中直接运行一些耗时的操作,这些操作是在工作进程,即子进程上运行,但是我们不需要自己手动创建子进程。
由上图可以看到,我们可以直接在onHandleIntent中运行工作进程的耗时任务,IntentService自动帮我们创建好了子进程。
2.6.多线程总结
Thread | 多线程的基础 |
ThreadPool | 对线程进行更好的管理 |
AsyncTask | Android中为了简化多线程的使用,而设计的默认封装 |
HandlerThread | 开启一个线程,就可以处理多个耗时任务 |
IntentService | Android中无界面(Service是后台执行)异步操作的默认实现 |
注意几个概念:
Thread:Java自带的线程。
AsyncTask:Android中封装的线程,简化了多线程的使用。
Handler:消息队列机制,可以实现定时任务以及线程间的通信。(注意与上一章所讲述的Activity与Fragment之间通信区分:setArguments和getArguments(Activity->Fragment),listener接口(Fragment->Activity))。
Intent:Android四大组件之一,用于实现组件之间的通信。
三、自定义View
1.View绘制的三个重要步骤
举例:画一个100 x 100的照片框
①需要尺子测量出宽高的长度(measure过程)
②然后确定照片框在屏幕中的位置(layout过程)
③最后借助尺子用手画出我们的照片框(draw过程)
当大小和位置不改变,只想改变形状,只需要调用invalidate方法,不再执行measure和layout,只进行draw。三个步骤都需要执行时才调用requestLaout方法。在这些方法中,最重要的是onDraw函数,需要我们自己重写。(重写需要两个重要的工具,Canvas:画布;Paint:画笔;)
2.示例
2.1.View绘制-点
在Init中初始化画笔,在onDraw中作图。
2.2.View绘制-线
2.3.View绘制-圆
2.4.View绘制-填充
Style的FILL表示全填充,STROKE表示绘制边框,在mPaint画笔设置中setStrokeWidth设置为边框大小为50f,FILL_AND_STROKE表示既绘制边框,又填充圆。
2.5.View绘制-不规则图形
最后调用了close方法,使图形闭合,即最后一个点连接上第一个点。
2.6.View绘制-画文本
2.7.View绘制-画文本
使用mPaint的setTextAlign方法将文本对齐方式改变,即文本坐标的设置,左边、中心、右边。