安卓面试基础6

ANR面试

ANR(Application Not Responding)是指应用程序无响应,是 Android 系统检测到应用程序在特定时间内未能响应用户输入事件或执行特定操作而触发的。

  • 输入事件(如按键或触摸事件):5 秒
  • BroadcastReceiver:后台广播 10 秒,前台广播 60 秒
  • Service:前台服务 20 秒

ANR 主要由以下几种情况引起:

  • 主线程阻塞:主线程执行了耗时操作,如网络请求、文件读写、大量计算等,导致无法及时处理用户输入事件。
  • 长时间未释放资源:如 BroadcastReceiver 或 Service 长时间未完成任务。
  • 死锁:多个线程之间相互等待资源,导致线程无法继续执行。
  • 界面更新频繁:频繁进行界面更新操作,导致主线程负载过高。
  • 内存不足:系统内存不足,导致 GC 频繁或 OOM(内存溢出)。

如何预防和解决 ANR?

  • 避免在主线程执行耗时操作
    在主线程中应避免执行如网络请求、文件读写、大量计算等耗时操作。这类操作应放在子线程中执行,通过 Handler 或 AsyncTask 等方式通知主线程更新 UI。

  • 使用合适的线程机制
    对于后台任务,尽量使用 IntentService 或 JobIntentService,它们在完成任务后会自动停止。
    使用 HandlerThread 或 ExecutorService 来管理线程,避免创建过多线程导致系统资源耗尽。

  • 合理使用 BroadcastReceiver
    避免在 BroadcastReceiver 中执行耗时操作。如果确实需要执行耗时任务,可以启动一个 Service 来处理。

  • 优化界面绘制
    避免频繁调用 invalidate() 或 requestLayout() 方法。
    尽量减少界面元素的层级,优化自定义 View 的绘制逻辑。

  • 资源优化
    合理管理和释放资源,如 Bitmap、Cursor 等,避免内存泄漏。
    使用合适的缓存策略,减少内存占用。

OOM面试详解

  • OOM(Out of Memory)是指应用程序在运行时内存耗尽,导致内存溢出异常。

  • 内存抖动(Memory Churn)是指短时间内频繁创建和销毁对象,导致内存频繁分配和回收,从而影响系统性能。

  • 内存泄漏(Memory Leak)是指应用程序在运行时,由于疏忽或错误导致某些对象无法被垃圾回收器回收,从而占用内存,导致内存泄漏。

OOM 的原因

  • 大尺寸图片加载:加载未经压缩的大图片到内存中。
  • 内存泄漏:对象未被正确释放,导致内存无法回收。
  • 多线程并发:过多线程同时占用内存。
  • 大量数据处理:处理大数据集合时未进行优化。
  • 不合理的缓存策略:长时间持有不必要的对象。

如何检测 OOM

  • 使用内存分析工具
    Android Profiler:Android Studio 提供的内存分析工具,可以检测内存使用情况和泄漏。
    MAT(Memory Analyzer Tool):分析内存堆快照,找出潜在的内存泄漏。
  • 使用 LeakCanary
    LeakCanary 是一个开源工具库,可以自动检测内存泄漏并显示详细信息。

如何预防和解决 OOM

  • 图片加载优化
    使用合适尺寸加载图片:通过 BitmapFactory.Options 设置 inSampleSize 来压缩图片。
    使用图片加载库:如 Glide 或 Picasso,它们具有内存优化功能。

  • 避免内存泄漏
    及时释放资源:如 Bitmap、Cursor 等。
    避免静态引用:不使用静态变量持有 Activity 或 Context 的引用。
    使用弱引用:对于需要长时间持有的对象,使用 WeakReference。

  • 优化内存使用
    使用 LRUCache:缓存数据,避免重复加载。
    优化数据结构:选择合适的数据结构存储数据。
    减少全局变量:避免不必要的全局变量。

  • 控制线程数量
    合理使用线程池:避免创建过多线程,使用 ExecutorService 管理线程。

Bitmap面试问题

在 Android 开发中,Bitmap 是一个非常重要的类,用于表示图像。面试中常见的 Bitmap 相关问题通常涉及到内存管理、性能优化和实际应用。

1. 什么是 Bitmap?

回答:
Bitmap 是 Android 中用于存储图像的类,可以从资源文件、文件或其他输入流中创建。Bitmap 在内存中以像素阵列的形式存储图像数据。

2. 如何高效地加载 Bitmap?

回答:
高效加载 Bitmap 可以使用以下方法:

  • 使用 BitmapFactory.Options 进行图片压缩:

    1
    2
    3
    4
    5
    6
    7
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);

    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    options.inJustDecodeBounds = false;
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large_image, options);

    calculateInSampleSize 是一个自定义方法,用于计算合适的 inSampleSize 值。

  • 使用图片加载库:
    使用如 Glide 或 Picasso 等图片加载库,它们具有内存优化和缓存功能。

3. 什么是 Bitmap 的 inSampleSize?

回答:
inSampleSizeBitmapFactory.Options 的一个参数,用于指定加载图片时的缩放比例。值为 2 表示图像的宽和高都会缩小一半,值为 4 表示宽和高缩小到四分之一。

