0%

2022-02-18-ekaterina-sazonova-CMhxw3lgZ7M-unsplash-modified.webp

1. 延时消息的原理

无论通过Handler的哪个post函数发消息,最终都会来到sendMessageAtTime,基于android.os.SystemClock#uptimeMillis时间,最终以SystemClock.uptimeMillis()+delayMillis的结果作为入队列和消息执行的基准。

1.1 延时消息是怎样入队列的?

消息入队列是通过MessageQueue#enqueueMessage函数,when表示消息执行的时间,通过下方源码可知如果当前消息队列为空,或者when等于0,或者新消息的when小于头消息的when,则直接将新消息替换为头消息。否则按照when的值将消息插入到队列的合适位置中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 当前消息队列为空 or when等于0 or 新消息的when小于头消息的when
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
// 否则按照when的值将消息插入到队列的合适位置中
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
...
}
return true;
}
Read more »

2021-12-16

1. MessageQueue的创建过程

即消息队列,以单链表的方式存储和管理着Message。在Andrid 2.3以前,只有Java世界的居民有资格向MessageQueue中添加消息以驱动Java世界的正常运转,但从Android 2.3开始,MessageQueue的核心部分下移至Native层,让Native世界的居民也能利用消息循环来处理他们所在世界的事情。因此MessageQueue心系Native和Java两个世界,担任着两个世界沟通的桥梁。下面即将从“存消息”和“取消息”两个动作详细分析Native和Java是怎么交流合作的。不过开始之前我们先扫清楚一些MessageQueue的细节知识点。

Read more »

2021-12-01

1. Handler的运行机制

Handler的主要作用是将一个任务切换到某个指定的线程中去执行,常见的就是在子线程中发消息通知主线程更新UI。通过Handler发送消息到处理消息的过程,从应用层来看很简单(但其实底层有完善的机制作为支撑),所以我们就从sendMessage入手开始分析。

无论Handler通过post还是sendMessage发消息,最终都会来到sendMessageAtTime,接着调用MessageQueue的enqueueMessage将Message消息按照执行时间入队列。另一边Looper的loop是个死循环,会不停的问MessageQueue要新消息,等拿到消息后会通过dispatchMessage进行分发,此时就将消息切换到了创建Looper所在的线程中执行,最后来到Handler的handleMessage中。

Read more »

iSplash-version-1

iSplash项目首个版本

技术栈 : LifeCycle + LiveData + ViewModel + Coroutine + Navigation + Kotlin

1. 一个完整的网络加载流程是怎样的?

以拿取最新照片列表为例

  1. MainActivity -> MainFragment -> LatestPhotosFragment
  2. 在LatestPhotosFragment中创建LatestPhotoViewModel实例,通过LatestPhotoViewModel实例内部的LiveData观察数据变化,只要数据一更新UI就能即时被刷新
  3. obtainLatestPhotoList函数内部就涉及到了LiveData、ViewModel、Coroutine的结合
    • LiveData与ViewModel的结合:在ViewModel中使用LiveData包装数据,达到实时监控数据变化,实时更新UI的目的
    • ViewModel与Coroutine的结合:在ViewModel中访问suspend函数,并将结果更新到LiveData数据中
Read more »

2021-04-02

这是JVM系列的第一篇,所以简单介绍下该系列的大纲,如下图所示:

jvm-outline

该系列文章绝大部分的素材来源是《深入理解Java虚拟机2》这本书,整个系列可以看做是学习笔记,自己是先通读了一遍该书,然后整理脑图,按照上面的大纲再逐篇整理。主要目的是理解虚拟机中一些高深又晦涩的概念,以及虚拟机底层运行的一些基本“常识”,打好这块的基础,以便后续更容易掌握Android平台上的热修复、插件化以及Hook技术等的一些原理。

JVM系列之内存区域

JVM系列之对象-Part1创建
JVM系列之对象-Part2布局
JVM系列之对象-Part3定位
JVM系列之对象-Part4是否还活着?

JVM系列之GC-Part1收集算法
JVM系列之GC-Part2收集器
JVM系列之GC-Part3GC分类
JVM系列之GC-Part4内存分配与回收策略

JVM系列之类加载-Part1类文件结构
JVM系列之类加载-Part2类加载机制
JVM系列之类加载-Part3字节码执行引擎

JVM系列之高效并发-Part1内存模型
JVM系列之高效并发-Part2Java与线程
JVM系列之高效并发-Part3线程安全与锁优化

1. 基础linux命令

