本文将介绍工作出现瓶颈,学习效率下降?通学这份Android进阶知识体系,从入门到高级只是时间问题!的详细情况,。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将涉及一些关
本文将介绍工作出现瓶颈,学习效率下降?通学这份Android进阶知识体系,从入门到高级只是时间问题!的详细情况,。我们将通过案例分析、数据研究等多种方式,帮助您更全面地了解这个主题,同时也将涉及一些关于2021 最新Android知识体系,【工作经验分享、2021最新Android知识体系总结,最全Android知识总结、Android DataBinding 从入门到进阶、Android 布局优化真的难,从入门到放弃的知识。
本文目录一览:- 工作出现瓶颈,学习效率下降?通学这份Android进阶知识体系,从入门到高级只是时间问题!
- 2021 最新Android知识体系,【工作经验分享
- 2021最新Android知识体系总结,最全Android知识总结
- Android DataBinding 从入门到进阶
- Android 布局优化真的难,从入门到放弃
工作出现瓶颈,学习效率下降?通学这份Android进阶知识体系,从入门到高级只是时间问题!
前言
工作一段时间一般都会出现一个瓶颈,这个时候学习的效率就会大打折扣,如果找不到好的方式,花的时间就会很长,然而进步的空间却很小。那么好的方式是什么呢?答案就是知识体系的思维导图。
构建自己的知识体系尤为重要,每个人都应该构建自己的知识体系,那么如何构建自己的知识体系呢?对于我来说,我是做 Android 开发的,从毕业到现在,大概六年多的时间,小公司呆过,大公司也呆过,自己独立开发过产品,同时也跟大团队一起开发过。因此基本上能经历的都会经历过,在这几年的开发时间里,我基本都会有学习总结的习惯,有的是工作上用到的,有的是平常想到的 idea ,我都会总结下来,然后抽空整理一下,写成博客。
这几天一直在准备构成大纲,前天后后大概花了四天时间来完成大纲思维导图。今天我就将它分享出来,欢迎大伙一起学习。
总纲
计算基础
- 数据结构与算法
- 操作系统
- 计算机网络
- 组成原理
编程语言
- Java
- ReactNative
- Flutter
Android学习
- 基础入门
- 进阶
- 高级
- 推荐书籍
面试总结
学习思维大纲主要由以上几大部分组成,基础部分,语言部分,还有其他方面的内容,不管工作还是生活都会涉及到,由于我是做 Android 开发的,因此主要会涉及 Android 开发的内容。接下来,我们就依次展开各个部分的详细大纲进行描述:
计算机基础
在计算机基础部分中,最重要的当然属于数据结构与算法,这部分在我们实际的工作中,用到的比较多,因此本公众号会着重总结所用到的各种数据结构以及算法。
数据结构
复杂度分析
- 时间复杂度
- 空间复杂度
线性表
- 数组
- 链表
- 单链表
- 双向链表
- 循环链表
- 静态链表
- 队列
- 顺序存储
- 链式存储
- 栈
- 顺序栈
- 链式栈
散列表
- 散列函数
- 冲突解决
树
- 二叉树
- 平衡二叉树
- 二叉查找树
- 平衡二叉树(AVL树,红黑树)
- 多路查找树
- B树
- B+树
- 2-3树
- 2-3-4树
- 堆
- 小顶堆
- 大顶堆
- 二项堆
- 裴波那契堆
- 遍历
- 深度优先
- 官渡优先
图
- 存储
- 邻接矩阵
- 邻接表
- 十字链表
- 最小生成树
- 遍历
- 最短路径
- 拓扑排序
算法
基本算法思想
- 贪心算法
- 分治算法
- 动态规划
- 回溯算法
- 枚举算法
排序
- O(n^2)
- 冒泡排序
- 插入排序
- 选择排序
- 希尔排序
查找
- 线性表查找
- 散列表查找
- 树结构查找
- 图查找
搜索
- 广度优先
- 深度优先
字串符
- 存储
- 模式匹配
编程语言
由于 Android 开发主要使用 Java 语言,因此对于 Java 的学习显得尤为重要,因此本公众号也会着重学习 Java 各方面的知识点,对于 Android 开发,Google 目前也主推 kotlin ,因此也会涉及到 kotlin 相关的内容。对于跨平台开发的内容,RN 和 Flutter 我们也会涉及到的。
基础
- JDK/JRE区别
- 数据类型
- 自动装包拆包
- 重载和重写区别
- 接口
- 多态
- 继承
- 内部类
高阶
集合框架
- Collection
- List(ArrayLlst,LinkedList,Vector)
- set(Treeset,HashSet)
- Map
- HahsMap
- HashTable
- TreeMap
- ArrayMap
- 多线程容器
- copyonwrteArrayLlst
- ConCurrencyHashMap
多线程/井发锁
- 生命周期
- 启动方式
- 线程安全/锁
- 线程池
进程/通信方式
- Socket
- 消息队列
- 管道
- 内存共享
- AIDL
- 信号量
反射
泛型
动态代理
垃圾回收和GC
- 分代回收
- 回收算法
- 垃圾回收机制
- 可达性分析
- 引用类型
JVM虚拟机
- classLoader
- JVM内存模型
- GC回收机制
注解
Android
接下来就是我们的重头戏 Android 部分的知识点,Android 部分的知识点多而且杂,因此做一个学习大纲很重要,将重要的知识点都以大纲的形式列出来,这对于我们学习 Android 开发帮助很大。
入门
Androld studio安装及插件使用
- 安装
- 常用插件
- GsonFormat
- Android Parcelable code generator
- Lifecycle sorter
- Adb WIFI
- ECTranslation
四大组件
- Actlvity
- Activty启动过程
- 启动模式
- 生命周期
- taskAtfinity
- intent
- onNewIntent
- Service
- startService
- bindService
- IntentService
- ContentProvider
- broadCaseReceiver
- 注册方式(动态,静态)
- 传递方式(有序,无序)
- 事件种类(自定义,系统)
- 范围(全局,本地)
组件持有的Context和Appllcation区别
fragment
- 懒加载
- 与Activity/fragment通信
view的基础知识
- 工作原理
- onMeasure
- onDraw(palnt,Bltmap,Canvas,Matrix,PorterDuftXtermode)
- onL ayout
- 基础
- 查看工具(GestureDetector,VelootyTracker,VlewConfiguration)
- 监听(onTouch,onTouchEvent,onCllck,执行顺序)
- 滑动
- 事件分发(dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent)
- 滑动冲突解决
常用控件/布局
- Recyclerview
- SupportLibrary
- v4
- v7
- v11
- Webview
- JSBridge
- Deeplink
- 首屏加速
- 内存泄漏
- Surfaceview
- Dialog
- SnackBar
- Notitication
- ActlonBar
- 约束布局
基础动画
- 属性动画
- 帧动画
- Animation补问动画
- MaterialDesign动画
MaterialDesign
- 状态栏/导航栏/沉漫式设计
资源
- 单位
- theme
- Layout
- Drawable
- .9patchEH
- Shape(gradient,size,stroke,padding,corners)
- selector
- 适配
进阶
进程/线程/事件
- 线程通信
- Handler
- Timer/TimerTask
- Thread/ThreadPoolExcutor
- AsyncTask
- IntentServlce
- 进程
- 优先级(可见进程,服务进程,后台进程,空进程,前台进程)
- 进程间通信(Messager,AIDL,BInder,序列化,匿名共享内存)
- 事件
- 事件总线
- 应用保活
- 白色保活
- 黑色保活
- 灰色保活
数据持久化
- 本地存储
- sqlite
- sharedPreterence
- 文件
- 数据格式
- Json
- xml
- 跨进程
- Serlalzable
- Parcelable
性能优化
- 内存优化
- 电量优化
- 数据传输优化
- 网络优化
- 缓存优化
- 其他优化(启动时间优化,布局优化,apk大小优化)
架构设计
- MVP
- MVC
- MVVM
- Androld Architecture Components
- Androld Jetpack
开源框架
- ORM数据库框架/GreenDao
- 图片缓存框架/Glide/Fresco/UlL/picasso
- 文件框架
- 网络框架/RetrofitlVolley/OKhttp
- 注入依赖/ButterKnite/Dagger2
- 序列化(gson/tastjson)
- 路由ARouter
- Rxjava
- 事件总銭EventBus
高级
Framework层源码
- UI组件
- 四大组件
- fragment
- context/application
- window
- webvlew
- recyclervlew
- 系统服务
- ActlvityManagerService
- Window/WIndowManager
- WindowManagerService
- ServlceManager
- 流程
- Androld系统启动流程
- 应用启动流程
- apk安装过程
- apk打包过程
- Androld系统
- 内存管理机制
- 任务管理机制
- 消息通信机制
- 安全机制
逆向安全
- root原理
- small字节码
- dex
- 加固和反加固
- hook
- ndk
- 混渐
- 签名
虚拟机
- Dalvlk虚拟机
- ART虚拟机
热门技术
- 插件话原理
- 组件化原理
- 热修复原理
结尾
对于其他部分的内容,我将准备部分涉及到,作为平常学习的润滑剂,主要的还是上面的三大部分,如果能将上面三大部分通学一遍,从入门到高级应该只是时间的问题。接来下的文章我们会分篇将对以上部分的大纲进行详细的记录学习总结。
关于程序员系统学习
我们都很渺小,在宇宙中甚至比不上一颗尘埃,可是人为何称之为人,就是因为人懂得思考,知道学习的重要性。并且随时愿意改变自己,重新出发。我们一生都在忙忙碌碌,为自己而活的时间太少太少,努力奔跑不仅仅是为了活着,更多的是实现我们自己的理想与报复。存在即为意义,我希望大家能跟我一样坚持,你不知道下一个黄金浪潮在哪里,但是只要你在里面,总会赶上的啊!下一个Android辉煌的时刻可能就在明天呢?
目前我们要做的就是选好自己细分领域深入研究,对基础原理性内容深入理解,尽快向高级水平靠近并达到。当然也要了解新技术,拥有开放的心态去学习一些新技术,不要一开始出来一个新技术就转入学习,这样会导致啥都会一点啥都不会,当然新技术是有一个发展过程的,不会立马流行和大量应用等它稍微成熟点。
这样你会少踩很多坑,也不要排斥它,完全不去理会和了解学习,正确的方式是简单了解这个技术产生是为了解决什么问题,有什么优缺点已经目前发展成熟度和应用范围,我们还是要多抓住技术的本质和基础,这样出现了新技术学起来也会很轻松,当然机会合适了再去学习它。 所以,找准自己的定位及细分领域。
多余的话就不讲了,接下来将分享面试的一个复习路线,如果你也在准备面试但是不知道怎么高效复习,可以参考一下我的复习路线,有任何问题也欢迎一起互相交流,加油吧!
首先是超级详细得不能再详细的Android开发学习思维导图,因为图片实在是太大了,所以我就只把二级目录的内容放出来,更加详细的你们可以私信获取。
接下来就需要梳理知识,提升储备了!(Android移动架构师七大专题学习资源)
-
架构师筑基必备技能:深入Java泛型+注解深入浅出+并发编程+数据传输与序列化+Java虚拟机原理+反射与类加载+动态代理+高效IO
-
Android高级UI与FrameWork源码:高级UI晋升+Framework内核解析+Android组件内核+数据持久化
-
360°全方面性能调优:设计思想与代码质量优化+程序性能优化+开发效率优化
-
解读开源框架设计思想:热修复设计+插件化框架解读+组件化框架设计+图片加载框架+网络访问框架设计+RXJava响应式编程框架设计+IOC架构设计+Android架构组件Jetpack
-
NDK模块开发:NDK基础知识体系+底层图片处理+音视频开发
-
微信小程序:小程序介绍+UI开发+API操作+微信对接
-
Hybrid 开发与Flutter:Html5项目实战+Flutter进阶
知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结。
然后再是通过源码来系统性地学习
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看api文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
《486页超全面Android开发相关源码精编解析》
刷大厂面试题备战,增加大厂通过率
历时半年,整理了这份市面上最全面的安卓面试题解析大全。
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《379页Android开发面试宝典》
以上内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。
最后还有耗时一年多整理的一系列Android学习资源:Android源码解析、Android第三方库源码笔记、Android进阶架构师七大专题学习、历年BAT面试题解析包、Android大佬学习笔记
等等,这些内容均免费分享给大家,需要完整版的朋友,点这里可以看到全部内容。
2021 最新Android知识体系,【工作经验分享
5.3 刷题
======
-
CS-Notes剑指Offer题解
-
CS-NotesLeetCode题解
-
JsonChao高频题集
6. Android基础
6.1 核心知识点
=========
6.1.1 四大组件
==========
-
Activity
-
Service
-
broadcastReceiver
-
ContentProvider
6.1.2 布局和控件
===========
-
RelativeLayout
-
FrameLayout
-
LinearLayout
-
ConstraintLayout
-
Button
-
TextView
-
RecyclerView
-
…
6.1.3 自定义view/ViewGroup
=======================
-
onMeasure
-
onLayout
-
onDraw
-
onTouchEvent
-
dispatchTouchEvent
-
自定义属性
6.1.4 动画和手势
===========
-
View动画
-
属性动画
-
layoutAnimation视图动画
-
手势检测(GestureDetector)
-
缩放手势检测(ScaleGestureDecetor)
6.1.5 网络
========
-
请求网络
-
解析数据
6.1.6 图片加载
==========
-
本地图片
-
网络图片
-
压缩图片
-
多图列表
-
diskLruCache
6.1.7 Handler
=============
-
Looper
-
Message
-
MessageQueue
-
内存泄漏
-
ThreadLocal
6.1.8 Android各版本新特性
===================
-
Android5.0
-
Android6.0
-
Android7.0
-
Android8.0(O)
-
Android9.0§
-
Android10.0(Q)
-
Android11.0®
6.1.9 其他
========
-
adb常用命令
-
文件和数据库
-
异步线程池
-
Resources
6.2 开源库使用
=========
-
Retrofit/OKhttp
-
RxJava
-
Glide
-
注解框架
-
Jetpack
6.3 性能优化
========
6.3.1 快-流畅的体验
=============
-
布局优化
-
绘制优化
-
内存优化
-
启动优化
-
其他
6.3.2 稳-稳定
==========
-
避免内存泄露
-
避免崩溃
6.3.3 省-省电/流量
=============
-
使用JobScheduler调度任务
-
使用懒惰法则
6.3.4 小-安装包小
============
-
apk构成
-
包体优化
7. Android进阶
7.1 核心知识点
=========
7.1.1 多进程
=========
-
Binder
-
AIDL
-
Messenger
7.1.2 Activity难点
================
-
onSaveInstanceState()和onRestoreInstanceState()
-
intent-filter匹配规则
-
生命周期
-
启动模式
-
Activity启动过程
-
App启动过程
7.1.3 Service难点
===============
-
startService流程
-
bindService流程
7.1.4 broadcastReceiver难点
=========================
- 注册/发送/接收工作原理
7.1.5 ContentProvider难点
=======================
- ContentProvider启动过程
7.1.6
=====
-
View绘制
-
事件分发
-
消息队列
-
AsyncTask原理
-
RemoteViews
-
Window和ViewRootImpl
7.2 开源库原理
=========
-
OkHttp
-
Retrofit
-
RxJava
-
Glide
-
LeakCanary
-
GreenDao
-
ARouter
7.3 Gradle
==========
-
编译
-
混淆
-
签名
-
Groovy
-
Gradle构建流程
-
GradlePlugin
-
Gradle构建优化
-
App构建过程
-
差异化打包
7.4 初级架构
========
-
设计模式
-
MVC,MVP,MVVM
7.5 JNI
=======
-
JNI基础
-
NDK基础
7.6 动态化
=======
7.6.1 前置知识
==========
-
Android打包流程
-
Java字节码
-
GradlePlugin
-
TransformAPI
-
ASM
7.6.2 热修复:nuwa&Tinker
=====================
-
经典-64k问题
-
dex分包
-
MultiDex优化
-
findClass原理
-
QQ空间热补丁方案
-
Nuwa应用层实现
-
NuwaGradle
7.6.3 插件化:VirtualAPK
====================
-
加载类
-
资源访问
-
生命周期管理
7.7 编译插桩
========
-
AOP
-
ASM
7.8 Framework
=============
7.8.1 系统服务
==========
-
Zygote启动
-
Android系统启动
-
ServiceManager启动及工作原理
7.8.2 应用进程
==========
-
启动流程
-
启用Binder机制
-
Application初始化流程
-
Context
7.8.3 UI体系
==========
-
显示原理
-
UI线程启动
-
屏幕刷新机制
-
surface
-
vsync
7.8.4 进程通信
==========
-
Binder
-
IPC通信流程
-
Binder对象跨进程传递
-
OneWay机制
7.8.5 线程通信
==========
-
消息队列
-
消息传递机制
-
Handler消息延迟实现
-
IdleHandler原理
-
消息屏障
-
ThreadLocal原理
7.8.6 其他
========
-
跨进程传递大图片
-
四大组件启动原理
8. 领域专家
8.1 性能优化方向
==========
-
稳定性优化
-
启动速度优化
-
绘制优化
-
内存优化
-
包体积优化
-
网络优化
-
IO优化
-
存储优化
-
耗电优化
8.2 基础架构方向
==========
-
Android工程实践
-
Gradle自动化构建
-
编译插桩技术
-
Android架构
-
Android热修复
-
Android插件化
-
Android核心源码
8.3 NDK、音视频
===========
-
C
-
C++
-
JNI基础
-
编译原理与语法基础
-
Linux基础
-
热修复
-
gif图实现
-
音视频基础
-
ffmpeg
-
WebRTC
-
opengl
-
vulkan
-
opencv
8.4 大前端
=======
8.4.1 前端基础
==========
-
HTML
-
CSS
-
JavaScript
8.4.2 Flutter(⭐⭐)
=================
-
Dart入门
-
Flutter入门
-
开发
-
工程管理
-
线上运维
-
发布
-
测试调试
8.4.3 其他
========
-
ReactNative
-
小程序
-
性能优化
9. 其他Android相关
9.1 Koltin(⭐⭐)
==============
9.1.1 基础部分
==========
========
-
Android工程实践
-
Gradle自动化构建
-
编译插桩技术
-
Android架构
-
Android热修复
-
Android插件化
-
Android核心源码
8.3 NDK、音视频
===========
-
C
-
C++
-
JNI基础
-
编译原理与语法基础
-
Linux基础
-
热修复
-
gif图实现
-
音视频基础
-
ffmpeg
-
WebRTC
-
opengl
-
vulkan
-
opencv
8.4 大前端
=======
8.4.1 前端基础
==========
-
HTML
-
CSS
-
JavaScript
8.4.2 Flutter(⭐⭐)
=================
-
Dart入门
-
Flutter入门
-
开发
-
工程管理
-
线上运维
-
发布
-
测试调试
8.4.3 其他
========
-
ReactNative
-
小程序
-
性能优化
9. 其他Android相关
9.1 Koltin(⭐⭐)
==============
9.1.1 基础部分
==========
2021最新Android知识体系总结,最全Android知识总结
前言
Hi~,我是 2020 届物联网专业毕业生,现就读于杭州。谨以此文来记录我的秋招以及入门前端以来的学习历程,如有错误,希望大家能及时提出!
面试情况
前前后后一共面试了 14 家公司的前端岗,按城市划分为:
- 北京:小米,京东,美团,百度,去哪儿
- 杭州:阿里,网易,微店,字节跳动
- 上海:七牛云,哈啰出行
- 深圳:腾讯,富途,乐信
历时4个月,最终收获了 小米、京东、微店、字节跳动 的offer
八大库
1.libavutil
核心工具库,最基础模块之一,其他模块都会依赖该库做一些基本的音视频处理操作
2. libavformat
文件格式和协议库,封装了Protocol层和Demuxer、muxer层,使得协议和格式对于开发者来说是透明的
3. libavcodec
编解码库,封装了Codec层,但是有一些codec是具备自己的License的,FFmpe不会默认添加像libx264、FDK-AAC、Lame等库,但是FFmpeg想一个平台,可以将其他的第三方codec以插件的方式添加进来,为开发者提供统一接口
4.libavfilter
音视频滤镜库,该模块包含了音频特效和视频特效的处理,在使用FFmpeg的API进行编解码的过程中,可以使用该模块高效的为音视频数据做特效处理
5. libavdevice
输入输出设备库,比如需要编译出播放声音或者视频的工具ffplay,就需要确保该模块是打开的,同事也需要libsdl的预先编译,该设备模块播放声音和视频都又是使用libsdl库
6.libswresample
用于音频重采样,可以对数字音频进行声道数、数据格式、采样率等多种基本信息的转换
7.libswscale
该模块用于图像格式转换,可以将YUV的数据转换为RGB的数据
8.libpostproc
该模块用于进行后期处理,当我们使用filter的时候,需要打开这个模块,filter会用到这个模块的一些基础函数
比较老的ffmpeg还会编译出avresamle模块,也是用于对音频原始出具进行重采样的,但是已经被废弃,推荐使用libswresample替代
另外,库里还可以包含对H.264/MPEG-4 AVC视频编码的X264库,是最常用的有损视频编码器,支持CBR、VBR模式,可以在编码的过程中直接改变码率的设置,在直播的场景中非常适用!可以做码率自适应的功能。
常用函数
1.av_register_all():注册所有组件
2.avformat_open_input():打开输入视频文件
3.avformat_find_stream_info():获取视频文件信息
4.avcodec_find_decoder():查找解码器
5.avcodec_open1():打开解码器
6.av_read_frame():从输入文件读取一帧压缩数据
7.avcodec_decode_video2():解码一桢压缩数据
8.avcodec_close():关闭解码器
9.avformat_close_input():关闭输入视频文件
最后,如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。
小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!
不论遇到什么困难,都不应该成为我们放弃的理由!
很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,需要一份小编整理出来的学习资料的关注我主页或者点击我的GitHub免费领取~
这里是关于我自己的Android 学习,面试文档,视频收集大整理,有兴趣的伙伴们可以看看~
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。
Android DataBinding 从入门到进阶
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持
android {
dataBinding {
enabled = true
}
}
一、基础入门
启用 DataBinding 后,这里先来看下如何在布局文件中绑定指定的变量
打开布局文件,选中根布局的 ViewGroup,按住 Alt + 回车键,点击 “Convert to data binding layout”,就可以生成 DataBinding 需要的布局规则
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</android.support.constraint.ConstraintLayout>
</layout>
和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道
这里先来声明一个 Modle
package com.leavesc.databinding_demo.model;
/**
* 作者:叶应是叶
* 时间:2018/5/16 20:20
* 描述:https://github.com/leavesC
*/
public class User {
private String name;
private String password;
···
}
在 data 标签里声明要使用到的变量名、类的全路径
<data>
<variable
name="userInfo"
type="com.leavesc.databinding_demo.model.User" />
</data>
如果 User 类型要多处用到,也可以直接将之 import 进来,这样就不用每次都指明整个包名路径了,而 java.lang.*
包中的类会被自动导入,所以可以直接使用
<data>
<import type="com.leavesc.databinding_demo.model.User"/>
<variable
name="userInfo"
type="User"/>
</data>
如果存在 import 的类名相同的情况,可以使用 alias 指定别名
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<import
alias="TempUser"
type="com.leavesc.databinding_demo.model2.User" />
<variable
name="userInfo"
type="User" />
<variable
name="tempUserInfo"
type="TempUser" />
</data>
这里声明了一个 User 类型的变量 userInfo,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置 userInfo 的变量值同时使 TextView 显示相应的文本 完整的布局代码如下所示
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<variable
name="userInfo"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical"
tools:context="com.leavesc.databinding_demo.Main2Activity">
<TextView
android:id="@+id/tv_userName"
···
android:text="@{userInfo.name}" />
<TextView
···
android:text="@{userInfo.password}" />
</LinearLayout>
</layout>
通过 @{userInfo.name}
使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法 之后可以在 Activity 中通过 DataBindingUtil
设置布局文件,省略原先 Activity 的 setContentView()
方法,并为变量 userInfo 赋值
private User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain2Binding activityMain2Binding = DataBindingUtil.setContentView(this, R.layout.activity_main2);
user = new User("leavesC", "123456");
activityMain2Binding.setUserInfo(user);
}
由于 @{userInfo.name}
在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号
android:text="@{userInfo.name,default=defaultValue}"
此外,也可以通过 ActivityMain2Binding 直接获取到指定 ID 的控件
activityMain2Binding.tvUserName.setText("leavesC");
每个数据绑定布局文件都会生成一个绑定类,ViewDataBinding 的实例名是根据布局文件名来生成,将之改为首字母大写的驼峰命名法来命名,并省略布局文件名包含的下划线。控件的获取方式类似,但首字母小写
也可以通过如下方式自定义 ViewDataBinding 的实例名
<data>
</data>
此外,在绑定表达式中会根据需要生成一个名为context
的特殊变量,context
的值是根 View 的getContext()
方法返回的Context
对象, context
变量会被具有该名称的显式变量声明所覆盖
Databinding 同样是支持在 Fragment 和 RecyclerView 中使用 。例如,可以看 Databinding 在 Fragment 中的使用
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentBlankBinding fragmentBlankBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_blank, container, false);
fragmentBlankBinding.setHint("Hello");
return fragmentBlankBinding.getRoot();
}
**以上实现数据绑定的方式,每当绑定的变量发生变化的时候,都需要重新向 ViewDataBinding 传递新的变量值才能刷新 UI 。接下来看如何实现自动刷新 UI **
二、单向数据绑定
实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable
、ObservableField
、ObservableCollection
BaseObservable
一个纯净的 ViewModel 类被更新后,并不会让 UI 自动更新。而数据绑定后,我们自然会希望数据变更后 UI 会即时刷新,Observable 就是为此而生的概念
BaseObservable 提供了 notifyChange() 和 notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图
/**
* 作者:叶应是叶
* 时间:2018/5/16 20:54
* 描述:
*/
public class Goods extends BaseObservable {
//如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
@Bindable
public String name;
//如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
private String details;
private float price;
public Goods(String name, String details, float price) {
this.name = name;
this.details = details;
this.price = price;
}
public void setName(String name) {
this.name = name;
//只更新本字段
notifyPropertyChanged(com.leavesc.databinding_demo.BR.name);
}
@Bindable
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
//更新所有字段
notifyChange();
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
在 setName() 方法中更新的只是本字段,而 setDetails() 方法中更新的是所有字段
添加两个按钮用于改变 goods 变量的三个属性值,由此可以看出两个 notify 方法的区别。当中涉及的按钮点击事件绑定,在下面也会讲到
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.Goods" />
<import type="com.leavesc.databinding_demo.Main3Activity.GoodsHandler" />
<variable
name="goods"
type="Goods" />
<variable
name="goodsHandler"
type="GoodsHandler" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp"
tools:context=".Main3Activity">
<TextView
···
android:text="@{goods.name}" />
<TextView
···
android:text="@{goods.details}" />
<TextView
···
android:text="@{String.valueOf(goods.price)}" />
<Button
···
android:onClick="@{()->goodsHandler.changeGoodsName()}"
android:text="改变属性 name 和 price"
android:textAllCaps="false" />
<Button
···
android:onClick="@{()->goodsHandler.changeGoodsDetails()}"
android:text="改变属性 details 和 price"
android:textAllCaps="false" />
</LinearLayout>
</layout>
/**
* 作者:叶应是叶
* 时间:2018/5/16 21:07
* 描述:
*/
public class Main3Activity extends AppCompatActivity {
private Goods goods;
private ActivityMain3Binding activityMain3Binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
activityMain3Binding = DataBindingUtil.setContentView(this, R.layout.activity_main3);
goods = new Goods("code", "hi", 24);
activityMain3Binding.setGoods(goods);
activityMain3Binding.setGoodsHandler(new GoodsHandler());
}
public class GoodsHandler {
public void changeGoodsName() {
goods.setName("code" + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void changeGoodsDetails() {
goods.setDetails("hi" + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
}
}
可以看到,name 视图的刷新没有同时刷新 price 视图,而 details 视图刷新的同时也刷新了 price 视图
实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback
当中 propertyId
就用于标识特定的字段
goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == com.leavesc.databinding_demo.BR.name) {
Log.e(TAG, "BR.name");
} else if (propertyId == com.leavesc.databinding_demo.BR.details) {
Log.e(TAG, "BR.details");
} else if (propertyId == com.leavesc.databinding_demo.BR._all) {
Log.e(TAG, "BR._all");
} else {
Log.e(TAG, "未知");
}
}
});
ObservableField
继承于 Observable 类相对来说限制有点高,且也需要进行 notify 操作,因此为了简单起见可以选择使用 ObservableField。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型
/**
* 作者:叶应是叶
* 时间:2018/5/13 21:33
* 描述:
*/
public class ObservableGoods {
private ObservableField<String> name;
private ObservableFloat price;
private ObservableField<String> details;
public ObservableGoods(String name, float price, String details) {
this.name = new ObservableField<>(name);
this.price = new ObservableFloat(price);
this.details = new ObservableField<>(details);
}
}
对 ObservableGoods 属性值的改变都会立即触发 UI 刷新,概念上与 Observable 区别不大,具体效果可看下面提供的源代码,这里不再赘述
ObservableCollection
--------------------
dataBinding 也提供了包装类用于替代原生的 `List` 和 `Map`,分别是 `ObservableList` 和 `ObservableMap`,当其包含的数据发生变化时,绑定的视图也会随之进行刷新
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.databinding.ObservableList"/>
<import type="android.databinding.ObservableMap"/>
<variable
name="list"
type="ObservableList<String>"/>
<variable
name="map"
type="ObservableMap<String,String>"/>
<variable
name="index"
type="int"/>
<variable
name="key"
type="String"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.leavesc.databinding_demo.Main12Activity">
<TextView
···
android:padding="20dp"
android:text="@{list[index],default=xx}"/>
<TextView
···
android:layout_marginTop="20dp"
android:padding="20dp"
android:text="@{map[key],default=yy}"/>
<Button
···
android:onClick="onClick"
android:text="改变数据"/>
</LinearLayout>
</layout>
private ObservableMap<String, String> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain12Binding activityMain12Binding = DataBindingUtil.setContentView(this, R.layout.activity_main12);
map = new ObservableArrayMap<>();
map.put("name", "leavesC");
map.put("age", "24");
activityMain12Binding.setMap(map);
ObservableList<String> list = new ObservableArrayList<>();
list.add("Ye");
list.add("leavesC");
activityMain12Binding.setList(list);
activityMain12Binding.setIndex(0);
activityMain12Binding.setKey("name");
}
public void onClick(View view) {
map.put("name", "leavesC,hi" + new Random().nextInt(100));
}