4. 如何避免 Bitmap 引起的内存泄漏?

回答:
避免 Bitmap 引起的内存泄漏可以采取以下措施:

  • 及时回收 Bitmap:
    使用 bitmap.recycle() 方法在不再需要 Bitmap 时手动回收其占用的内存。

    1
    2
    3
    4
    if (bitmap != null && !bitmap.isRecycled()) {
    bitmap.recycle();
    bitmap = null;
    }
  • 使用弱引用:
    使用 WeakReference 持有 Bitmap 对象,避免长时间持有导致内存泄漏。

    1
    WeakReference<Bitmap> bitmapWeakReference = new WeakReference<>(bitmap);
  • 避免静态引用:
    不要使用静态变量持有 Bitmap 引用。

5. 如何处理大图片的显示?

回答:
处理大图片可以通过以下方法:

  • 分块加载:
    将大图片分成多个小块,按需加载。

  • 使用 subsampling-scale-image-view:
    这是一个开源库,支持大图片的平滑缩放和移动。

6. 如何在 Canvas 上绘制 Bitmap?

回答:
在 Canvas 上绘制 Bitmap 通常通过 drawBitmap 方法:

1
2
3
Canvas canvas = new Canvas();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
canvas.drawBitmap(bitmap, left, top, null);

7. 如何缩放 Bitmap?

回答:
缩放 Bitmap 可以使用 Bitmap.createScaledBitmap 方法:

1
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);

8. 如何将 Drawable 转换为 Bitmap?

回答:
将 Drawable 转换为 Bitmap 可以通过以下代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
public static Bitmap drawableToBitmap(Drawable drawable) {
if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable) drawable).getBitmap();
}

Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);

return bitmap;
}

9. 如何处理 Bitmap 旋转问题?

回答:
处理 Bitmap 旋转问题可以使用 Matrix 类:

1
2
3
Matrix matrix = new Matrix();
matrix.postRotate(90); // 旋转90度
Bitmap rotatedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);

10. Bitmap.Config 有哪些类型?有什么区别?

回答:
Bitmap.Config 定义了 Bitmap 的像素配置,有以下几种类型:

  • ARGB_8888: 每个像素 4 字节(32 位),包括透明度通道。图像质量高,内存占用大。
  • RGB_565: 每个像素 2 字节(16 位),没有透明度通道。图像质量较低,内存占用小。
  • ARGB_4444: 每个像素 2 字节(16 位),包括透明度通道。已被废弃。
  • ALPHA_8: 每个像素 1 字节(8 位),只有透明度通道。

LruCache内存缓存

在 Android 开发中,LruCache 是一个内存缓存实现,主要用于缓存应用中的数据,以提高性能和响应速度。以下是 LruCache 的详细讲解:

什么是 LruCache?

LruCache 是 “Least Recently Used Cache” 的缩写,即最近最少使用缓存。它是一种缓存机制,当缓存达到最大容量时,会优先移除最近最少使用的条目,以腾出空间。

为什么使用 LruCache?

  • 提高性能: 减少对磁盘或网络的频繁访问。
  • 节省资源: 通过缓存经常使用的数据,减少加载时间。
  • 自动管理: 自动处理缓存的添加和移除。

如何使用 LruCache?

  1. 创建 LruCache 实例

    创建一个 LruCache 实例时,需要指定缓存的大小。一般使用应用可用内存的一部分。

    1
    2
    int cacheSize = 4 * 1024 * 1024; // 4MiB
    LruCache<String, Bitmap> bitmapCache = new LruCache<>(cacheSize);
  2. 存储数据

    使用 put 方法将数据存入缓存:

    1
    bitmapCache.put("imageKey", bitmap);
  3. 检索数据

    使用 get 方法从缓存中获取数据:

    1
    Bitmap bitmap = bitmapCache.get("imageKey");
  4. 移除数据

    可以使用 remove 方法提前移除数据:

    1
    bitmapCache.remove("imageKey");

如何计算缓存大小?

缓存大小的计算可以根据应用可用内存来决定。例如,可以使用如下方法计算:

1
2
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8; // 使用最大可用内存的1/8

自定义 LruCache 行为

你可以通过重写 LruCachesizeOf 方法,自定义每个缓存条目的大小。例如,对于 Bitmap 缓存:

1
2
3
4
5
6
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount() / 1024;
}
};

LruCache 的应用场景

  • 图片缓存: 缓存已加载的图片,减少重复加载。
  • 数据缓存: 缓存从网络获取的 JSON 数据或其他轻量级数据。

UI卡顿面试

什么是 UI 卡顿?

UI 卡顿表现为界面不流畅、响应延迟。理想情况下,Android 应用应该以 60 帧每秒的速度运行,这意味着每帧的渲染时间不应超过 16ms。