描述 命令
清除屏幕 clear
重新初始化终端 reset
查看当前目录 pwd
将当前目录下的子文件&子目录平铺在控制台 ls
ls -ll
往控制台输出信息 echo 'test content'
echo 'test content' > test.txt
在当前目录下新建一个文件 touch [文件名]
查看对应文件的内容 cat [文件的url]
编辑文件 vim [文件的url]
  - 按i键进入插入模式
  - 按esc键进行命令执行
    * q! 强制退出(不保存)
    * wq 保存退出
    * set nu 设置行号
新建文件夹 mkdir [文件目录]
将对应目录下的子孙文件&子孙目录平铺在控制台 find [目录名]
将对应目录下的文件平铺在控制台 find [目录名] -typef
删除文件 rm [文件名]
删除文件夹 rm -r [文件目录]
重命名 mv [源文件] [重命名文件]
查看历史命令 history
退出 exit

2. 新建仓库

描述 命令
在当前目录新建一个git代码库 git init
新建一个目录,将其初始化为git代码库 git init [project-name]
下载一个项目和它到整个代码历史 git clone [url]

3. 配置

描述 命令
显示当前的git配置 git config --list
编辑git配置文件 git config -e [--global]
设置提交代码时的用户信息 git config [--global] user.name "[name]"
git config [--global] user.email "[email address]"

4. 增加/删除文件

描述 命令
添加当前目录的所有文件到暂存区 git add .
删除工作区文件,并且将这次删除放入暂存区 git rm [file1] [file2] …
停止追踪指定文件,但该文件会保留在工作区 git rm --cached [file1]
改名文件,并且将这个改名放入暂存区 git mv [file-original] [file-renamed]
Read more »

readnote-kotlin-core-programming-cover

1. 重点理解val的使用规则

1.1 引用1

  • 如果说var代表了varible(变量),那么val可看成value(值)的缩写。但也有人觉得这样并不直观或准确,而是把val解释成varible+final,即通过val声明的变量具有Java中的final关键字的效果,也就是引用不可变。
  • val声明的变量是只读变量,它的引用不可更改,但并不代表其引用对象也不可变。事实上,我们依然可以修改引用对象的可变成员。

1.2 引用2

  • 优先使用val来避免副作用
  • 在很多Kotlin的学习资料中,都会传递一个原则:优先使用val来声明变量。这相当正确,但更好的理解可以是:尽可能采用val、不可变对象及纯函数(其实就是没有副作用的函数,具备引用透明性)来设计程序
Read more »

2020-07-10

1. 现象

广告模块引入了很多三方sdk,这里的问题体现在google的admob上,期望是将admob升级到18.3.0版本,结果主工程中由于引入了firebase,具体来讲就是admob和firebase都引入了google的基础服务gms,但版本没统一导致了Duplicate class的错误。

2. 解决过程

2.1 先处理广告SDK问题

期望将admob升级到18.3.0版本

1
com.google.android.gms:play-services-ads:18.3.0

这个版本里面使用了

1
com.google.android.gms:play-services-measurement-base:17.1.0

我们项目中还使用了两个google的服务框架,分别如下(已是最高版本了):

1
2
com.google.android.gms:play-services-gcm:17.0.0
com.google.firebase:firebase-core:17.0.0

这两个包里面使用了com.google.android.gms:play-services-measurement-base:17.0.0,导致工程现在编译时报错:

1
Duplicate class com.google.android.gms.internal.measurement.zzio found in modules classes.jar (com.google.android.gms:play-services-measurement-base:17.1.0) and classes.jar (com.google.android.gms:play-services-measurement:17.0.0)

解决方法:将admob18.3.0降到18.0.0,因为这个版本里面是play-services-measurement-base:17.0.0

Read more »

2020-07-08

1. 现象

成功缓存2个mtg广告,但显示第2个时失败报错:is not ready(can’t show because load fail),报错的详细日志:

1
onShowFailure[mintegral_interstitial_ad] unitId is xxx, errorMessage is mtg interstitial video ad is not ready, errorCode is -2020007。

2. 初步分析

抓包+日志定位确定isNotReady有两种可能,①广告素材视频没下载完 ②广告间隔时间太长,offer过期。

  1. 我这边只有等mtgSdk回调了onVideoLoadSuccess才会标记缓存成功,如果没有缓存成功都没有机会调用show,也就是没有机会调起isReady的判断。所以不存在已回调了onVideoLoadSuccess但是视频没下载完的情况,如若这样就是mtgSdk内部回调的重大bug
  2. offer过期问题不存在,目前就是间隔几分钟
Read more »