现创建了一个Android开发水友圈,圈内会不定时更新一些Android中高级的进阶资料,欢迎大家带着技术问题来讨论,共同成长进步!(包含资深UI工程师,Android底层开发工程师,Android架构师,原生性能优化及混合优化,flutter专精);希望有技术的大佬加入,水圈内解决的问题越多获得的权利越大!
一. Android相关
1. mvc mvp mvvm三种架构模式
mvc:业务逻辑、数据、界面分离的一种模式,简单的来说,就是通过controller来操作model层的数据,并且返回给view显示。 activity不是标准的controller,随着界面逻辑交互的复杂度提升,activity类的职责不断增加,变得臃肿。 view和model相互耦合,不利于开发。
mvp:主要是提出了presenter层,作为view和model之间沟通的桥梁。 程序逻辑放在presenter中处理,完全将view和model进行了分离,不允许他们之间沟通。
mvvm:主要是将presenter改为了viewmodel,和mvp类似,不同的是viewmodel跟view和model进行双向绑定。 使用了data binding
二. Android系统结构层次
Android系统架构分为5层,从下到上依次为 Linux内核层,硬件抽象层,系统运行库层(Native),应用框架层,应用层。
Linux内核层:Android的核心基于Linux内核,在此基础上添加了Android的专用驱动(比如Binder)、系统的安全性、内存管理、进程管理等等。
硬件抽象层(HAL):有了核心还不行,你得需要运行到相应的硬件上才能实现自己的价值吧。而硬件抽象层就是硬件和Linux内核之间的接口,目的就是将硬件抽象化,保护硬件厂商的知识产权(Linux是有开源协议的)
系统运行库层:怎么操纵硬件,显示图像到屏幕?这一层就是干这个的,它分为两部分,分别是C++程序库和Android运行时。 C++程序库:被Android系统中的不同组件使用,可以通过应用框架层被开发者使用,下面是主要的程序库: openGL ES 3D绘图函数库 Media Framework 多媒体库 SQLite 关系型数据库引擎 SSL 安全套接层 Android Runtime:ART虚拟机(5.0之后,Dalvik虚拟机被ART取代),ART在应用第一次安装的时候,就会将字节码预编译成机器码存储到本地,这样应用每次运行就无须执行编译了(Dalvik是每次打开都要即时编译),典型的以空间换时间 应用框架层:Framework层,这层代码是用java编写的,为开发人员提供了API。 应用层。
三. Activity活动的启动模式及应用场景
standard: 默认的模式,新建一个Activity就在栈中新建一个activity实例。
singleTop:栈顶复用模式,与standard相比栈顶复用可以有效减少activity重复创建对资源的消耗 。 登录页面 ,wxpay等支付页面
singleTask:栈内单例模式,栈内只有一个activity实例,栈内已存activity实例,在其他activity中start这个activity,Android直接把这个实例上面其他activity实例踢出栈GC掉。主页面 ,WebView页面、扫一扫页面 ,付款界面
singleInstance:开辟一个新的栈存放activity。系统Launcher、锁屏键、来电显示等系统应用。
四. Android进程间通信的方式
1.)传统的IPC通信方式
Android系统是基于Linux内核的,Linux提供了管道、消息队列、共享内存和socket等IPC机制。那为什么Android还要提供Binder来实现IPC呢?主要是基于性能、稳定性和安全性方面的考虑。
性能:socket作为通用接口,传输效率低,开销大,主要用到跨网络进程通信。消息队列、共享内存和管道采用存储-转发模式,数据拷贝至少需要两次,共享内存虽然无需拷贝,但是控制复杂,难以使用。而binder只需要拷贝一次,性能上只次于共享内存。
稳定性:Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。
安全性:Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。
2.)传统IPC通信原理
通常的做法是消息发送方将要发送的数据存放在内存缓存区中,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信。
一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝
接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。
3.)Binder IPC实现
Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
五. 多线程的实现方法(synchronized和lock的异同)
继承Thread类创建线程
实现Runnable接口创建线程,推荐使用这种方式,可以复用runnable
实现Callbale接口,通过FutureTask包装器来创建一个带返回值的线程
synchronized
在用法上,它是java的关键字,一般我们不太需要关注他的锁的释放,代码执行完毕或者报错会自动释放锁,并且无法判断锁的状态。
lock
是一个接口,我们使用ReentrantLock 比较多,有多个获取锁的方式,可以trylock直接返回获取成功或者失败,线程不用一直等待。在finally中必须要释放该锁。
六. 说一下View的事件分发机制
为什么要有事件分发
注:引用水圈G神的博客
Android中的view是树形结构的,view可能会重叠在一起,当我们点击的地方有多个view的时候,这个时间该给谁,这就是为什么要有事件分发。
先来看看view的树形结构:
上面多出来两个东西是phonewindow和decorview,其中,主题颜色和标题栏内容等主要就是decorview来负责显示的,那PhoneWindow是做什么的呢?
PhoneWindow 继承window,并且是window唯一的实现类,window是一个抽象类,是所有视图的最顶层容器,视图的外观和行为都归他管,不论是背景显示,标题栏还是事件处理都是他管理的范畴,它其实就像是View界的太上皇。
`DecorView` 是 `PhoneWindow` 的一个内部类,其职位相当于小太监,就是跟在 `PhoneWindow` 身边专业为 `PhoneWindow` 服务的,除了自己要干活之外,也负责消息的传递,`PhoneWindow` 的指示通过 `DecorView` 传递给下面的 View,而下面 View 的信息也通过 `DecorView` 回传给 `PhoneWindow`
事件分发、拦截、消费
类型 相关方法 Activity ViewGroup View 事件分发 dispatchTouchEvent √ √ √ 事件拦截 onInterceptTouchEvent X √ X 事件消费 onTouchEvent √ √ √
Activity作为原始的事件分发者,不需要拦截事件,如果需要这个事件不分发下去就行了。
同样的,view在事件传递的最末端,也不需要拦截事件,不处理回传回去就行了。
事件在收集之后最先传递给Activity,然后依次向下传递:
Activity -> PhoneWindow -> DectorView -> ViewGroup -> ... -> view
如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后 Activity 也没有处理,本次事件才会被抛弃 :
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
上面的模式是一个非常经典的责任链模式
七、说一下View从app启动到显示在界面上的绘制流程
注:参考链接
在activity的attach方法里面,会创建一个PhoneWindow。
在onCreate中调用setContentView,setContentView是window的一个抽象方法,真正实现类是PhoneWindow:
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { //1.初始化 //创建DecorView对象和mContentParent对象 ,并将mContentParent关联到DecorView上 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews();//Activity转场动画相关 } //2.填充Layout if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene);//Activity转场动画相关 } else { //将Activity设置的布局文件,加载到mContentParent中 mLayoutInflater.inflate(layoutResID, mContentParent); } //让DecorView的内容区域延伸到systemUi下方,防止在扩展时被覆盖,达到全屏、沉浸等不同体验效果。 mContentParent.requestApplyInsets(); //3. 通知Activity布局改变 final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { //触发Activity的onContentChanged方法 cb.onContentChanged(); } mContentParentExplicitlySet = true; }
核心方法就两个:installDecor() 和 mLayoutInflater.inflate(layoutResID, mContentParent) ;
installDecor会创建一个DecorView 对象,该对象将作为整个应用窗口的根视图。然后配置不同窗口修饰属性(style theme等)。
mLayoutInflater.inflate就是解析xml,深度优先地递归解析xml,一层层添加到root view上,最终返回root view.解析的部分大致包含两点:1.解析出View对象,2.解析View对应的Params,并设置给View。
八、知道什么会引起ANR吗 怎么避免
有四种情况会造成ANR发生:
5秒内无法响应屏幕触摸事件或键盘输出
在执行前台广播的onReceive()函数时10秒没有处理完成,后台为20秒
前台服务20秒内,后台服务在200秒内没有执行完成
ContentProvider的publish在10s内没进行完
如何避免:
尽量避免在主线程中做耗时操作。 多线程==>引出如何实现多线程,线程池的使用
如何分析ANR:
产生anr之后,会在data/anr/目录下生成一个文件traces.txt
Logcat中查看
Java线程调用分析
DDMS分析
九、有做过app的性能优化吗
app启动加速:一个app的启动分为三种不同的状态,其中,我们只需要对第一种状态做优化。对于App来说, 我们可以控制的启动时间线无外乎Application的onCreate,首屏Activity的渲染。 冷启动:App没有启动过或者App进程被kill,系统中不存在该App进程。此时App的启动需要创建App进程,加载相关资源,启动main thread,初始化首屏Activity等。 热启动:App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕。 温启动:介于冷启动和热启动之间, 一般来说在以下两种情况下发生, 用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建 用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复
布局优化 减少不必要的嵌套 尽量不要嵌套使用RelativeLayout 尽量不要在嵌套的LinearLayout中都使用weight属性 ConstraintLayout 善用TextView的Drawable减少布局层级
响应优化
内存优化 bitmap的使用,options的jusdecodeBounds属性,设置只解析bitmap的宽高等,然后使用insimplesize对bitmap进行压缩。在android2.3的时代,bitmap的回收需要调用recycler方法,并且置空,但是之后只需要进行置空操作。 加载一张大图:使用BitmapRegionDecode进行局部解码, decodeRegion(Rect rect, BitmapFactory.Options options) 指定rect区域获取图像,options参数不支持inPurgeable,其他都支持 lru算法的实现=> LinkedHashMap
电池使用优化
网络优化 参考:Android App优化, 要怎么做?
十、了解过Android最新技术吗 使用过吗
flutter
一步手机的刷新率为60hz,当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(如VSync), 60Hz的屏幕就会一秒内发出 60次这样的信号。而这个信号主要是用于同步CPU、GPU和显示器的。
一般地来说,计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给 GPU,GPU渲染后放入帧缓冲区,然后视频控制器按照同步信号从帧缓冲区取帧数据传递给显示器显示。
进圈方式:点赞+关注,私信回复我‘资料’即可进圈。