整理了一些安卓的面试题,大概是1-2年水平,本人菜鸡,如有错漏欢迎留言
1. Activity
1.1 生命周期
|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| onCreate(Bundle savedInstanceState) | 进行一次性初始化操作,如加载布局(setContentView)、初始化变量、绑定数据到视图、设置点击监听器等。如果Activity之前被销毁并有状态保存,可以通过savedInstanceState参数恢复数据。执行后Activity已创建但不可见。 |
| onStart() | Activity由不可见变为可见时。处理Activity即将对用户可见时需要进行的准备工作。例如,注册广播接收器、绑定可以影响UI的数据(但此时Activity还未进入前台,无法与用户交互)。执行后Activity可见但不在前台 |
| onResume() | Activity进入前台,获得焦点,开始可与用户交互时。启动或恢复需要与用户交互紧密相关的操作,例如开启动画、启动摄像头预览、注册传感器监听、恢复音频/视频播放等。执行后Activity可见且位于前台 |
| onPause() | Activity正在失去焦点,但通常仍部分可见,此时不能交互。保存持久性数据:提交未保存的更改(如用户编辑的草稿);停止消耗CPU的操作:暂停动画、停止传感器、释放相机等独占资源。此方法必须迅速执行,等待此方法返回后才能继续下个生命周期(如onStop),耗时操作会影响用户体验和界面流畅度。 |
| onStop() | Activity完全不可见时。释放或减少在Activity不可见时不需要的资源,以节省系统内存。例如,停止网络请求、注销广播接收器、暂停一些重量级操作等。系统内存紧张时,处于Stopped状态的Activity可能会被回收 |
| onRestart() | Activity从Stopped状态(完全不可见)重新变为可见之前调用(在onStart之前)。处理Activity从停止状态恢复时需要特别更新的逻辑,许多简单的恢复操作通常在onStart或onResume中处理。 |
| onDestroy() | Activity被销毁之前,进行最终的清理工作,释放所有资源,防止内存泄漏。例如,取消正在执行的任务、关闭数据库连接、解绑服务等。 |
额外的重要方法:onSaveInstanceState(Bundle outState):并非生命周期循环中的常规方法,在Activity可能被系统销毁(如内存回收、配置变更)前调用(通常在onStop之前,但时机有版本差异)。用于将Activity的临时UI状态(如文本框内容、列表滚动位置)保存到Bundle中,以便在后续onCreate或onRestoreInstanceState中恢复
onRestoreInstanceState(Bundle savedInstanceState):在onStart之后、onResume之前被调用,当Activity确实是从之前保存的状态重建时,用于恢复UI状态。也可以在onCreate中通过判断savedInstanceState是否为null来进行恢复。
1.2 activity启动模式有几种
标准模式(standard):每次启动Activity时都会创建一个新的实例,即使已存在该Activity的实例。新实例将被放入启动它的Activity所在的任务栈中。
栈顶复用模式(singleTop):如果新启动的Activity已经位于任务栈的栈顶,则不会创建新的实例,而是调用现有实例的onNewIntent()方法。
栈内复用模式(singleTask):在任务栈中只允许一个Activity实例存在。如果栈中已有该Activity的实例,则将该实例移至栈顶,并调用onNewIntent()方法,同时清除该实例之上的所有Activity。
单例模式(singleInstance):该Activity将在一个新的任务栈中运行,确保只有一个实例存在。如果其他应用启动了这种模式的Activity,它将共享这个实例。(例如手机拨号的活动)
1.3 Activity的onNewIntent
onNewIntent的主要职责是处理新传递的Intent,并更新Activity的状态或UI,确保用户看到的是最新数据,常用于需要复用Activity实例且处理动态数据的场景.
onNewIntent的调用需满足Activity已存在且通过特定方式启动的条件,主要包括以下场景:
启动模式触发:当Activity的launchMode设置为singleTop(栈顶复用)或singleTask(栈内复用)时,若目标Activity已在任务栈中(singleTop要求在栈顶,singleTask无位置限制),再次启动该Activity不会创建新实例,而是调用现有实例的onNewIntent。
Intent Flag触发:启动Activity时设置FLAG_ACTIVITY_SINGLE_TOP标志(等同于singleTop模式),若Activity在栈顶,会触发onNewIntent
2. Fragment
2.1 Fragment是什么?和Activity的联系?
可以理解为"Activity的片段"或"子模块"。它代表Activity中界面的一部分行为或用户界面;Activity作为容器,可以包含一个或多个Fragment;Fragment作为内容,必须依附于Activity存在;Fragment的生命周期受宿主Activity的生命周期直接影响。当Activity暂停时,其中的所有Fragment也会暂停;当Activity被销毁时,其中的Fragment也会被销毁;
主要优势:
|----------------------------------|
| 模块化:将代码分散到不同Fragment中,提高代码可维护性。 |
| 可重用性:同一个Fragment可以被多个Activity复用。 |
| 可适配性:根据屏幕尺寸和方向灵活调整布局。 |
| 轻量切换:相比Activity切换更流畅。 |
数据交互:
|--------------------------------------------------|
| Activity向Fragment传递数据:通过Bundle和setArguments()方法。 |
| Fragment向Activity传递数据:通过定义回调接口,让Activity实现该接口。 |
| Fragment间通信:通过共享的Activity作为中介。 |
2.2 Fragment生命周期
创建阶段
onAttach():当Fragment与Activity建立关联时调用。
onCreate():Fragment被创建时调用,进行初始化操作。
onCreateView():创建并返回与Fragment关联的视图层次结构。这是必须实现的方法,通常在这里加载布局文件。
onActivityCreated():当宿主Activity的onCreate()方法完成时调用,表明Activity已完全创建,可以安全执行与Activity相关的操作。
可见阶段
onStart():Fragment变为可见状态时调用,但还不可交互。
onResume():Fragment变为可见且可交互状态,可以获取焦点。
不可见阶段
onPause():Fragment开始离开前台,失去交互能力但可能仍部分可见。
onStop():Fragment完全不可见。
销毁阶段
onDestroyView():与Fragment关联的视图被移除时调用。
onDestroy():Fragment被销毁前调用。
onDetach():Fragment与Activity解除关联时调用,生命周期中最后一个方法。
2.3 Fragment 管理
Fragment的所有动态操作(添加、移除、替换)都必须通过FragmentTransaction进行事务提交**。**常用事务方法:
|----------------------------------------|
| add():添加Fragment到容器 |
| remove():移除Fragment |
| hide()/show():隐藏/显示Fragment(不销毁实例) |
| detach():分离Fragment(销毁视图但保留实例) |
| attach():重新关联Fragment |
| replace():替换容器中的Fragment(先remove后add) |
FragmentManager内部维护回退栈,通过addToBackStack()将事务加入栈中。用户点击返回按钮时,会回退到上一个Fragment状态。回退栈记录每次add或replace的Fragment,点击返回时自动执行退栈操作。
2.4 IllegalStateException异常
Can not perform this action after onSaveInstanceState:commit()在onSaveInstanceState()之后调用导致。避免方案:不要在异步任务回调中执行Fragment事务,或使用commitAllowingStateLoss()。
2.5. getActivity()返回null
应该在Fragment的onAttach()中获取Activity引用,避免在异步任务中直接调用getActivity()。
3. Context
Context(上下文)是 Android 系统的核心组件,代表应用程序的运行环境信息,提供了访问应用资源、启动组件、获取系统服务等能力。Context 是一个抽象类,其具体实现由 Android 系统提供
3.1 主要功能
|------------------------------------------------------------|
| 访问应用资源(Resources、Assets、字符串、图片等) |
| 启动四大组件(Activity、Service、BroadcastReceiver、ContentProvider) |
| 获取系统服务(LayoutInflater、NotificationManager 等) |
| 文件操作和数据库访问 |
| 权限检查和系统配置访问 |
3.2 ApplicationContext
生命周期与整个应用进程一致,应用启动时创建,进程结束时销毁,无内存泄漏风险;无主题支持,不能做ui操作。无任务栈,启动activity时必须在 Intent 中添加 FLAG_ACTIVITY_NEW_TASK标志。
3.3 ActivityContext
生命周期与 Activity 绑定,Activity 销毁时随之销毁;ui操作相关的context只能用ActivityContext;有主题支持;被生命周期长的对象(如静态变量、单例)持有,容易导致内存泄漏。
3.4 ServiceContext
生命周期与 Servcie绑定,Service 销毁则其 Context 销毁;不支持直接进行 UI 操作(如显示 Dialog),但可通过 WindowManager添加视图到系统窗口(需权限);无主题支持;无任务栈,启动activity时必须在 Intent 中添加 FLAG_ACTIVITY_NEW_TASK标志。
3.5 getApplication() 与 getApplicationContext() 的区别
getApplication():返回 Application 实例,仅 Activity 和 Service 可用
getApplicationContext():返回 Application Context,所有 Context 实现类都可用
两者本质上返回的是同一个 Application 实例,但 getApplication() 是 Activity/Service 的方法,而 getApplicationContext() 是 Context 接口的方法。applicationcontext需要转换类型才能调appliaction的方法
4. Intent
4.1 Intent是什么
Intent是一种组件之间的通信机制,可以将其理解为一个含有指令和参数的包裹,告诉系统或应用你想执行什么操作,操作的对象是谁,以及附带什么数据。
4.2 显式 Intent 与隐式 Intent
显式 Intent直接指定目标组件的完整类名(ComponentName),明确告诉系统要启动哪个组件。通过 setClass()或 setComponent()明确指定目标组件,目标组件唯一,系统无需解析,通常用于应用内部组件跳转。
java
// 方式一:构造函数指定
Intent intent = new Intent(this, TargetActivity.class);
startActivity(intent);
// 方式二:setClass 方法
Intent intent = new Intent();
intent.setClass(this, TargetActivity.class);
startActivity(intent);
隐式Intent不指定具体组件类名,而是通过 Action、Category、Data 等属性描述要执行的操作,由系统根据这些属性找到合适的组件来处理。通过 IntentFilter 匹配规则找到目标组件,通常用于启动其他应用中的组件或执行系统级别操作。
java
// 启动系统拨号器
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:123456"));
startActivity(intent);
// 打开网页
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
startActivity(intent);
4.3 intent如何工作
工作机制大概是由系统解析intent,如果是显式的,获取目标组件的Class对象。随后检查目标组件是否在清单文件中声明(未声明则抛出异常),若存在则启动组件;如果是隐式intent,系统向PackageManager查询所有已安装应用的AndroidManifest.xml,收集所有声明了<intent-filter>的组件(Activity/Service/BroadcastReceiver)。对每个组件,依次匹配Action、Data、Category(若任一条件不满足,则排除该组件)。
如果涉及跨进程就走binder进行通信,一般都是通知AMS或者ATMS进行启动
4.4 数据传递
通过putExtra,指定key和value进行设置,获取的时候不同类型有对应函数,指定key进行获取;如果传递的是对象,需要实现序列化接口。
如果当需要传递的数据量较大、类型较多,或者这些数据需要作为一个整体在多个Activity间连续传递时,使用 Bundle是更优选择,它能让代码更清晰、更易于维护。使用方法是创建Bundle对象,然后对于普通数据有对应的put函数,复杂数据调用putExtra,并且要求数据是序列化的。
总结:Intent 可以传递基本数据类型、String、数组、实现了 Serializable 或 Parcelable 接口的对象,以及 Bundle 对象。
注:Intent 传递数据时,数据会存放在 Binder 的事务缓冲区中,该缓冲区大小通常为 1MB。如果传递的数据量过大,会抛出 TransactionTooLargeException异常,所以避免传输大数据。
4.5 IntentFilter 匹配规则
必须同时匹配 action、category 和 data(如果定义了)。
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Intent Filter可以声明多个Action。只要Intent的Action与其中任何一个匹配即可, 如果Intent Filter未声明任何Action,则只有不包含Action的Intent才能与之匹配 |
| Data匹配是两部分:MIME类型和URI。 1. MIME类型:必须完全匹配,或Intent Filter使用通配符(如image/*匹配所有图片类型)。 2. URI:被拆分为Scheme、Host、Port、Path等部分进行匹配,遵循线性依赖规则: - 若未指定Scheme,则Host被忽略。 - 若未指定Host,则Port和Path被忽略。 - Path支持通配符匹配。 3. 特殊规则:如果Intent Filter只指定了MIME类型而未指定URI,则默认支持content:和file:这两种Scheme的URI |
| Intent Filter声明的Category可以比Intent中的多,但只要包含了Intent的所有Category,就算通过。 特殊情况:使用startActivity()或startActivityForResult()发起的隐式Intent,系统会自动为其添加android.intent.category.DEFAULT类别。因此,希望接收隐式Intent的Activity必须在Intent Filter中包含此类别。 如果Intent Filter未声明任何Category,则只有不包含任何Category的Intent才能与之匹配 |
5. 序列化
序列化是将对象转换为可存储或传输的字节序列的过程,以便在不同场景(如内存、磁盘、网络或进程间)中重建对象。其核心目的是解决对象在非运行时状态下的持久化与传输问题。
5.1 常见序列化
安卓主要两种序列化方式, Serializable(Java原生接口)实现简单,仅需声明接口(如public class User implements Serializable)。依赖反射机制,性能较低,可能触发频繁GC。支持版本控制(通过serialVersionUID),但需手动维护以避免反序列化失败;
Parcelable(Android专属接口)通过手动实现writeToParcel()和CREATOR完成序列化,无反射,性能高效。代码较复杂,需处理Parcel对象的读写逻辑。仅适用于内存中数据传输(如Intent传参),不支持直接存储到磁盘。适合高频内存操作(如Activity间传递大数据),AIDL跨进程通信。
另外常见还有使用Gson进行序列化变成Json。
5.2 为什么parcelable不能持久存储
设计初衷是为了在内存中高效地进行对象传输,特别是在进程间通信(IPC)和组件(如 Activity、Service)间传递数据。Parcelable是使用共享内存完成用户空间和内核空间的数据交换,没有io操作,也没有版本控制,速度快,但是由于不同版本实现可能不一样,无法保证反序列化成功(没有版本控制),所以不支持持久存储。
6. 广播
6.1 Android里广播的分类
普通广播和有序广播;普通广播异步、同时到达、无法中断、效率高;有序广播同步、按优先级顺序传递、可中断、可修改数据。
按注册方式还可以分静态注册和动态注册, 静态注册方式在应用未启动也能接收,常驻系统,但 Android 8.0+ 大部分广播受限,主要是系统广播才可以用;普通应用需要动态注册广播,按需注册和销毁。
6.2 广播是否可以跨应用
使用隐式intent发送广播,只要指定应用配置的intent过滤器匹配就可以收到
7. ContentProvider
Android app进程间有隔离,contentProvider可以从一个app暴露出指定接口,控制app的部分数据允许外部访问,实现应用程序之间的数据共享。底层用的是binder机制。
7.1 ContentProvider组成
7.1.1 URI
|----------------------------------------------------------------------------|
| URI 是 ContentProvider 中数据的唯一标识符,格式固定为:content://authority/path/[id]。 |
| content://:标准前缀,表示该 URI 由 ContentProvider 管理。 |
| authority:ContentProvider 的唯一标识,通常在 AndroidManifest.xml 中注册,一般使用包名来避免冲突。 |
| path:路径,用于区分不同类型的数据(如 users, books)。 |
| id:可选部分,指定某一条具体记录(如 users/1) |
7.1.2 ContentResolver
这是数据访问方(客户端)使用的核心类。应用通过 getContentResolver()获取 ContentResolver 实例,然后调用其 query(), insert(), update(), delete()等方法。ContentResolver 并不直接处理数据,而是作为代理,根据 URI 将请求转发给正确的 ContentProvider。
7.1.3 ContentObserver
这是一个回调类,用于监听指定 URI 对应的数据变化。当 ContentProvider 中的数据发生变更(插入、更新、删除)后,可以调用 getContext().getContentResolver().notifyChange(uri, null)来通知所有注册了该 URI 的 ContentObserver,其 onChange()方法会被回调,从而实现数据更新的实时通知。
7.2 为什么要用ContentResolver来访问
访问contentProvider需要使用ContentResolver,主要有几个原因:
|----------------------------------------------------------------------------------|
| ContentResolver作为系统级服务,为所有ContentProvider提供了标准化的接口。客户端无需关心底层ContentProvider的具体实现 |
| 解耦 |
| 屏蔽binder跨进程的复杂性,缓存contentProvider的Binder代理对象,减少重复IPC连接的开销 |
| 集中管理权限检查 |
| 异步查询避免主线程anr |
| 查询cursor自动释放资源避免内存泄露 |
7.3 线程问题
ContentProvider 的 CRUD 方法(query, insert等)运行在 Binder 线程池中,而非主线程。这意味着这些方法可能被多个线程同时调用,必须确保其实现是线程安全的,特别是在操作数据库时。
8. Service
8.1 Service启动方式
startService启动方式,生命周期onCreate→ onStartCommand→ 运行中→ onDestroy;Service启动后与调用者(如Activity)无关,即使调用者退出或销毁,Service仍可在后台长期运行;调用者无法直接调用Service中的方法,只能通过Intent传递简单数据;必须显式调用stopService()或Service内部调用stopSelf()才能停止。(后台音乐播放,上传下载,日志记录等。)
bindService启动方式,生命周期:onCreate→ onBind→ 运行中 → onUnbind→ onDestroy;Service与调用者绑定,调用者退出时如果没有解绑,Service也会随之销毁;调用者可以通过Binder接口调用Service中的方法,实现双向通信。(需要与Activity频繁交互的后台服务,跨进程通信,提供数据查询或计算服务的场景。)
同时startservice启动和bindservice绑定, Service既能独立运行,又能与组件交互;必须同时调用stopService()和unbindService()才会销毁Service;生命周期顺序:无论启动和绑定的顺序如何,onCreate()只会调用一次。如果先绑定后启动,启动时会直接调用onStartCommand();如果先启动后绑定,绑定时会直接调用onBind()
8.2 onStartCommand返回值 (用于被意外杀死后的恢复逻辑)
| 返回值 | 含义 | 适用场景 |
|---|---|---|
START_STICKY |
粘性重启。服务被杀死后,系统会尝试重新创建服务并再次调用 onStartCommand()。但之前传入的 Intent 不会保留,如果重启时没有新的启动命令,Intent 参数将为 null。 |
适用于需要长期运行但不依赖于特定 Intent 数据的服务,如背景音乐播放。 |
START_NOT_STICKY |
非粘性。服务被杀死后,系统不会自动重启该服务。除非后续有新的 startService调用。 |
适用于执行一次性任务或可以安全中断的任务,例如定时轮询服务器数据。 |
START_REDELIVER_INTENT |
重传 Intent。服务被杀死后,系统会重新创建服务,并将最后一次传入的 Intent 重新传递给 onStartCommand(),保证任务数据不丢失。 |
适用于必须完成的关键任务,例如文件下载,确保中断后能继续执行。 |
8.3 后台服务与前台服务
Android服务默认在后台运行,但可以通过设置为前台服务来提升优先级。系统在内存不足时可能会将后台服务回收。而前台服务通过调用startForeground()方法,会在状态栏显示一个不可移除的通知,具有更高的优先级,系统不易回收,适合执行用户可感知的持续任务(如音乐播放、文件下载等)。从Android 8.0(API 26)开始,系统对后台服务有严格限制,长时间运行的后台任务推荐使用前台服务或JobScheduler/WorkManager等替代方案。
8.4 是否可以在Service做耗时操作
一般服务是在后台运行,可以做一些耗时操作,但是有几个点要注意可能导致anr;
Service的生命周期方法(onCreate(), onStartCommand(), onBind())都运行在主线程,如果服务在前台,这些生命周期方法执行超过20s会anr,后台200s,所以在这些生命周期方法里面做耗时操作要考虑到这一点。
耗时操作可以用线程完成;IntentService继承Service的抽象类,内置工作线程处理任务,自动停止Service
8.5 IntentService
IntentService是 Android 中用于处理异步后台任务的特殊 Service子类,其核心设计结合了 Service的稳定性和 HandlerThread的异步处理能力。内部通过 HandlerThread创建一个独立的工作线程,所有耗时操作在 onHandleIntent()方法中执行,避免阻塞主线程(UI 线程)。开发者无需手动创建或管理线程,简化了代码逻辑。仅支持 startService()启动方式,适用于无需绑定的场景。
多个请求通过 startService(Intent)发送时,IntentService会按顺序逐个处理,形成串行队列,确保任务执行的顺序性。当所有 Intent处理完成后,IntentService会自动调用 stopSelf()停止服务,无需开发者干预。
8.6 通信
Intent通信(简单数据传递),activity通过intent携带数据传给service;
binder通信适用于bindService启动方式,同进程内的方法调用,需要Service创建继承Binder的内部类并暴露公共方法,通过ServiceConnection获取Binder实例并调用方法;
Messenger通信(跨进程消息传递),Service创建Handler和Messenger处理消息,Activity通过Messenger发送Message;
使用广播,Service发送广播Intent,Activity注册广播接收器
9.如何判断当前线程是否在主线程
|--------------------------------------------------------------------------------------------|
| 1.判断当前线程消息循环是否是主线程消息循环 Looper.myLooper() == Looper.getMainLooper() |
| 2.通过当前线程对象比较主线程关联的 Thread对象 Thread.currentThread() == Looper.getMainLooper().getThread() |
| 3.app启动时记录主线程id,后面需要比较时拿线程id比较 |
10. View
10.1 自定义View的流程
先选择继承哪个类,可以继承现有控件扩展,继承ViewGroup创建组合控件,继承view完全自定义;然后定义一些需要传入的自定义属性,实现构造函数,提取自定义属性,测量,布局,绘制,事件处理等方法
10.2 自定义view如何处理padding
1.测量阶段:在onMeasure()中计算包含padding的总尺寸。确保子view的范围已经是加上padding的作用后的结果
2.绘制阶段:在onDraw()中根据padding调整内容位置。防止显示不全
3.ViewGroup:需额外处理子View的布局偏移。设置子view要测量的尺寸
10.3 自定义View需要重写的方法
首先构造函数要重写,包括不带属性集的构造函数,带属性集的,带默认属性的,还有api21以后带默认样式的构造函数。建议在构造函数中完成对象的创建,而不是在绘制相关方法中创建。
OnMeasure处理view大小,要处理好三种测量模式;
onDraw()方法用于绘制View的内容;
onLayout()(仅继承ViewGroup需要),用来定位子View
10.4 自定义View的完整生命周期方法调用顺序
构造函数:View初始化时调用
onAttachedToWindow():View被附加到窗口时调用
onMeasure():测量View尺寸
onSizeChanged():View尺寸变化时调用(首次也会调用)
onLayout():ViewGroup定位子View(普通View不需要)
onDraw():绘制View内容
onDetachedFromWindow():View从窗口分离时调用
10.5 自定义view绘制流程的典型顺序:
背景绘制(drawBackground())
主体内容绘制(onDraw())
子View绘制(dispatchDraw(),仅ViewGroup)
前景绘制(onDrawForeground())
10.6 MeasureSpec
32位int,头两位表示测量模式,后面30位是测量值,测量模式三种
精确模式,对应match_parent或者写死数值,特点是父视图已经确定了子视图的确切尺寸,子视图必须使用这个尺寸;
最大模式,对应wrap_content,特点是父视图给出最大尺寸,子视图只能小于等于它;
未指定模式,在scrollview、listview等使用,父视图不做限制。
10.7 在ondraw、onmeasure、onlayout创建对象会怎么样
应该避免在onMeasure()和onLayout()中创建对象,onMeasure()可能被调用多次以确定合适尺寸,onLayout()在布局变化时会被调用;
Ondraw不建议创建对象,onDraw()会被频繁调用(如动画、滚动时),频繁创建对象会导致内存抖动,可能引发GC停顿和界面卡顿,影响绘制性能,降低帧率。
10.8 自定义view和xml布局优劣
|---------------------------------------------------------------------|---------------------------------------------------------|
| 解析开销:XML需通过LayoutInflater解析为View对象,涉及反射和属性解析,嵌套过深时(如10层)解析耗时可能增加3倍。 | 直接控制绘制:避免XML解析,复杂UI(如游戏、图表)通过onDraw()直接操作Canvas,性能更优。 |
| 内存占用:XML属性需存储冗余数据,复杂布局可能占用更多内存(如60MB vs Compose的30MB)。 | 动态布局灵活:高频增删子View或调整参数时,代码动态创建(如addView())比XML更高效。 |
| 动态更新效率低:频繁调用findViewById()更新视图会导致性能损耗。 | 硬件加速优化:自定义View可针对性启用GPU加速(如LAYER_TYPE_HARDWARE),减少绘制卡顿。 |
10.9 视图刷新
Android视图刷新过程主要通过测量(Measure)、布局(Layout)、绘制(Draw)三大流程实现,并受VSYNC信号和消息队列调度控制。触发重绘有三种方法
invalidate():标记视图为"脏区",仅触发重绘(onDraw()),不重新测量或布局。适用于内容变化但尺寸不变的场景(如动画、文本更新)。非线程安全。
requestLayout():触发完整的三大流程(Measure→Layout→Draw),用于视图尺寸或位置变化(如动态调整布局参数)。
postInvalidate():通过ViewRootImpl中的mHandler将invalidate重绘请求发送到主线程队列。
Vsync信号每隔一定时间触发一次,同步cpu、gpu、display的工作节奏避免画面撕裂;Choreographer接收VSYNC信号后,通过Handler将performTraversals()(三大流程的入口)加入主线程工作队列,确保刷新在下一帧完成;
10.10 事件分发
Activity接收系统事件,一层一层往下分发到子view,首先到ViewGroup,ViewGroup的子View可以是ViewGroup或者view。在通过dispatchTouchEvent往子view传递前,viewGroup可以调用onInterceptTouchEvent()拦截,通过自身onTouchEvent处理。如果不拦截就由子view处理,如果子view的onTouchEvent()返回true代表事件消费了,就此结束;如果返回false代表没有处理或者需要父ViewGroup处理,此时会把事件分发到父ViewGroup的onTouchEvent,甚至可以回传到activity、系统层。如果子view没有消费onEventDown,那后续的move、up都不会分发给它(down、move、up需要由同个view处理)
特殊的事件:
ACTION_CANCEL:事件被上层拦截时触发(如父 View 调用 onInterceptTouchEvent)。
ACTION_OUTSIDE:触摸点超出视图边界时触发
当一个子View消费了ACTION_DOWN事件后,系统默认后续的ACTION_MOVE和ACTION_UP事件应由该子View处理。若父容器(ViewGroup)中途拦截事件,需通过ACTION_CANCEL通知子View事件序列被强制终止,避免子View因未收到ACTION_UP而残留错误状态(如按钮保持按下效果)。
11 ViewPager2 和 ViewPager
|--------------------------------|------------------------|
| ViewPager2 | ViewPager |
| 基于RecyclerView | 自定义 ViewGroup |
| 支持水平和垂直滑动 | 仅支持水平 |
| Adapter统一为RecyclerView.Adapter | 分FragmentPagerAdapter等 |
| 性能更高(视图回收优化) | 较低 |
| 原生支持RTL(右到左) | 需手动实现 |
| 支持 notifyDatasetChanged() | 不支持 |
| 懒加载默认支持 | 需手动实现 |
12. 动画
属性动画可作用于任何对象的属性(如 View 的透明度、位置、缩放等),不限于View,修改对象的实际属性值,动画结束后状态保留,支持组合动画、自定义插值器和估值器。
补间动画仅作用于View,通过图像变换(平移、旋转、缩放、透明度)实现动画。仅改变 View 的绘制效果,不改变实际属性(如点击事件仍在原位置)。
帧动画逐帧播放静态图片序列(类似 GIF)。需预加载所有帧图片,内存占用较高。
插值器控制动画的变化速率(如加速、减速、弹跳等)