Android事件分发分析

本篇所分析的源码为Android 28,可能与其他版本有所出入

流程概括

  1. 输入事件由FrameWork层发送到ViewRootImpl的内部类WindowInputEventReceiver上,根据InputEvent的类型,进行不同的分发方式。
  2. 如果是TouchEventViewRootImpl调用其保存的View.dispatchTouchEvent()方法,这个View一般是DecorView,如果是悬浮窗一类的直接是WindowManagerGlobal.addView()的那个View
  3. DecorView首先将该事件交由Window中的callback进行处理,也就是ActivityDialog
  4. Activity然后在交由Window也就是PhoneWindow处理
  5. PhoneWindow在交由内部保存的DecorView进行处理
  6. DecorView使用ViewGroup分发逻辑向下分发。
  7. ViewGroup首先判断自身是否拦截事件,如果onInterceptTouchEvent返回true,则自身消费事件。
  8. 如果不拦截,则将子View按z-order的顺序排序,根据MotionEvent的x、y寻找子View,将事件分发给它们。
  9. 如果子ViewViewGroup,则同样继续分发给子View
  10. 如果子ViewView,那么通过判断mOnTouchListeneronTouchEvent的返回值看是否事件消费
  11. 如果所有的子View都没有消费这个事件,则事件向上返回,由父ViewGroup决定自身是否消费。
  12. 最终ActivityWindow都不消费,则ViewRootImpl调用finishInputEvent结束本次事件。
阅读更多

Handler机制

本篇所分析的源码为Android 28

Handler经常被用于异步消息通知延时任务处理

在子线程中处理数据后,此时需要在UI线程回显数据,在主线程创建Handler对象后,子线程通过Handler.sendMessage()方法发送到handler的处理队列中,在handleMessage()方法中做相应的处理。

延时任务通过postDelayed()方法,一定时间后执行传入的Runnable对象。

源码分析

Handler中的关键类为Looper,其提供了一个MessageQueue消息队列,无论是将要发送的message还是执行的runnable都会被封装为Meassage类压入消息队列中,Looper中通过loop()方法中的死循环,根据压入的时间或延时时间,顺序分发队列中的消息。

阅读更多

精妙的位运算

位运算

  • 与运算(and)

    &表示,当两个相同位对应的数都是 1 的时候,该位获得的结果才是 1,否则为 0
    例如说6 & 11,转换为二进制就是0110 & 1011,结果为0010
    在位运算中常常与mask进行提取对应位值的操作。

  • 或运算(or)

    |表示,当两个相同位对应的数只要有一个数 1 的时候,该位获得的结果为 1,否则为 0
    还是用 6 和 11 这两个数做例子,就是0110 | 1011 = 1111在位运算中可以起到赋值的作用。

  • 非运算(not)

    ~表示将每一位按位取反(原来是 0 结果就是 1,原来是 1 结果就是 0)。~110 = 001
    在对Int值进行非运算时,因为计算机中存储的是补码,此时的非运算对补码进行取反后,再还原为原码后就会变为奇怪的值。
    为何~1等于-2,~0等于-1呢

  • 异或运算(xor)

    ^表示,当两个相同位对应的数字不同的时候为 1,否则为 0,可以用A ^ B == 0来判断两个数是否相等。例如说:0110 ^ 1011 = 1101可以用于判断两值是否相同并且提取出改变的位

  • 左移(shl)

    <<表示,a << b表示 a 左移 b 位,由于移位在末位多出来的未知数字补零。
    在这里面可以等价为a * 2^b这个运算(针对十进制)。0001 << 1 = 0010 相当于1 * 2^1 = 2

  • 右移(shr)

    >>表示,a >> b表示将 a 右移 b 位,原本的末位进行右移后会被舍弃,左边的用原有标志位补充,正数补0,负数补1。同样的,右移在十进制里面也可以近似为a / (2^b)的形式,不过要对结果取整,也不一定准确,只能够说意思大概如此。

  • 无符号右移

    >>>表示,不管正负标志位为 0 还是 1,将该数的二进制码整体右移,左边部分总是以 0 填充,右边部分舍弃。

运算优先级: ~ > << = >>= >>> > & > ^ > | > && > ||

阅读更多

Android屏幕适配知识总结

1. 概念解释

屏幕尺寸

手机对角线的物理尺寸,单位:英寸(inch),1英寸=2.54cm

屏幕分辨率

手机在横向、纵向上的像素点数的总和,由横向像素数 * 纵向像素数表示(1280*720),每个像素都是1px

屏幕PPI(屏幕像素密度)

表示每英寸像素数,则在相同尺寸下,PPI越高,则越清晰。在PPI相同的情况下,屏幕尺寸越大,越模糊。

PPI计算规则

eg:小米8的像素密度为402PPI

DPI

是印刷业使用的单位,其表示的是打印纸上每一英寸包含的墨点数量,网络上的观点是Google误用了该单位。在android中可以将PPI与DPI等价看待。

物理上DPI值是一个固定值,无法修改。但Android系统中的densityDpi参数可以通过root或adb命令修改。修改后会有一个Physical densityOverride density