三、双向数据绑定
========
双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
看以下例子,当 EditText 的输入内容改变时,会同时同步到变量 `goods`,绑定变量的方式比单向绑定多了一个等号: `android:text="@={goods.name}"`
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.ObservableGoods"/>
<variable
name="goods"
type="ObservableGoods" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main10Activity">
<TextView
···
android:text="@{goods.name}" />
<EditText
···
android:text="@={goods.name}" />
</LinearLayout>
</layout>
public class Main10Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMain10Binding activityMain10Binding = DataBindingUtil.setContentView(this, R.layout.activity_main10);
ObservableGoods goods = new ObservableGoods("code", "hi", 23);
activityMain10Binding.setGoods(goods);
}
}

四、事件绑定
======
严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已 事件绑定可用于以下多种回调事件
* android:onClick
* android:onLongClick
* android:afterTextChanged
* android:onTextChanged
* ...
在 Activity 内部新建一个 **UserPresenter** 类来声明 **onClick()** 和 **afterTextChanged()** 事件相应的回调方法
public class UserPresenter {
public void onUserNameClick(User user) {
Toast.makeText(Main5Activity.this, "用户名:" + user.getName(), Toast.LENGTH_SHORT).show();
}
public void afterTextChanged(Editable s) {
user.setName(s.toString());
activityMain5Binding.setUserInfo(user);
}
public void afterUserPasswordChanged(Editable s) {
user.setPassword(s.toString());
activityMain5Binding.setUserInfo(user);
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<import type="com.leavesc.databinding_demo.MainActivity.UserPresenter" />
<variable
name="userInfo"
type="User" />
<variable
name="userPresenter"
type="UserPresenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="20dp"
android:orientation="vertical"
tools:context="com.leavesc.databinding_demo.MainActivity">
<TextView
···
android:onClick="@{()->userPresenter.onUserNameClick(userInfo)}"
android:text="@{userInfo.name}" />
<TextView
···
android:text="@{userInfo.password}" />
<EditText
···
android:afterTextChanged="@{userPresenter.afterTextChanged}"
android:hint="用户名" />
<EditText
···
android:afterTextChanged="@{userPresenter.afterUserPasswordChanged}"
android:hint="密码" />
</LinearLayout>
</layout>
方法引用的方式与调用函数的方式类似,既可以选择保持事件回调方法的签名一致:**@{userPresenter.afterTextChanged}**,此时方法名可以不一样,但方法参数和返回值必须和原始的回调函数保持一致。也可以引用不遵循默认签名的函数:**@{()->userPresenter.onUserNameClick(userInfo)}**,这里用到了 Lambda 表达式,这样就可以不遵循默认的方法签名,将`userInfo`对象直接传回点击方法中。此外,也可以使用方法引用 **::** 的形式来进行事件绑定 
五、使用类方法
=======
首先定义一个静态方法
public class StringUtils {
public static String toUpperCase(String str) {
return str.toUpperCase();
}
}
在 data 标签中导入该工具类
<import type="com.leavesc.databinding_demo.StringUtils" />
然后就可以像对待一般的函数一样来调用了
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->userPresenter.onUserNameClick(userInfo)}"
android:text="@{StringUtils.toUpperCase(userInfo.name)}" />
六、运算符
=====
基础运算符
-----
DataBinding 支持在布局文件中使用以下运算符、表达式和关键字
* 算术 + - / \* %
* 字符串合并 +
* 逻辑 && ||
* 二元 & | ^
* 一元 + - ! ~
* 移位 >> >>> <<
* 比较 == > < >= <=
* Instanceof
* Grouping ()
* character, String, numeric, null
* Cast
* 方法调用
* Field 访问
* Array 访问 \[\]
* 三元 ?:
目前不支持以下操作
* this
* super
* new
* 显示泛型调用
此外,DataBinding 还支持以下几种形式的调用
Null Coalescing
---------------
空合并运算符 **??** 会取第一个不为 null 的值作为返回值
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{user.name ?? user.password}" />
等价于
android:text="@{user.name != null ? user.name : user.password}"
属性控制
----
可以通过变量值来控制 View 的属性
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="可见性变化"
android:visibility="@{user.male ? View.VISIBLE : View.GONE}" />
避免空指针异常
-------
DataBinding 也会自动帮助我们避免空指针异常 例如,如果 **"@{userInfo.password}"** 中 **userInfo** 为 **null** 的话,**userInfo.password** 会被赋值为默认值 **null**,而不会抛出空指针异常
七、include 和 viewStub
====================
include
-------
对于 include 的布局文件,一样是支持通过 dataBinding 来进行数据绑定,此时一样需要在待 include 的布局中依然使用 layout 标签,声明需要使用到的变量
`view_include.xml`
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<variable
name="userInfo"
type="User" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#acc">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:text="@{userInfo.name}" />
</android.support.constraint.ConstraintLayout>
</layout>
在主布局文件中将相应的变量传递给 include 布局,从而使两个布局文件之间共享同一个变量
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.User" />
<variable
name="userInfo"
type="User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main6Activity">
<include
layout="@layout/view_include"
bind:userInfo="@{userInfo}" />
</LinearLayout>
</layout>
viewStub
--------
dataBinding 一样支持 ViewStub 布局
在布局文件中引用 viewStub 布局
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/view_stub"/>
获取到 ViewStub 对象,由此就可以来控制 ViewStub 的可见性
ActivityMain6Binding activityMain6Binding = DataBindingUtil.setContentView(this, R.layout.activity_main6);
View view = activityMain6Binding.viewStub.getViewStub().inflate();
如果需要为 ViewStub 绑定变量值,则 ViewStub 文件一样要使用 layout 标签进行布局,主布局文件使用自定义的 bind 命名空间将变量传递给 ViewStub
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/view_stub"
bind:userInfo="@{userInfo}" />
如果在 xml 中没有使用 `bind:userInfo="@{userInf}"` 对 ViewStub 进行数据绑定,则可以等到当 ViewStub **Inflate** 时再绑定变量,此时需要为 ViewStub 设置 `setOnInflateListener`回调函数,在回调函数中进行数据绑定
activityMain6Binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
//如果在 xml 中没有使用 bind:userInfo="@{userInf}" 对 viewStub 进行数据绑定
//那么可以在此处进行手动绑定
ViewStubBinding viewStubBinding = DataBindingUtil.bind(inflated);
viewStubBinding.setUserInfo(user);
Log.e(TAG, "onInflate");
}
});
八、BindingAdapter
================
dataBinding 提供了 **BindingAdapter** 这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 `android:src`、`android:text`等,也可以自定义属性然后在 xml 中使用
例如,对于一个 ImageView ,我们希望在某个变量值发生变化时,可以动态改变显示的图片,此时就可以通过 BindingAdapter 来实现
需要先定义一个静态方法,为之添加 BindingAdapter 注解,注解值是为 ImageView 控件自定义的属性名,而该静态方法的两个参数可以这样来理解:当 ImageView 控件的 url 属性值发生变化时,dataBinding 就会将 ImageView 实例以及新的 url 值传递给 loadImage() 方法,从而可以在此动态改变 ImageView 的相关属性
@BindingAdapter({"url"})
public static void loadImage(ImageView view, String url) {
Log.e(TAG, "loadImage url : " + url);
}
在 xml 文件中关联变量值,当中,bind 这个名称可以自定义
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.leavesc.databinding_demo.model.Image" />
<variable
name="image"
type="Image" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Main8Activity">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher_background"
bind:url="@{image.url}" />
</android.support.constraint.ConstraintLayout>
</layout>
BindingAdapter 更为强大的一点是可以覆盖 Android 原先的控件属性。例如,可以设定每一个 Button 的文本都要加上后缀:“-Button”
@BindingAdapter("android:text")
public static void setText(Button view, String text) {
view.setText(text + "-Button");
}
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->handler.onClick(image)}"
android:text=''@{"改变图片Url"}''/>
这样,整个工程中使用到了 **"android:text"** 这个属性的控件,其显示的文本就会多出一个后缀

