一、Android基础
binder调用是否会阻塞当前线程?
A:默认跨进程调用Binder 客户端线程会被阻塞,如果客户端不在乎返回结果,调用后想立即返回,可以给方法加上oneway关键字
Android SharedPreference 是否支持多进程读写?
A:不支持的,虽然SharedPreference有跨进程模式,如果项目有多个进程使用同一个Preference,需要使用该模式,但是也已经废弃了,官方建议使用ContentProvider
如何统计 Android 应用的帧率(FPS)?
A:利用Choreographer的postcallback方法接口轮询方式,能够对帧率进行统计
Android ANR是什么?系统如何得知发生了ANR?
A:ANR触发流程,可以比喻为埋炸弹和拆炸弹的过程,
以启动Service为例,Service的onCreate方法调用之前会使用Handler发送延时10s的消息,Service 的onCreate方法执行完,会把这个延时消息移除掉。
假如Service的onCreate方法耗时超过10s,延时消息就会被正常处理,也就是触发ANR,会收集cpu、堆栈等信息,弹ANR Dialog。
Android 组件化之间的组件如何通信
通过建立common组件,各个组件依赖common组件来完成组件化通信,定义好相关的接口;ARouter、Eventbus等提供响应方法
Android子线程如何创建handler
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
// ...使用handler发送消息
Looper.loop();
}
}).start();
介绍Handler postdelay的机制
A:原来在next方法中对链表头部的Message的执行时间进行了判断,如果当前时间小于msg.when,则计算阻塞时间,
然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。
1、比如postDelay()一个延时10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;
2、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;
3、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;
4、Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
直到阻塞时间到或者下一次有Message进队再次唤醒;
Handler同步消息屏障的作用
A:同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。Android 应用框架中为了更快的响应UI刷新事件在 ViewRootImpl.scheduleTraversals 中使用了同步屏障,mTraversalRunnable 调用了 performTraversals 执行measure、layout、draw。
为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障。
Android 每个线程如何保证只有一个Looper
A:Looper 通过 ThreadLocal 存储在每个线程中: 在 Android 中,Looper 通过 ThreadLocal 存储在每个线程中。Looper.myLooper()
方法实际上是使用 ThreadLocal 来获取当前线程的 Looper。如果当前线程没有关联的 Looper,这个方法会返回 null
Android触摸事件的原理
Android触摸事件机制主要涉及两个方面:触摸事件的类型和事件传递的三个阶段。
触摸事件类型主要有三种:ACTION_DOWN、ACTION_MOVE和ACTION_UP。其中,ACTION_DOWN表示用户按下操作,标志着一次触摸事件的开始;ACTION_MOVE表示用户在屏幕上滑动的过程;ACTION_UP表示用户手指离开屏幕的操作,标志着一次触摸事件的结束。
事件传递的三个阶段包括分发(Dispatch)、拦截(Intercept)和消费(Consume)。在分发阶段,事件由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程。在拦截阶段,ViewGroup会通过onInterceptTouchEvent方法来决定是否拦截事件,如果拦截则不再分发给子视图,否则继续传递给子视图。在消费阶段,View会通过onTouchEvent方法来消费事件,包括消费完、消费不完全和消费不了三种情况。
Android触摸事件机制是实现用户与应用程序交互的重要基础,通过处理不同类型的事件和事件传递的三个阶段,应用程序可以响应用户的操作并提供相应的反馈。
如何处理Android滑动冲突?方法有哪些?
在 Android 中,滑动冲突通常发生在包含多个可滑动的 View 或 ViewGroup 的情况下,例如嵌套的 ScrollView、ListView、RecyclerView 等。解决滑动冲突的方法通常包括以下几种:
1. **外部拦截法(父布局拦截事件):**
- 在父容器的 `onInterceptTouchEvent` 方法中进行事件拦截,根据具体情况判断是否拦截事件。
- 当父容器确定要处理滑动时,可以在 `onTouchEvent` 方法中进行相应的处理。
2. **内部拦截法(子 View 拦截事件):**
- 子 View 拦截事件,需要重写子 View 的 `onInterceptTouchEvent` 方法。
- 子容器在处理事件时,通过 `requestDisallowInterceptTouchEvent(true)` 来通知子 View 不要拦截事件。
3. **使用 NestedScrollView、NestedScrollingParent 和 NestedScrollingChild:**
- 使用 `NestedScrollView` 作为父容器,它已经实现了 NestedScrollingParent 接口。
- 在子 View 中使用 `NestedScrollingChild` 接口,以便通知父容器它的滑动情况。
ViewModel是如何在Activity配置发生变化的时候保存数据的
A:生命周期感知: ViewModel
是与生命周期关联的,它能感知相关联的 Activity
或 Fragment
的生命周期变化。当配置发生变化时,例如屏幕旋转导致 Activity
被销毁并重新创建,ViewModel
会存储在 ViewModelStore
中,以便下一个 Activity
实例能够获取到相同的 ViewModel
实例。
单线程池 与 HandlerThread的区别
-
执行方式: 单线程池按照提交顺序串行执行任务,而
HandlerThread
通过消息队列和Looper
实现异步执行任务。 -
线程数量: 单线程池中只有一个工作线程,而
HandlerThread
可以创建多个线程。 -
使用场景: 单线程池适用于需要保证任务按照指定顺序串行执行的场景,而
HandlerThread
适用于需要在工作线程中处理耗时操作或进行线程间通信的场景。
Serializable 和 Parcelable的区别
`Serializable` 和 `Parcelable` 都是在 Android 中用于实现对象序列化的接口,但它们有一些重要的区别。
1. **性能:**
- `Parcelable` 性能通常比 `Serializable` 好,因为它是为 Android 设计的,并且在内部实现上更加高效。`Parcelable` 直接将对象的字段写入 Parcel(一种 Android 中的轻量级序列化工具),而不需要像 `Serializable` 那样进行大量的反射操作。
2. **实现方式:**
- `Serializable` 是 Java 平台的标准序列化接口,它只需实现 `Serializable` 接口并定义一个唯一的序列化标识符(serialVersionUID)即可。Java 虚拟机(JVM)负责处理序列化和反序列化的细节。
- `Parcelable` 是 Android 提供的接口,需要实现 `Parcelable` 接口,并重写 `writeToParcel` 和 `createFromParcel` 方法。这种方式需要手动编写序列化和反序列化的逻辑,但由于是手动控制,可以更灵活地控制序列化的细节。
3. **跨平台:**
- `Serializable` 是 Java 平台的标准,因此可用于在不同的 Java 环境中进行对象传递,但可能存在一些兼容性问题。
- `Parcelable` 是 Android 特有的,不适用于非 Android 环境。如果需要在 Android 和其他平台之间进行对象传递,可能需要使用其他的跨平台序列化方式。
4. **手动实现:**
- `Parcelable` 需要手动实现相关方法,这样开发者可以更精确地控制对象的序列化和反序列化过程,以提高性能和减小序列化数据的大小。
- `Serializable` 虽然不需要手动实现方法,但由于它使用反射,可能会导致性能问题,并且无法精确地控制序列化的过程。
综上所述,如果在 Android 环境中进行对象传递,通常推荐使用 `Parcelable`,因为它在性能上更为高效。但如果需要在不同的 Java 环境中进行对象传递,或者希望使用标准的 Java 序列化机制,那么可以选择使用 `Serializable`。
Android View post方法的实现机制
主线程执行: 一旦消息被添加到主线程的消息队列中,主线程的 Looper 就会开始处理消息队列中的消息。当消息包含一个 Runnable
对象时,它会执行 run
方法。因此,通过 View.post
提交的代码块最终会在主线程上执行。
为什么Android只能在主线程更新UI
在 Android 中,UI 框架不是线程安全的,这意味着多个线程并发地修改 UI 可能导致不可预测的结果和应用程序崩溃。为了解决这个问题,Android 引入了一种单线程模型,即主线程(也称为 UI 线程),以确保 UI 操作是串行执行的。
主要原因有以下几点:
1. **View层次结构的访问和更新不是线程安全的:** Android UI 框架的设计是为了简化开发者的工作,并且大多数 UI 操作都需要访问和修改 View 层次结构。如果允许在多个线程中并发地修改 View 层次结构,就会导致竞态条件和不一致性。为了避免这种情况,Android 采用了单线程模型。
2. **性能和同步问题:** 在多线程环境中,需要引入额外的同步机制来确保对 UI 元素的访问是线程安全的。这可能会引入性能开销和复杂性。通过限制 UI 操作在主线程中执行,Android 可以避免这些同步问题。
3. **事件处理机制:** Android 中的触摸事件、键盘事件等都是在主线程中处理的。如果 UI 操作允许在其他线程中执行,可能会导致事件处理与 UI 更新之间的不一致性和难以调试的问题。
为了在主线程中执行后台任务并更新 UI,Android 提供了一些机制,例如使用 `View.post`、`Handler`、`AsyncTask` 等,它们可以帮助你将任务传递到主线程队列中执行。这样,你可以在后台线程中进行耗时操作,但最终的 UI 更新仍然在主线程中完成,确保了线程安全性。
Service 和 IntentService的区别
`Service` 和 `IntentService` 都是 Android 中用于执行后台任务的组件,但它们有一些重要的区别。
1. **执行方式:**
- **Service:** `Service` 是一个通用的后台执行组件,没有默认的工作线程。你需要在 `Service` 中自行管理线程,确保在后台执行任务而不影响主线程。通常,你需要手动创建线程或使用异步任务(AsyncTask)等方式来处理异步操作。
- **IntentService:** `IntentService` 是 `Service` 的子类,它默认会创建一个工作线程,并且在工作线程中处理传递给它的 Intent。每次启动 `IntentService` 时,它会将 Intent 放入队列,并在工作线程中逐个处理这些 Intent。
2. **生命周期:**
- **Service:** `Service` 的生命周期较为灵活,可以通过 `startService()` 和 `bindService()` 启动和绑定。它可以长时间运行,即使调用它的组件(如 Activity)被销毁,`Service` 仍然可以继续运行。
- **IntentService:** `IntentService` 在处理完所有的 Intent 后会自动停止。这使得 `IntentService` 更适合执行一些单次的、独立的后台任务。
3. **线程管理:**
- **Service:** 需要手动管理线程,以避免在主线程中执行耗时操作。
- **IntentService:** 内部已经处理了线程管理,你只需要实现 `onHandleIntent` 方法,该方法在工作线程中被调用。
4. **适用场景:**
- **Service:** 适用于长时间运行的任务,比如在音乐播放器中播放音乐或在后台下载数据的情况。
- **IntentService:** 适用于执行单个任务后自动停止的情况,例如在后台处理推送消息或下载文件。
总的来说,`IntentService` 更适合用于简单的、异步的后台任务,因为它自动管理了线程和生命周期,而 `Service` 更为灵活,适用于需要手动管理线程和生命周期的情况。选择使用哪个取决于你的具体需求。
Android 四大组件 哪些是继承context的?为什么这么设计?
在 Android 中,四大组件指的是 Activity、Service、BroadcastReceiver 和 ContentProvider。其中,Activity、Service 和 ContentProvider 是继承自 Context 的,而 BroadcastReceiver 不是。
1. **Activity:** Activity 是 Android 应用程序中的一个核心组件,代表一个用户界面屏幕。Activity 是 Context 的子类,这是因为 Activity 需要访问应用程序的资源(如布局文件、字符串等)、启动其他组件(如启动新的 Activity)以及处理用户交互事件等,而这些操作都需要访问应用程序的上下文信息。
2. **Service:** Service 是 Android 应用程序中的一个后台运行的组件,用于执行长时间运行的任务或处理后台任务。Service 也是 Context 的子类,这是因为 Service 通常需要访问应用程序的资源,并且可能需要在后台执行与用户界面无关的操作。
3. **ContentProvider:** ContentProvider 是 Android 应用程序中用于共享数据的组件,它可以让应用程序之间共享数据。ContentProvider 也是 Context 的子类,这是因为 ContentProvider 需要访问应用程序的数据存储区域(如数据库、文件等)并提供数据访问接口。
4. **BroadcastReceiver:** BroadcastReceiver 是 Android 应用程序中用于接收和处理广播消息的组件。BroadcastReceiver 不是 Context 的子类,这是因为 BroadcastReceiver 主要用于接收系统级别的广播消息,与应用程序的上下文信息关系较小。
继承 Context 的设计使得 Activity、Service 和 ContentProvider 可以方便地访问应用程序的资源和执行相关操作,而不需要传递额外的上下文信息。同时,这也符合面向对象设计的原则,即将相关功能封装在一个类中,提高代码的可维护性和可重用性。
Android 网络连接复用的机制
在 Android 中,网络连接的复用是通过 HTTP/1.1 协议中的 Keep-Alive 机制来实现的。当使用 HTTPURLConnection 或者 HttpClient 进行网络请求时,默认情况下会启用 Keep-Alive 机制,即在请求完成后保持与服务器的 TCP 连接,以便可以在同一个连接上发送多个请求和接收多个响应。
具体来说,当使用 Keep-Alive 时,客户端会在请求头中添加一个 `Connection: Keep-Alive` 的字段,告诉服务器保持连接。服务器在接收到请求后,在响应头中也会添加一个 `Connection: Keep-Alive` 的字段,表示同意保持连接。这样,客户端和服务器之间的 TCP 连接就可以在多次请求和响应之间复用,减少了 TCP 连接的建立和关闭次数,提高了网络请求的效率和性能。
在 Android 中,你可以通过以下方法来禁用 Keep-Alive 机制,以确保每个请求都使用一个新的 TCP 连接:
使用 HttpURLConnection:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Connection", "close");
需要注意的是,虽然 Keep-Alive 机制可以减少 TCP 连接的建立和关闭次数,提高网络请求的效率,但在某些情况下可能会导致连接超时或连接池耗尽的问题。因此,在设计网络请求时需要根据具体情况进行权衡和选择。
Android Crash治理
参考文章:精选文章|得物App Android Crash治理演进
Android 插件化原理
参考文章:吹爆系列: Android 插件化的今生前世大揭秘
Android binder 原理
在 Android 系统中,Binder 是一种跨进程通信(IPC)机制,用于在不同进程之间传输数据和调用方法。Binder 的实现原理涉及到以下几个关键点:
1. **Binder 驱动:** Binder 机制是基于 Linux 内核的 Binder 驱动实现的。Binder 驱动提供了 Binder 设备节点(/dev/binder)用于进程间通信,并提供了底层的数据传输和处理功能。
2. **Binder 通信流程:**
- 服务端进程(Server):服务端会创建一个 Binder 对象并将其注册到 Binder 驱动中,然后等待客户端连接。
- 客户端进程(Client):客户端会获取到服务端的 Binder 对象,并通过 Binder 驱动发送请求到服务端。
- Binder 驱动:Binder 驱动接收到客户端的请求后,会将请求转发给服务端,并将服务端的响应返回给客户端。
3. **Binder 对象和 BinderProxy:** 在客户端和服务端之间传递的是 Binder 对象的引用,而不是对象本身。客户端获取到的 Binder 对象实际上是一个 BinderProxy 对象,它是 Binder 的代理对象,用于发送请求到服务端。
4. **Binder 的调用过程:**
- 客户端调用代理对象的方法,并传入参数。
- 代理对象将方法调用转化为一个请求消息,并通过 Binder 驱动发送到服务端。
- 服务端接收到请求消息后,调用自己的 Binder 对象的方法,并将结果返回。
- Binder 驱动将结果返回给客户端的代理对象,代理对象再将结果返回给客户端。
5. **Binder 的安全性:** Binder 机制提供了权限验证和进程隔离机制,确保只有具有相应权限的进程可以访问 Binder 对象,从而保障系统的安全性。
总的来说,Binder 是 Android 系统中实现跨进程通信的重要机制,通过 Binder 机制,不同进程之间可以方便、高效地进行数据传输和方法调用,从而实现各种跨进程通信的功能。
Android APK瘦身有哪些方式?
在 Android 应用开发中,可以通过以下几种方式来减小 APK 大小,即进行 APK 瘦身:
1. **资源优化:**
- 使用 WebP 格式替换 PNG、JPEG 等图片,可以减小图片大小;
- 使用 VectorDrawable 替换大部分 PNG 图片,减小 APK 大小;
- 删除不必要的资源文件,如未使用的图片、布局文件等。
2. **代码优化:**
- 使用 ProGuard、R8 等代码混淆工具来移除未使用的代码和资源,减小 APK 大小;
- 使用 App Bundle 功能,只打包需要的资源和代码,减小安装包大小;
- 优化代码结构,减少代码冗余,提高代码复用。
3. **压缩资源和代码:**
- 使用 PNG、JPEG 等图片压缩工具对图片进行压缩,减小图片大小;
- 使用压缩工具对代码进行压缩,减小 APK 大小;
- 对资源文件进行压缩,减小 APK 大小。
4. **移除不必要的库和功能:**
- 移除不必要的第三方库和功能,减小 APK 大小;
- 使用动态模块加载(Dynamic Feature Module)功能,将不常用的功能打包为独立的模块,按需下载。
5. **资源和代码分包:**
- 将资源和代码分包,按需下载,减小安装包大小;
- 使用 Android App Bundle(aab)格式,系统会根据设备配置和用户需求自动下载和安装相应的资源和代码。
6. **优化资源和代码加载:**
- 使用更高效的加载方式,减少资源和代码加载时间;
- 避免重复加载资源和代码,提高加载效率。
通过以上方式,可以有效地减小 APK 大小,提高应用性能和用户体验。
Android 自定义ViewGroup 支持瀑布流 如何设计
要实现支持瀑布流(Waterfall Flow)的自定义 ViewGroup,你可以按照以下步骤设计:
-
确定布局方式: 瀑布流布局是一种不规则的多列布局,每一列的高度可以不同。因此,你需要确定列数和列与列之间的间距,以及行与行之间的间距。
-
重写 onMeasure() 方法: 在 onMeasure() 方法中,你需要测量子 View 的大小,并根据列数和间距计算出每列的宽度。同时,根据子 View 的高度确定每一行的高度。
-
重写 onLayout() 方法: 在 onLayout() 方法中,你需要确定每个子 View 的位置,按照瀑布流的布局方式来摆放子 View。可以使用一个二维数组来保存每列的高度,然后根据高度最小的列来确定新添加子 View 的位置。
-
添加子 View: 在自定义 ViewGroup 中,你需要提供方法来动态添加子 View。当添加子 View 时,需要重新计算布局,并调用 requestLayout() 方法来触发重新布局。
-
处理滑动: 如果你希望支持滑动功能,可以在自定义 ViewGroup 中处理触摸事件,并实现滑动逻辑。可以参考 Android 的 ScrollView 或 RecyclerView 的实现方式。
-
优化性能: 在实现瀑布流布局时,要注意优化性能,尽量减少不必要的计算和布局操作,避免频繁地请求布局。
Android View绘制流程
Android View 的绘制流程可以简单概括为以下几个步骤:
1. **测量(Measure):** 在测量阶段,系统会调用 View 的 `onMeasure()` 方法来确定 View 的大小。在这个阶段,View 可以通过重写 `onMeasure()` 方法来指定自己的测量规则,例如确定 View 的宽高或者根据内容自适应大小。
2. **布局(Layout):** 在布局阶段,系统会根据测量阶段得到的 View 的大小,来确定 View 的位置。系统会调用 View 的 `onLayout()` 方法来确定 View 的位置。在这个阶段,View 可以通过重写 `onLayout()` 方法来指定自己的位置。
3. **绘制(Draw):** 在绘制阶段,系统会调用 View 的 `onDraw()` 方法来绘制 View 的内容。在这个阶段,View 可以通过重写 `onDraw()` 方法来自定义绘制内容,例如绘制图形、文字等。
4. **绘制缓存(Draw Cache):** 在绘制缓存阶段,系统会将 View 的绘制结果缓存起来,以便下次重绘时可以直接使用缓存结果,从而提高绘制性能。可以通过调用 `setDrawingCacheEnabled(true)` 来开启绘制缓存。
5. **重绘(Invalidate and Redraw):** 当 View 需要重绘时,可以调用 `invalidate()` 方法来请求重绘。系统会在下一个绘制周期中重新调用 View 的 `onDraw()` 方法来实现重绘。
6. **视图树的绘制(Traversal):** 在绘制过程中,系统会遍历整个视图树(View Hierarchy),从根视图开始递归绘制每个子视图。在遍历过程中,系统会根据绘制顺序(从后向前)来绘制视图,保证上层视图覆盖下层视图。
视图树中的 View 绘制顺序是从顶层到底层,即从父 View 到子 View,从后向前绘制,保证上层 View 覆盖下层 View
以上就是 Android View 的绘制流程
RecyclerView 缓存机制
Android Message是如何分发到对应的Handler的?
在 Android 中,Message 的 target 变量是在发送消息时由 Handler 赋值的。当你调用 Handler 的 sendMessage 或者 post 等方法发送消息时,Handler 会创建一个 Message 对象,并将这个 Message 的 target 属性设置为自己,即当前的 Handler。
target 的作用是用来标识消息应该被发送到哪个 Handler。当消息被放入消息队列中时,Looper 会从消息中取出 target,并将消息分发给 target 对应的 Handler 处理。这样,每个 Message 都知道自己应该被哪个 Handler 处理,从而实现了消息的分发功能。