Density

  • DisplayMetrics.density
    该density是为了方便px与dp的转换,额外提供了此参数,density = densityDpi / 160。
  • adb shell wm density
    这个density其实就是dpi的概念。

很多文章中的density也就是指的是dpi

DP

密度无关像素(density-independent pixel),与终端上的实际物理像素点无关。

在dpi = 160的设备上,1dp=1px。当dpi提升到240时,1dp所能表示的像素点提高,扩大到(240/160)=1.5倍,此时1dp=1.5dp。

dp与px转换可以使用 px = dp * (densityDpi / 160)或者px = dp * density

SP

可缩放像素,与dp基本相同,用于字体大小设置。唯一不同就是sp会随着系统设置的字体缩放进行缩放。

阅读更多

Gradle构建流程分析(一)- 初始化与Task创建

基于AGP3.4.2版本进行分析

获取源码

在build.gradle中添加implementation 'com.android.tools.build:gradle:3.4.2'Sync后即可看到源码

插件入口

获取到源码后,看/META-INF/gradle-plugins/com.android.application.properties文件内容为

1
implementation-class=com.android.build.gradle.AppPlugin

可以看出插件“com.android.application”对应为AppPlugin.java,在同级目录下的com.android.library.properties文件

1
implementation-class=com.android.build.gradle.LibraryPlugin

可以看出插件“com.android.library”为LibraryPlugin.java

阅读更多

设置屏幕缩放后缩放距离变为双倍

复现步骤(问题)

  1. 在Android6.0系统中设置屏幕缩放,向内缩小屏幕任意范围
  2. 在一定的条件下,如关闭一个dialog风格的activity、关闭popupwindow
  3. 会发现向内缩小的屏幕尺寸变大一倍

解决

通过Android Studio中的layout inspector分析发现:屏幕缩放的实现方式是在DecorView中设置padding进行屏幕变化,但出现问题后,一个id为action_bar_rootFitWindowsLinearLayout中也出现了相同数值的padding。找到相应的abc_screen_simple.xml后发现这个View上配置了android:fitsSystemWindows="true"

阅读更多

Android焦点机制

本篇所分析的源码为Android 28,可能与其他版本有所出入

焦点存储与设置

焦点的标志位

View中,自身的许多状态都是使用mPrivateFlags来记录,其中FOCUSABLE_MASK位为记录View的焦点状态

View.java
1
2
3
4
/**
* Mask for use with setFlags indicating bits used for focus.
*/
private static final int FOCUSABLE_MASK = 0x00000011;

View的焦点焦点状态共有3种NOT_FOCUSABLEFOCUSABLEFOCUSABLE_AUTO

View.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* This view does not want keystrokes.
*/
public static final int NOT_FOCUSABLE = 0x00000000;

/**
* This view wants keystrokes.
*/
public static final int FOCUSABLE = 0x00000001;

/**
* This view determines focusability automatically. This is the default.
*/
public static final int FOCUSABLE_AUTO = 0x00000010;

其中FOCUSABLE_AUTO为默认状态,当未在xml未配置android:focusable属性时,系统会将焦点状态设置为FOCUSABLE_AUTO

阅读更多

Android的TouchMode

什么是TouchMode

TouchMode就是”触摸模式“。在一般情况下,Android系统都是处于TouchMode的模式下也就是View.isInTouchMode() == true的状态下。大多Android开发都是开发的手机App应用,所以可能没有接触或使用过TouchMode,而在开发Android TV应用或其他没有触摸屏的应用时会接触到这个TouchMode,但一旦使用遥控器遥控或调用了View.requestFocusFromTouch等可以更改TouchMode的方法后,系统就会退出TouchMode,当用户点击屏幕后,就会进入触屏模式也就是TouchMode模式。

阅读更多

Unity2019与Android混合开发

0. 开始前的版本对齐

Unity版本:Unity2019.3.4f1
AndroidStudio版本:3.5.3

1. Unity – 准备项目

  1. 新建项目

  2. 打开File -> Build Setting

阅读更多

《Android开发艺术探索》笔记

第一章 Activty的生命周期和启动模式

Activity的生命周期全面分析

典型情况下的生命周期分析

在正常的情况下,Activity会经历如下生命周期。

  1. onCreate: 表示Activity正在被创建,是生命周期的第一个方法
  2. onRestart: 表示Activity正在被重新启动。一般由用户从ActivityA启动ActivityB后,重新返回ActivityA触发。
  3. onStart: 表示Activity已经可见,但无法与用户交互(没有获取到焦点)
  4. onResume: 表示activity获得了焦点,用户可以进行操作
  5. onPause: Activity正在停止,失去了焦点,不能进行操作。此时可以做一些存储数据、停止动画等工作,但不能太耗时,会影响新Activity的显示 (新Activity的onResume会在老Activity的onPause方法后执行)
  6. onStop: 表示Activity即将停止,对用户来说已经不可见。 可以在此时做稍重的回收工作,也不能太耗时。一般的操作尽量在onStop中执行,不要放到onPause中
  7. onDestory : 表示activity即将被销毁,是最后一个回调。可以做回收工作和最终资源的释放。
阅读更多