今日计划:
1.学习runOnUiThread来快速操控界面
2.学习工作管理器WorkManager
3.学习Http访问的移动数据格式JSON
4.学习okhttp调用HTTP接口
上周五遗留问题已解决,见Day28
那么有了新的问题:
点击暂停后再次点击开始会闪退,下面这行报错:
thread.start();
一个线程在创建的时候会指定一个线程任务,当执行完这个线程任务之后,线程自动销毁。
那么点完暂停他应该就销毁了
去看了作者源码,发现它重写了一个类继承Thread并重写run方法,这样使用的时候直接类名+start
所以我认为是第一次销毁之后没办法再初始化了
我的方法:
Thread thread = initThread();
thread.start();
把原来初始化thread封装到方法里然后用的时候调用
成功解决
新的问题:post可以修改UI吗
可以实现
还有一个问题:
为什么setText没有成功执行
textView.setText(news);
while (true){
long i = -99999999;
while (i < 555555555){
i++;
}
String word = Util.getNowTime() + "这是主线程的消息"+"\n";
news += word;
Log.d(TAG, "onClick: "+ word);
textView.setText(news);
}
界面刷新是异步的,需要时间,也需要占用CPU的,死循环把CPU占用了
当一个TextView的实例调用setText()方法后,会执行以下步骤:
-
更新TextView的内部存储的文本内容。
-
调用requestLayout()方法,请求重新测量TextView的大小。
-
在下一个绘制周期中,系统会调用TextView的onMeasure()方法,确定TextView的大小。
-
然后,系统会调用TextView的onLayout()方法,确定TextView的位置。
-
最后,系统会调用TextView的onDraw()方法,绘制TextView的文本内容。
这是Android View绘制流程中TextView更新文本内容后的简要描述。
据此我想理解掌握TextView刷新的过程,到底怎么刷新的
于是找到了:
Android 绘制流程(从Activity创建 -> 内容展示)
由此找到了
ActivityThread的理解和APP的启动过程
然后又找到了:
Looper与Handler机制
大概的流程如上图所示,具体的细节我们结合源代码进行分析。思路是这样的:
-
消费者部分:我们先从程序的入口开始,看看
Looper
循环是如何初始化和开始循环的,它如何获得消息,如何对消息进行处理。 -
生产者部分:我们以自己平时是如何生产消息为例子,然后分析消息是如何推入队列的。
-
消息队列:
MessageQueue
,消息队列并不会按照顺序单独分为一个部分来讲解,因为它的逻辑是糅合在生产-消费的过程中的。所以只要把上面的部分理解了,消息队列的部分自然就理解了。这里我们可以先补充一些概念:-
消息队列是用链表实现的
-
它包含了数据、对应的
Handler
、期待它被处理的时间
-
Handler处理信息的优先级:
-
1.
callback
:优先级最高,这是一个Runable
,当我们调用post(Runable r)
时其实就是用这个变量。 -
2.
mCallback
:优先级次之,我们在初始化Handler
时可以传进去:它是一个
Callback
接口,只有一个方法:handleMessage(@NonNull Message msg)
注意:
if (mCallback.handleMessage(msg)) { return; }
,所以当该接口返回true
时则不希望Handler.handleMessage()
再进行处理。 -
3.
handleMessage()
:优先级最低,我们继承该Handler
时,通过重写该方法来处理消息。在Handler
中默认不作任何处理:
在performLaunchActivity方法中,可以看到:
-
执行了Activity所属Context的真正执行类ContextImpl的创建
-
创建完了Activity之后,在Instrumentation中通过newInstance方式,反射创建了Activity
-
创建完Activity之后,在onCreate方法执行之前先调用了关键方法:attach
-
在执行完attach方法后,通过层层调用,最终执行Activity的onCreate生命周期方法
执行流程: performLaunchActivity - > Activity创建 -> Activtiy.attach -> Activity.onCreate
在performLaunchActivity的过程中,先后执行了
1.Activity创建
2.Activity的attch
-
创建了PhoneWindow以及对应的WindowManager
3.Activity的onCreate
-
执行setContentView,完成了DerView的创建
在performLaunchActivity过程中,完成Activity中设置resId对应的布局文件的从DecorView到最后子View的加载(过程中涉及xml解析,递归以及反射,详细细节可以看看这篇,但到Activity的onCreate位置,视图还并未显示,需要再执行performResumeActivity方式。
在handleResumeActivity之前,DecorView已经被创建但未绘制,在handleResumeActivity中,先执行了onResume生命周期,后创建了ViewRootImpl作为DecorView的父类,配置Choreography进行刷新以及ViewTree的绘制工作。
由此可见:
在Activity首次创建的过程中,在执行onResume生命周期时,控件树已经被创建,即我们可以通过findViewById获取到UI控件,但这个时期,内容并未展示,而是去向系统申请刷新信号,当信号到来之时,才进行绘制(measure,layout,draw)工作。
在performLaunchActivity到handleResumeActivity的过程中,先后执行了
1.Activity创建
2.Activity的attch
-
创建了PhoneWindow以及对应的WindowManager
3.Activity的onCreate
-
执行setContentView,完成了DerView的创建 4.Activity的onResume
5.WindowMananger.addView
-
创建ViewRootImpl,并将DecorView传入
-
通过Choreography申请刷新信号 6.VSync刷新信号到来
-
ViewRootImpl执行performTraversals
-
ViewTree控件树,执行measure,layout,draw方法
其中draw方法会根据是否开启硬件加速有多种实现机制。
13.1.2 通过runOnUiThread快速操纵界面
分线程若想操纵界面控件,在线程内部调用runOnThread方法即可
调用代码如下:
//回到主线程(UI线程)操作界面
runOnUiThread(new Runnable(){
@Override
public void run (){
//操作界面的代码放在这里
}
});
Runnable属于函数式接口可简化为:
runOnUiThread(()->{
});
如果Runnable的运行代码只有一行,那么Lambda允许进一步简化,也就是省略外面的花括号
runOnUiThread(()->/*如果只有一行代码,那么连花括号也可省掉*/)
new Thread(new Runnable() {
@Override
public void run() {
while (isSend){
String word = newsArray[ new Random().nextInt(6)];
Log.d(TAG, "run: "+word);
news += word+"\n";
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(news);
}
});
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}).start();
13.1.3 工作管理器WorkManager
对于后台的异步服务,官方建议改为使用工作管理器WorkManager
继承Worker的工作者重写的doWork方法运行于分线程,因此该方法内部不能操作界面控件
在活动页面构建并启动工作任务,详细过程分为下列四个步骤:
1.构建约束条件
该步骤说明在哪些情况下才能执行后台任务,也就是运行后台任务的前提条件,此时用到了约束工具Constraints
2.构建输入数据
该步骤吧后台任务需要的参数封装到一个数据对象中,此时用到了数据工具Data
3.构建工作请求
该步骤把约束条件、输入数据等请求内容组装起来,此时用到了工作请求工具OneTimeWorkRequest
4.执行工作请求
该步骤生成工作管理器实例,并将步骤3的工作请求对象加入管理器的执行队列中,由于管理器调度并执行请求任务
工作管理器不止拥有enqueue方法,还有其他的调度方法,常用的几个方法:
enqueue:将文件请求加入执行队列
cancelWorkById:取消指定编号(步骤3getId方法返回的workId)的工作
cancelAllWorkByTag:取消指定标签(步骤3getId方法返回的workId)的工作
cancelAllWork:取消所有工作
getWorkInfoByIdLiveData:获取指定编号的工作信息
后台工作是异步执行的,若想知晓工作任务的处理结果,就得调用getWorkInfoByIdLiveData方法
获取工作信息并实时监听他的运行情况
// 周期性任务的观察器实际无法操纵界面,因为周期任务一直在“执行”与“等待”之间切换,不会结束处理
workManager.getWorkInfoByIdLiveData(workId).observe(this, workInfo -> {
Log.d(TAG, "workInfo:" + workInfo.toString());
if (workInfo.getState() == WorkInfo.State.SUCCEEDED) { // 该分支永远都进不去
Data outputData = workInfo.getOutputData(); // 获得工作信息的输出数据
int resultCode = outputData.getInt("resultCode", 0);
String resultDesc = outputData.getString("resultDesc");
String desc = String.format("%s resultCode=%d,resultDesc=%s",
DateUtil.getNowTime(), resultCode, resultDesc);
tv_result.setText(desc);
}
});
13.2 HTTP访问:
1.学习如何利用移动数据格式JSON封装结构信息
2.学习如何从JSON串解析获得结构对象
3.学习okhttp调用HTTP接口的三种方式(GET方式、表单格式的POST请求、JSON格式的POST请求)
4.学习使用okhttp下载网络文件
5.将本队文件上传到服务器
13.2.1 移动数据格式JSON
网络通信的交互数据格式有两大类:JSON和XML
JSON的基本格式:
1.整个JSON串由一对花括号包裹,并且内部的每个结构都以花括号包起来
2.参数格式类似键值对,其中键名与键值之间以冒号分隔
3.两个键值对之间以逗号分隔
4.键名需要用双引号引起来,键值为数字的话无需双引号,为字符串的话仍需双引号
5.JSON数组通过方括号表达,方括号内部依次罗列各个元素,具体格式形如“数组的键名:[元素1,元素2,元素3]”
针对JSPN字符串,Android提供了JSON解析工具,支持对JSONObject(JSON对象)和JSOnArray(JSON数组)的解析处理
1.JSONObjet
常用方法:
今日总结:
1.解决了上周遗留的post(thread)问题
2.学习了使用runOnUiThread快速操纵界面
3.学习了工作管理器WorkManager
4.学习在活动页面构建并启动工作任务的四个步骤
5.学习了移动数据格式JSON
6.学习了使用Gson库转换JSON数据
明日计划:
1.学习okhttp调用HTTP接口的三种方式
2.学习利用Glide实现图片的三级缓存
3.学习使用Glide加载特殊图像
4.学习通过SocketIO传输文本消息和图片消息