持续更新…
Q:Fragment的生命周期
onAttach -> onCreate -> onCreateVIew -> onActivityCreated -> onStart -> onResume -> onPause -> onStop -> onDestroyView -> onDestroy -> onDetach
通过FragmentManager管理Fragment的生命周期:
只有再创建的时候加入返回栈,在退栈时才会一起销毁创建的fragment,否则退栈时不销毁fragment
Q:Activity的生命周期
onCreate -> onStart -> onResume -> onPause -> onStop -> onDestoy
Q:onSaveInstanceState() 与 onRestoreIntanceState()
二者不是生命周期方法
- onSaveInstanceState 用于意外死亡保存应用内容(如果是用户点击退出这种正常退出则不会走这个方法)
- onRestoreIntanceState 用于恢复数据内容,方法传入的Bundle对象是onSaveInstanceState 所保存的
onSaveInstanceState和onPause的区别:
- onSaveInstanceState用于临时性的数据保存
- onPause用于持久性的数据保存
Q:启动Service的两种方式
多次调用:
需要同时调用unBind和stopService才能停止服务
注意:
在Service也是IBinder于UI线程,所以避免进行耗时操作, 所以可以使用IntentService;
IntentService
运行在子线程,并且运行完会自动销毁,但是需要通过startService来启动一个IntentService,不能使用bindService,因为IntentService其实里面封装了一个子线程的Handler,在IntentService的onStart方法里面会通过这个Handler发送一个message,然后在这个Handler里面的handlerMessage才会走到我们执行任务的onHandleIntent,但是如果直接bindService,就不会走到onStart方法,更不会执行onHandleIntent里面的方法了
Q:IntentService使用以及原理:
使用:
IntentService 是一个抽象类,继承并实现 onHandleIntent(Intent intent),在onHandleIntent处理异步任务
原理
工作流程
- 在Service的 onCreate 创建了一个 HandlerThread和该线程的Handler
- 在onStartCommand 调用onStart,在onStart又使用第一步创建的Handler发送一个包装(带有启动信息的)Intent的Message
- 在handleMessage,会调用onHandleIntent,之后会调用stopSelf
注意:
- 可以多次开启IntentService,但是一次只能处理一次请求,剩下的请求会被挂起,依次执行后,如果没有任务了就会调用stopSelf停止
- HandlerThread 是封装了 Handler的线程,带有Looper
推荐阅读
Q:安卓中数据持久化有哪几种方案?
- SP存储
- 文件存储
- 数据库
- 网络存储
Q:SharedPreferences是线程安全的吗?commit和apply的区别?
- SP是线程安全的,但是进程不安全
- commit是同步操作,会返回结果,尽量不在主线程操作;apply是异步操作
- 每次commit/apply都会把全部数据一次性写入到磁盘,即没有增量写入的概念 。 所以单个文件千万不要太大 否则会严重影响性能
Q:广播相关
广播接收器的两种注册方式:
广播的种类
推荐参考:厘米姑娘
Q:MVP和MVC模式的区别
如果不采用MVP/MVC模式,将所有的功能全部写在Activity/Fragment,会发现稍微复杂点的逻辑Activity/Fragment会变得非常臃肿
传统MVC(Model-View-Controler)中,xml无力承担所有View的功能,会发现Activity/Fragment同时承担了View和Controler的角色,所以对Activity解耦不够完全
MVP (Model-View-Presenter)中,Activity/Fragment只负责显示,将业务逻辑放在P层,P层再通过实际需求向M层请求数据,再返回给P层,P层再控制V层显示
Q:组件化相关
模块化以module的形式将业务模块分开,实现隔离/封装、高内聚,更加灵活可以随意拔插
组件化在模块化的基础上演变的,目的是解耦和复用,各个组件可以单独编译,在开发时可以专注于该模块,减少编译时间
具体参考这篇文章
需要注意的点:
Q:讲一下Handler
Handler 是安卓里面用于跨线程通信的一个机制
主要使用为在一个线程创建一个Handler对象,重写其handleMessage方法,对接收的消息进行处理,而在其他线程则通过sendMessage或者sendMessageDelayed发送一个Message消息
优化:
发送的消息通过Message.obtain方法获取,这样可以减少资源浪费,不用每一次都new一个新对象
一般我们使用Handler对象都会创建一个匿名内部类,这样回默认持有外部类的引用,导致内存泄漏,解决方法是:通过创建静态内部类,并且传入外部Activity或者Fragment的弱引用,又或者是在生命周期销毁时移除Handler所有的消息,可以避免内存泄漏
实现原理:
在主线程里有一个looper对象,在主线程被创建的时候会自动的创建这个对象,并且这个looper会不停的从MessageQueue读取消息,而当我们发送Message时,会将这个消息加入到MessageQueue消息队列中,looper从MessageQueue取到消息后会回调到handleMessage方法对消息进行处理;
需要注意的是,子线程中默认没有looper,所以如果要在子线程中创建Handler,需要先调用 Looper.prepare() 方法将子线程变成一个looper线程,再调用 Looper.loop() 开始对消息的读取,这时创建的Handler会自动绑定子线程的looper,开始不停的从MessageQueue消息。
内存泄漏:
- 使用非静态内部类会导致内存泄漏,将其改为静态内部类 + 弱引用的形式传入Activity
- 在生命周期结束前移除所有的消息
MessageQueue是什么数据结构?
- 单链表
Q:什么是ANR?如何避免
ANR 是指应用程序在一定时间内没有相应(主线程阻塞),系统对跳出一个对话框显示XXX未响应;
不同的组件有不同的时间:1. Activity(5s)2. BroadCastReceiver(10s)3. Service(20s)
几种常见的情况:
- 主线程被IO操作(从4.0之后网络IO不允许在主线程中)阻塞。
- 主线程中存在耗时的计算
- 应用在5秒内未响应用户的输入事件(如按键或者触摸)
- BroadcastReceiver未在10秒内完成相关的处理
- Service在特定的时间内无法处理完成 20秒
处理:
- /data/anr/traces.txt 存放有ANR信息,通过ADB命令获取
- 异步执行耗时操作(AsyncTask、Handler)
- Activity onCreate、onResume避免进行耗时操作
Q:Android里面的Context
Context有三种类型:Application、Service、Activity。一个应用中的Context数量为:Context数量 = Activity数量 + Service数量 + 1
实现类:ContextImpl
大多数情况下,这三种Context可以通用,除了:
生命周期的区别:
ApplicationContext 是伴随着应用的生命周期,一些情况下需要传入ApplicationContext,防止内存泄漏
Q:Android的几种动画
Android 动画分为三种:属性动画、视图动画、帧动画
- 视图动画(View Animation):作用于View上,XML/代码两种方式使用,没有改变View的属性(位置等,影响点击等事件)
- 帧动画(Frame Animation):通过一系列图片播放达到动画效果,没有改变View的属性(位置等,影响点击等事件)
- 属性动画(Property Animation):Android 3.0 之后引入,进行动画的同时也会改变相关控件的属性
- 插值器 对动画的运行过程进行操作(匀速、先快后慢等等)(0~1变化的规律)
- 估值器 对动画变化的结果进行操作(0~1变化时,每一个点应该是什么值)
示例:
视图动画:
// set代表一个动画集合
// 缩放动画
// 平移动画
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="100%"
android:toYDelta="100%"
android:duration="2000"
/>
set>
属性动画:
- ValueAnimator 先改变值,再手动赋值给对应的View
- ObjectAnimator 先改变值,再自动赋值给对应的对象
配合ObjectAnimator使用:估值器,推荐参考
属性动画使用:推荐参考
作用 | 资源ID | 对应的Java类 |
---|---|---|
动画加速进行 | @android:anim/accelerate_interpolator | AccelerateInterpolator |
快速完成动画,超出再回到结束样式 | @android:anim/overshoot_interpolator | OvershootInterpolator |
先加速再减速 | @android:anim/accelerate_decelerate_interpolator | AccelerateDecelerateInterpolator |
先退后再加速前进 | @android:anim/anticipate_interpolator | AnticipateInterpolator |
先退后再加速前进,超出终点后再回终点 | @android:anim/anticipate_overshoot_interpolator | AnticipateOvershootInterpolator |
最后阶段弹球效果 | @android:anim/bounce_interpolator | BounceInterpolator |
周期运动 | @android:anim/cycle_interpolator | CycleInterpolator |
减速 | @android:anim/decelerate_interpolator | DecelerateInterpolator |
匀速 | @android:anim/linear_interpolator | LinearInterpolator |
Q:LRU缓存原理
LRU(Least Recently Used)
- 核心思想:当缓存满时, 会优先淘汰那些近期最少使用的缓存对象。主要是两种方式:
- LruCache(内存缓存):LruCache类是一个线程安全的泛型类:内部采用一个LinkedHashMap以强引用的方式存储外界的缓存对象,并提供get和put方法来完成缓存的获取和添加操作,当缓存满时会移除较早使用的缓存对象,再添加新的缓存对象。
- DiskLruCache(磁盘缓存): 通过将缓存对象写入文件系统从而实现缓存效果
Q:Android里几种异步机制
- IntentService
- Handler
- AsyncTask
- RxJava
- runOnUiThread 切换到主线程
Q:Handler 和 Rxjava的异同
相同点:都是为了线程之间的通信
不同点:区别:
1.RxJava线程切换更方便,Handler需要在子线程去发送消息,在主线程去接受消息然后才能改变UI。
2.RxJava是观察者模式,Handler的消息队列用的是单链表
Q:事件分发机制
参考 Android事件分发机制详解:史上最全面、最易懂
从activity的dispatchTouchEvent->phoneWindow->frameLayout->viewGroup->View
三个关键方法:
- public boolean dispatchTouchEvent(MotionEvent event)
- public boolean onInterceptTouchEvent(MotionEvent ev)
- public boolean onTouchEvent(MotionEvent event
dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent |
---|---|---|
用于进行事件的分发,如果事件能传递到当前View,此方法一定会调用 | 存在于ViewGroup中,在dispatchTouchEvent方法中调用,判断是否拦截某个事件 | 用来处理点击事件 |
返回值:受当前View的onTouchEvent和下一个View的dispatchTouchEvent影响 | true代表拦截 | 返回值:代表是否消耗此事件 |
关系伪代码
public boolean dispatchTouchEvent(MotionEvent event){
boolean consume = false;
if (onInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else {
consume = child.dispatchTouchEvent(event);
}
return consume;
}
在事件分发机制里主要有dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent三个方法,分别的作用是分发事件、拦截事件、对事件的处理;
对于事件的分发:
- Activity -> ViewGroup:
事件分发从Activity开始,传递到ViewGroup再到View,当产生一个事件时,调用Activity的dispatchTouchEvent,里面调用了getWindow().superDispatchTouchEvent(ev),这里其实调用了mDecor.superDispatchTouchEvent(event),而mDecor是一个DecorView对象,DecorView的父类是继承FrameLayout,所以实际上是调用类ViewGroup的dispatchTouchEvent,这里将事件从Activity传递到ViewGroup- ViewGroup对事件的处理:对于一个根ViewGroup,当产生一个点击事件时,此时会调用dispatchTouchEvent方法,然后再dispatchTouchEvent方法里面会调用onInterceptTouchEvent方法,如果onInterceptTouchEvent返回true,那么这个ViewGroup的onTouchEvent方法会被调用,事件分发停止;
如果onInterceptTouchEvent返回false(默认),则会将事件传递给子元素,子元素会调用它的dispatchTouchEvent,直到事件最终被处理;- ViewGroup -> View :
如果子元素是View,则会调用子元素的onTouchEvent方法,onTouchEvent的返回结果就是dispatchTouchEvent的返回结果对于事件的处理:
对于最后分发到的View,如果他的onTouchEvent方法返回false,那么会回调他的父容器的onTouchEvent。直到Activity
如果设置了OnTouchListener,那么会先进入OnTouchListener的onTouch方法,如果onTouch方法返回false,则会继续回调到onTouchEvent,如果返回true,则不会回调到onTouchEvent方法。并且OnClickListener也是处于onTouchEvent方法中的,由此可见他的优先级其实是最低的
Q:View的绘制流程
开发艺术之旅 | View的工作原理
x
Q:View重新绘制几个方法的区别
null | requestLayout | invalidate | postInvalidate |
---|---|---|---|
工作线程 | UI线程 | UI线程 | 子线程 |
作用效果 | 重新确定位置,调用onMeasure和onLayout方法,也有可能会调用onDarw方法 | 重新绘制View,调用onDraw方法 | 和invalidate一样 |
Q:自定义View 需要重写哪几个方法?
Q:为什么跨进程需要使用AIDL等机制?
在 Stub的asInterface 方法,如果是本地进程,会返回一个Stub对象本身;
如果不是同一进程,就会返回一个Stub.Proxy 对象,进而导致我们在ServiceConnection的onServiceConnected接收到的IBinder对象其实是一个BinderProxy,而不是Binder,所以不能转换成我们自己定义的Binder,会报错,需要用到AIDL或者Messenger等跨进程机制
Q:Stub.Proxy 如何实现跨进程调用?
- Stub.Proxy 实现了我们定义的接口,但是他的实现方法是,通过调用Stub的transact方法,传入需要的参数、一个用于接收返回值的Parcel以及方法标识,最后Stub回调到onTransact方法。
- 在onTransact方法会取出参数,调用Stub相应的方法,再把结果写入传入的Parcel;
- 在onTransact执行完方法后,在Stub.Proxy相应的方法会取出结果并返回
Q:了解Binder机制吗
Binder机制流程图
Q:Android各版本适配
Android 6.0 —— SDK 23
- 动态权限申请
Android 7.0 —— SDK 24(7.1 是 25)
- 新版V2签名 打包只勾选V2版本会在7.0以下显示未安装
- 跨应用共享文件(拍照相册等会涉及),需要用到FileProvider
Android 8.0 —— SDK 26 官方文档
通知渠道适配:可以创建通知渠道组(可选)、渠道ID,发通知时指定渠道ID
服务创建的适配:处于后台的应用(无可见的Activity、无可见的前台服务、无被其他应用关联)不可以启动一个后台服务;在后台的应用创建的后台服务需要用**startForegroundService()启动一个服务,并且在5s内在服务调用startForeground()**将应用推到前台,否则会ANR错误
对于周期性的任务,官方推荐以“作业”的方式(JobScheduler);替代IntentService:JobIntentService(JobScheduler使用:推荐参考)广播的适配:禁止在其清单中为隐式广播注册广播接收器
Android 9.0 —— SDK 28
- 默认禁用http,不允许明文流量( application标签下:android:usesCleartextTraffic=“true” 或者定义一个xml标签: android:networkSecurityConfig="@xml/network_security_config" )
- 需要禁用 Apache HTTP 客户端
- 启动前台服务需要加上权限
<network-security-config>
<base-config cleartextTrafficPermitted="true"/>
network-security-config>
<manifest ... >
<application
...
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
...
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
...
application>
manifest>
Q:布局性能优化
Q:主线程Looper.loop()进入无限循环,为什么不会造成ANR
- Android应用是由事件驱动,looper.loop()不断接收事件,Handler不断处理消息,如果没有消息,那么主线程就关闭了,应用也退出了。所以不是Looper.loop()阻塞了主线程,而是在处理事件的时候超过一定时间造成的ANR;
- MessageQueue.next 会一直读取Message,直到有消息返回Message;而Looper.loop方法会调用MessageQueue.next获取Message,如果没有消息了就返回为NULL,退出循环
主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
线程的阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
Q:res文件夹下的资源会不会被编译成二进制编码?和asset的区别?
- 二者文件夹下的文件都会被原封不动的打包到apk包中,不会被编译成二进制编码
获取方式
- res 文件夹下的文件会被映射到R.java文件中,访问资源可以用过R.id 可以引用到资源文件
- asset文件夹下的文件不会被映射到R.java,访问资源通过AssetManager
res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹
目录结构
res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹
Q:XML的几种解析方式和对比
参考区别:链接
参考使用:链接
Q:图片加载框架对比
Glide
- 多种图片格式的缓存,适用于更多的内容表现形式(如Gif、WebP、缩略图、Video)
- 生命周期集成(根据Activity或者Fragment的生命周期管理图片加载请求)
- 高效处理Bitmap(bitmap的复用和主动回收,减少系统回收压力)
- 高效的缓存策略,灵活(Picasso只会缓存原始尺寸的图片,Glide缓存的是多种规格),加载速度快且内存开销小(默认Bitmap格式的不同,使得内存开销是Picasso的一半)
Fresco
- 最大的优势在于5.0以下(最低2.3)的bitmap加载。在5.0以下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)
- 大大减少OOM(在更底层的Native层对OOM进行处理,图片将不再占用App的内存)
- 适用于需要高性能加载大量图片的场景
进阶