九、BindingConversion
===================
dataBinding 还支持对数据进行转换,或者进行类型转换
与 BindingAdapter 类似,以下方法会将布局文件中所有以`@{String}`方式引用到的`String`类型变量加上后缀`-conversionString`
@BindingConversion
public static String conversionString(String text) {
return text + "-conversionString";
}
xml 文件
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=''@{"xxx"}''
android:textAllCaps="false"/>

可以看到,对于 Button 来说,BindingAdapter 和 BindingConversion 同时生效了,而 BindingConversion 的优先级要高些
此外,BindingConversion 也可以用于转换属性值的类型
看以下布局,此处在向 `background` 和 `textColor` 两个属性赋值时,直接就使用了字符串,按正常情况来说这自然是会报错的,但有了 BindingConversion 后就可以自动将字符串类型的值转为需要的 `Drawable` 和 `Color` 了
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background=''@{"红色"}''
android:padding="20dp"
android:text="红色背景蓝色字"
android:textColor=''@{"蓝色"}''/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background=''@{"蓝色"}''
android:padding="20dp"
android:text="蓝色背景红色字"
android:textColor=''@{"红色"}''/>
@BindingConversion
public static Drawable convertStringToDrawable(String str) {
if (str.equals("红色")) {
return new ColorDrawable(Color.parseColor("#FF4081"));
}
if (str.equals("蓝色")) {
return new ColorDrawable(Color.parseColor("#3F51B5"));
}
return new ColorDrawable(Color.parseColor("#344567"));
}
@BindingConversion
public static int convertStringToColor(String str) {
if (str.equals("红色")) {
return Color.parseColor("#FF4081");
}
if (str.equals("蓝色")) {
return Color.parseColor("#3F51B5");
}
return Color.parseColor("#344567");
}