UI 卡顿的常见原因

  1. 主线程阻塞:

    • 在主线程执行耗时操作,如网络请求、数据库查询或文件读写。
  2. 布局过于复杂:

    • 布局层次过深或过于复杂,导致渲染时间过长。
  3. 不当的绘制操作:

    • onDraw 方法中进行复杂计算或创建复杂对象。
  4. 内存不足:

    • 内存泄漏导致可用内存减少,GC 频繁触发。
  5. 动画执行不当:

    • 动画中的逻辑复杂或帧率设置不当。

如何检测 UI 卡顿?

  1. Systrace 工具:

    • Android Profiler 中的 Systrace 能帮助分析线程执行情况。
  2. 布局检查工具:

    • 使用 Layout Inspector 检查布局的复杂程度。
  3. 日志监控:

    • 查看 Logcat 中的“Choreographer”日志,检测掉帧情况。

如何解决 UI 卡顿?

  1. 避免主线程阻塞:

    • 将耗时操作移到后台线程,如使用 AsyncTask、HandlerThread 或 RxJava。
  2. 优化布局:

    • 减少布局嵌套,使用 ConstraintLayout 等性能优化布局。
  3. 优化绘制操作:

    • onDraw 中缓存计算结果,避免创建不必要的对象。
  4. 减少内存使用:

    • 使用合适的数据结构,及时释放不再需要的对象。
  5. 优化动画:

    • 使用硬件加速,简化动画逻辑。

实践建议

  • 使用 ViewStub: 延迟加载不常用的布局。
  • RecyclerView 代替 ListView: 提高列表性能。
  • Profile UI 渲染: 使用 Android Profiler 分析布局和渲染性能。

Android内存泄漏

什么是内存泄漏?

内存泄漏是指应用中不再需要的对象无法被垃圾回收器回收,导致内存使用不断增加。

常见内存泄漏原因

  1. 静态变量持有上下文:

    • 静态变量持有 ActivityContext 的引用,导致无法释放。
  2. 匿名内部类和非静态内部类:

    • 内部类隐式持有外部类的引用。
  3. 未取消注册的监听器或接收器:

    • 忘记在 onDestroy 中取消注册的 BroadcastReceiverListener
  4. 未关闭的资源:

    • 未及时关闭的 CursorFileInputStream 等。
  5. Handler 导致的泄漏:

    • 使用 Handler 时,消息队列中的消息持有外部类的引用。

如何检测内存泄漏?

  1. Android Profiler:

    • 使用 Android Profiler 的内存分析工具检查内存使用情况。
  2. LeakCanary:

    • 一个开源库,能自动检测并报告内存泄漏。

如何避免内存泄漏?

  1. 避免持有 Activity 的静态引用:

    • 不在静态变量中直接引用 ActivityContext
  2. 使用静态内部类:

    • 将内部类声明为静态,并使用 WeakReference 持有外部类实例。
  3. 及时取消注册:

    • onDestroy 中取消注册所有监听器和接收器。
  4. 关闭资源:

    • 在使用完 CursorFile 等资源后立即关闭。
  5. 使用弱引用:

    • 使用 WeakReference 持有不重要的对象,允许其被回收。

实践建议

  • 使用应用上下文: 在需要长时间持有的对象中使用 Application Context
  • 谨慎使用单例模式: 确保单例中不持有 Activity 引用。
  • 定期检查代码: 使用工具检测和优化内存使用。

冷启动优化

  • 冷启动:系统中没有应用程序进程的任何实例,需要创建并启动一个新的进程。会执行 ApplicationonCreate 方法,然后创建 Activity
  • 热启动:系统中已有应用程序进程的实例,只需要将 Activity 带到前台。会执行 ActivityonCreateonStartonResume 方法。

冷启动流程:Application 的 构造方法 ->attchBaseContext() ->onCreate() ->Activity 的 onCreate() ->onStart() ->onResume()-> 测量布局绘制

冷启动优化

  1. 减少onCreate()方法的工作量
  2. 不要让application参与业务操作
  3. 不要在Application中做耗时操作
  4. 不要以静态变量的方式在Application中保存数据
  5. 布局/mainThread
  6. 懒加载延时初始化

其他优化

1、android不用静态变量存储数据

  • 内存泄漏:
    静态变量的生命周期与应用进程相同,如果持有对 Context(如 Activity)的引用,会导致内存泄漏,因为这些对象无法被垃圾回收。
  • 数据不一致性:
    静态变量在应用进程中是全局共享的,可能会被多个组件同时访问和修改,容易导致数据不一致。
  • 应用进程被系统终止:
    Android 系统可能会在内存不足时终止应用进程。静态变量的内容在这种情况下会丢失,无法保证数据持久性。

传输方式替代:文件/sp/contentProvider/SQLite/LiveData

2、SP不能跨进程使用、不能存储大数据

3、内存对象序列化

  • 序列化:将对象转换为字节序列的过程
  • 反序列化:将字节序列恢复为对象的过程
    Serializable:java提供的一个序列化接口,实现该接口,将对象序列化
    Parcelable:Android提供的一个序列化接口,更适合Android平台,使用内存的时候效率比Serializable高
    Parcelable 磁盘数据序列化有问题

4、避免在ui线程中作繁重的操作
Handler、AsyncTask、线程池、RxJava、IntentService、HandlerThread