十、Array、List、Set、Map ...
========================
dataBinding 也支持在布局文件中使用 **数组、Lsit、Set 和 Map**,且在布局文件中都可以通过 `list[index]` 的形式来获取元素
而为了和 **variable** 标签的尖括号区分开,在声明 **Lsit< String >** 之类的数据类型时,需要使用尖括号的转义字符
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="java.util.List" />
<import type="java.util.Map" />
<import type="java.util.Set" />
<import type="android.util.SparseArray" />
<variable
name="array"
type="String[]" />
<variable
name="list"
type="List<String>" />
<variable
name="map"
type="Map<String, String>" />
<variable
name="set"
type="Set<String>" />
<variable
name="sparse"
type="SparseArray<String>" />
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".Main7Activity">
<TextView
···
android:text="@{array[1]}" />
<TextView
···
android:text="@{sparse[index]}" />
<TextView
···
android:text="@{list[index]}" />
<TextView
···
android:text="@{map[key]}" />
<TextView
···
android:text=''@{map["leavesC"]}'' />
<TextView
···
android:text=''@{set.contains("xxx")?"xxx":key}'' />
</LinearLayout>
</layout>
十一、资源引用
=======
dataBinding 支持对尺寸和字符串这类资源的访问
`dimens.xml`
<dimen name="paddingBig">190dp</dimen>
<dimen name="paddingSmall">150dp</dimen>
`strings.xml`
<string name="format">%s is %s</string>
<data>
<variable
name="flag"
type="boolean" />
</data>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@{flag ? @dimen/paddingBig:@dimen/paddingSmall}"
android:text=''@{@string/format("leavesC", "Ye")}''
android:textAllCaps="false" />
对 DataBinding 的介绍到这里也就结束,当然,肯定还有些遗落的知识点,不过大体上我自认也已经讲得很清楚了,剩下的就留待日后补充了
#### 相关视频推荐:
[【2021最新版】Android studio安装教程+Android(安卓)零基础教程视频(适合Android 0基础,Android初学入门)含音视频_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1Jb4y187C4?spm_id_from=333.999.0.0)
[金三银四面试高峰期必问MVVM技术之databinding_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV19b4y1t71c?spm_id_from=333.999.0.0)
[【 Android进阶教程】——Jetpack DataBinding使用及原理_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV11M4y1w7CU?spm_id_from=333.999.0.0)
Android 布局优化真的难,从入门到放弃
前言
Android的绘制优化其实可以分为两个部分,即布局(UI)优化和卡顿优化,而布局优化的核心问题就是要解决因布局渲染性能不佳而导致应用卡顿的问题,所以它可以认为是卡顿优化的一个子集。
本文主要包括以下内容:
1.为什么要进行布局优化及android绘制,布局加载原理。
2.获取布局文件加载耗时的方法。
3.介绍一些布局优化的手段与方法。
4.为什么放弃使用这些优化方法?
1为什么要进行布局优化?
为什么要进行布局优化?
答案是显而易见的,如果布局嵌套过深,或者其他原因导致布局渲染性能不佳,可能会导致应用卡顿。
那么布局到底是如何导致渲染性能不佳的呢?首先我们应该了解下android绘制原理与布局加载原理。
android绘制原理
Android的屏幕刷新中涉及到最重要的三个概念(为便于理解,这里先做简单介绍)。
1、CPU:执行应用层的measure、layout、draw等操作,绘制完成后将数据提交给GPU。
2、GPU:进一步处理数据,并将数据缓存起来。
3、屏幕:由一个个像素点组成,以固定的频率(16.6ms,即1秒60帧)从缓冲区中取出数据来填充像素点。
总结一句话就是:CPU 绘制后提交数据、GPU 进一步处理和缓存数据、最后屏幕从缓冲区中读取数据并显示。
双缓冲机制
看完上面的流程图,我们很容易想到一个问题,屏幕是以16.6ms的固定频率进行刷新的,但是我们应用层触发绘制的时机是完全随机的(比如我们随时都可以触摸屏幕触发绘制)。
如果在GPU向缓冲区写入数据的同时,屏幕也在向缓冲区读取数据,会发生什么情况呢?
有可能屏幕上就会出现一部分是前一帧的画面,一部分是另一帧的画面,这显然是无法接受的,那怎么解决这个问题呢?
所以,在屏幕刷新中,Android系统引入了双缓冲机制。
GPU只向Back Buffer中写入绘制数据,且GPU会定期交换Back Buffer和Frame Buffer,交换的频率也是60次/秒,这就与屏幕的刷新频率保持了同步。
虽然我们引入了双缓冲机制,但是我们知道,当布局比较复杂,或设备性能较差的时候,CPU并不能保证在16.6ms内就完成绘制数据的计算,所以这里系统又做了一个处理。
当你的应用正在往Back Buffer中填充数据时,系统会将Back Buffer锁定。
如果到了GPU交换两个Buffer的时间点,你的应用还在往Back Buffer中填充数据,GPU会发现Back Buffer被锁定了,它会放弃这次交换。
这样做的后果就是手机屏幕仍然显示原先的图像,这就是我们常常说的掉帧。
布局加载原理
由上面可知,导致掉帧的原因是CPU无法在16.6ms内完成绘制数据的计算。
而之所以布局加载可能会导致掉帧,正是因为它在主线程上进行了耗时操作,可能导致CPU无法按时完成数据计算。
布局加载主要通过setContentView来实现,我们就不在这里贴源码了,一起来看看它的时序图。
我们可以看到,在setContentView中主要有两个耗时操作:
1.解析xml,获取XmlResourceParser,这是IO过程。
2.通过createViewFromTag,创建View对象,用到了反射。
以上两点就是布局加载可能导致卡顿的原因,也是布局的性能瓶颈。
2获取布局文件加载耗时的方法
我们如果需要优化布局卡顿问题,首先最重要的就是:确定定量标准。
所以我们首先介绍几种获取布局文件加载耗时的方法。
常规获取
首先介绍一下常规方法:
val start = System.currentTimeMillis()
setContentView(R.layout.activity_layout_optimize)
val inflateTime = System.currentTimeMillis() - start
这种方法很简单,因为setContentView是同步方法,如果想要计算耗时,直接将前后时间计算相减即可得到结果了。
AOP(Aspectj,ASM)
上面的方式虽然简单,但是却不够优雅,同时代码有侵入性,如果要对所有Activity测量时,就需要在基类中复写相关方法了,比较麻烦了。
下面介绍一种AOP的方式计算耗时。
@Around("execution(* android.app.Activity.setContentView(..))")
public void getSetContentViewTime(ProceedingJoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.toShortString();
long time = System.currentTimeMillis();
try {
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
Log.i("aop inflate",name + " cost " + (System.currentTimeMillis() - time));
}
上面用的Aspectj,比较简单,上面的注解的意思是在setContentView方法执行内部去调用我们写好的getSetContentViewTime方法。
这样就可以获取相应的耗时。
我们可以看下打印的日志:
I/aop inflate: AppCompatActivity.setContentView(..) cost 69
I/aop inflate: AppCompatActivity.setContentView(..) cost 25
这样就可以实现无侵入的监控每个页面布局加载的耗时。
具体源码可见文末。
获取任一控件耗时
有时为了更精确的知道到底是哪个控件加载耗时,比如我们新添加了自定义View,需要监控它的性能。
我们可以利用setFactory2来监听每个控件的加载耗时。
首先我们来回顾下setContentView方法:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
...
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
...
return view;
}
在真正进行反射实例化xml结点前,会调用mFactory2的onCreateView方法。
这样如果我们重写onCreateView方法,在其前后加上耗时统计,即可获取每个控件的加载耗时。
private fun initItemInflateListener(){
LayoutInflaterCompat.setFactory2(layoutInflater, object : Factory2 {
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
val time = System.currentTimeMillis()
val view = delegate.createView(parent, name, context, attrs)
Log.i("inflate Item",name + " cost " + (System.currentTimeMillis() - time))
return view
}
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
return null
}
})
}
如上所示:真正的创建View的方法,仍然是调用delegate.createView,我们只是其之前与之后做了埋点。
注意,initItemInflateListener需要在onCreate之前调用。
这样就可以比较方便地实现监听每个控件的加载耗时。
3布局加载优化的一些方法介绍
布局加载慢的主要原因有两个,一个是IO,一个是反射。
所以我们的优化思路一般有两个:
1.侧面缓解(异步加载)。
2.根本解决(不需要IO,反射过程,如X2C,Anko,Compose等)。
AsyncLayoutInflater方案
AsyncLayoutInflater 是来帮助做异步加载 layout 的,inflate(int, ViewGroup, OnInflateFinishedListener) 方法运行结束之后 OnInflateFinishedListener 会在主线程回调返回 View;这样做旨在 UI 的懒加载或者对用户操作的高响应。
简单的说我们知道默认情况下 setContentView 函数是在 UI 线程执行的,其中有一系列的耗时动作:Xml的解析、View的反射创建等过程同样是在UI线程执行的,AsyncLayoutInflater就是来帮我们把这些过程以异步的方式执行,保持UI线程的高响应。
使用如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncLayoutInflater(AsyncLayoutActivity.this)
.inflate(R.layout.async_layout, null, new AsyncLayoutInflater.OnInflateFinishedListener() {
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
setContentView(view);
}
});
// 别的操作
}
这样做的优点在于将UI加载过程迁移到了子线程,保证了UI线程的高响应。
缺点在于牺牲了易用性,同时如果在初始化过程中调用了UI可能会导致崩溃。
X2C方案
X2C是掌阅开源的一套布局加载框架。
它的主要是思路是在编译期,将需要翻译的layout翻译生成对应的java文件,这样对于开发人员来说写布局还是写原来的xml,但对于程序来说,运行时加载的是对应的java文件。
这就将运行时的开销转移到了编译时。
如下所示,原始xml文件:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="10dp">
<include
android:id="@+id/head"
layout="@layout/head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true" />
<ImageView
android:id="@+id/ccc"android:layout_below="@id/head" />
</RelativeLayout>
X2C 生成的 Java 文件:
public class X2C_2131296281_Activity_Main implements IViewCreator {
@Override
public View createView(Context ctx, int layoutId) {
Resources res = ctx.getResources();
RelativeLayout relativeLayout0 = new RelativeLayout(ctx);
relativeLayout0.setPadding((int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,10,res.getDisplayMetrics())),0,0,0);
View view1 =(View) new X2C_2131296283_Head().createView(ctx,0);
RelativeLayout.LayoutParams layoutParam1 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
view1.setLayoutParams(layoutParam1);
relativeLayout0.addView(view1);
view1.setId(R.id.head);
layoutParam1.addRule(RelativeLayout.CENTER_HORIZONTAL,RelativeLayout.TRUE);
ImageView imageView2 = new ImageView(ctx);
RelativeLayout.LayoutParams layoutParam2 = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,(int)(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,1,res.getDisplayMetrics())));
imageView2.setLayoutParams(layoutParam2);
relativeLayout0.addView(imageView2);
imageView2.setId(R.id.ccc);
layoutParam2.addRule(RelativeLayout.BELOW,R.id.head);
return relativeLayout0;
}
}
使用时如下所示,使用X2C.setContentView替代原始的setContentView即可。
// this.setContentView(R.layout.activity_main);X2C.setContentView(this, R.layout.activity_main);
X2C优点
1.在保留xml的同时,又解决了它带来的性能问题。
2.据X2C统计,加载耗时可以缩小到原来的1/3。
X2C问题
1.部分属性不能通过代码设置,Java不兼容。
2.将加载时间转移到了编译期,增加了编译期耗时。
3.不支持kotlin-android-extensions插件,牺牲了部分易用性。
Anko方案
Anko是JetBrains开发的一个强大的库,支持使用kotlin DSL的方式来写UI,如下所示:
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
MyActivityUI().setContentView(this)
}
}
class MyActivityUI : AnkoComponent<MyActivity> {
override fun createView(ui: AnkoContext<MyActivity>) = with(ui) {
verticalLayout {
val name = editText()
button("Say Hello") {
onClick { ctx.toast("Hello, ${name.text}!") }
}
}
}
}
如上所示,Anko使用kotlin DSL实现布局,它比我们使用Java动态创建布局方便很多,主要是更简洁,它和拥有xml创建布局的层级关系,能让我们更容易阅读。
同时,它去除了IO与反射过程,性能更好,以下是Anko与XML的性能对比。
以上数据来源于:https://medium.com/android-ne...
不过由于AnKo已经停止维护了,这里不建议大家使用,了解原理即可。
AnKo建议大家使用Jetpack Compose来替代使用。
Compose方案
Compose 是 Jetpack 中的一个新成员,是 Android 团队在2019年I/O大会上公布的新的UI库,目前处于Beta阶段。
Compose使用纯kotlin开发,使用简洁方便,但它并不是像Anko一样对ViewGroup的封装。
Compose 并不是对 View 和 ViewGroup 这套系统做了个上层包装来让写法更简单,而是完全抛弃了这套系统,自己把整个的渲染机制从里到外做了个全新的。
可以确定的是,Compose是取代XML的官方方案。
Compose的主要优点就在于它的简单好用,具体来说就是两点:
1.它的声明式 UI。
2.去掉了 xml,只使用 Kotlin 一种语言。
由于本文并不是介绍Compose的,所以就不继续介绍Compose了,总得来说,Compose是未来android UI开发的方向,读者可以自行查阅相关资料。
4为什么放弃使用这些优化方法?
上面介绍了不少布局加载优化方法,而我最后在项目中最后都没有使用,这就是从真从入门到放弃。
总得来说有以下几个原因:
1.有些方式(如AsyncLayoutInflater,X2C)牺牲了易用性,虽然性能提升了,但是开发变得麻烦了。
2.Anko使用上比较方便同时性能较高,但是比起XML方式改动很大,同时Anko已经放弃维护了,在团队中推动难度大。
3.Compose是未来android UI开发的方向,但目前仍处于Beta阶段,相信在Release后,会成为我们替换XML的有效手段。
4.还有最主要的一点是,针对我们的项目,布局加载耗时并不是主要耗时的地方,所以优化收益不大,可以将精力投入到其他地方。
如下所示,我们将setConteView前后时间相减,得到布局加载时间。
而onWindowFocusChanged是Activity真正可见时间,将其与onCreate时间相减,可得页面显示时间。
在我们的项目中测试效果如下:
android 5.0
I/Log: inflateTime:33
I/Log: activityShowTime:252
I/Log: inflateTime:11
I/Log: activityShowTime:642
I/Log: inflateTime:83
I/Log: activityShowTime:637
android 10.0
I/Log: inflateTime:11
I/Log: activityShowTime:88
I/Log: inflateTime:5
I/Log: activityShowTime:217
I/Log: inflateTime:27
I/Log: activityShowTime:221
我在android5.0手机与10.0手机上分别做了测试,在我们的项目中布局加载耗时并不很长,同时它们在整个页面可见过程中,占得比例也并不高。
所以得出结论:针对我们项目,布局加载耗时并不是主要耗时的地方,优化收益不大。
这就是从入门到放弃的原因。
一些常规优化手段
上面介绍了一些改动比较大的方案,其实我们在实际开发中也有些常规的方法可以优化布局加载。
比如优化布局层级,避免过度绘制等,这些简单的手段可能正是可以应用到项目中的。
优化布局层级及复杂度
1.使用ConstraintLayout,可以实现完全扁平化的布局,减少层级。
2.RelativeLayout本身尽量不要嵌套使用。
3.嵌套的LinearLayout中,尽量不要使用weight,因为weight会重新测量两次。
4.推荐使用merge标签,可以减少一个层级。
5.使用ViewStub延迟加载。
避免过度绘制
1.去掉多余背景色,减少复杂shape的使用。
2.避免层级叠加。
3.自定义View使用clipRect屏蔽被遮盖View绘制。
总结
本文主要介绍了以下内容:
1.andrid绘制原理与布局加载原理。
2.如何定量的获取android布局加载耗时。
3.介绍了一些布局加载优化的方法与手段(AsyncLayoutInflater,X2C,Anko,Compose等)。
4.介绍了因为在我们在项目中布局加载耗时优化收益不大,所以没有引入上述优化手段。
Android高级开发系统进阶笔记、最新面试复习笔记PDF,我的GitHub
文末
您的点赞收藏就是对我最大的鼓励!
欢迎关注我,分享Android干货,交流Android技术。
对文章有何见解,或者有何技术问题,欢迎在评论区一起留言讨论!
今天关于工作出现瓶颈,学习效率下降?通学这份Android进阶知识体系,从入门到高级只是时间问题!的讲解已经结束,谢谢您的阅读,如果想了解更多关于2021 最新Android知识体系,【工作经验分享、2021最新Android知识体系总结,最全Android知识总结、Android DataBinding 从入门到进阶、Android 布局优化真的难,从入门到放弃的相关知识,请在本站搜索。
本文标签: