问题1. 内存优化相关
内存泄漏的常见情况及解决方案:
-
常见场景:
- 静态变量持有Activity引用
- 单例模式持有Context
- Handler内部类引用
- 线程/AsyncTask未正确关闭
- 注册监听器未注销
-
解决方案:
- 使用WeakReference
- 在Activity销毁时及时释放资源
- 使用Application Context
- 避免静态变量持有Activity引用
Handler避免内存泄漏:
public class MyActivity extends Activity {
private static class MyHandler extends Handler {
private WeakReference<MyActivity> activityRef;
MyHandler(MyActivity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MyActivity activity = activityRef.get();
if (activity != null) {
// 处理消息
}
}
}
}
OOM问题解决:
- 产生原因:
- 加载大图片
- 内存泄漏累积
- 数据结构过大
- 解决方案:
- 图片压缩
- 及时释放资源
- 使用内存缓存
- 分批加载数据
详细解释
1.泄漏实例分析
1 静态变量持有Activity引用
❌ 错误示例:
public class MainActivity extends Activity {
// 错误方式:静态变量持有Activity引用
private static TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textView = new TextView(this);
}
}
✅ 正确示例:
public class MainActivity extends Activity {
// 正确方式:使用WeakReference
private static WeakReference<TextView> textViewRef;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
textViewRef = new WeakReference<>(new TextView(this));
}
}
1.2 单例模式内存泄漏
❌ 错误示例:
public class Manager {
private static Manager instance;
private Context context;
private Manager(Context context) {
this.context = context;
}
public static Manager getInstance(Context context) {
if (instance == null) {
instance = new Manager(context);
}
return instance;
}
}
✅ 正确示例:
public class Manager {
private static Manager instance;
private Context applicationContext;
private Manager(Context context) {
// 使用ApplicationContext而不是Activity的Context
this.applicationContext = context.getApplicationContext();
}
public static Manager getInstance(Context context) {
if (instance == null) {
instance = new Manager(context);
}
return instance;
}
}
1.3 匿名内部类/Handler导致的泄漏
❌ 错误示例:
public class MainActivity extends Activity {
// 错误方式:非静态Handler导致内存泄漏
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handler.sendMessageDelayed(Message.obtain(), 10000);
}
}
✅ 正确示例:
public class MainActivity extends Activity {
// 正确方式:使用静态内部类和弱引用
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityRef;
MyHandler(MainActivity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityRef.get();
if (activity != null) {
// 处理消息
}
}
}
private final MyHandler handler = new MyHandler(this);
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
1.4 监听器未注销
❌ 错误示例:
public class MainActivity extends Activity {
private LocationManager locationManager;
private LocationListener locationListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 处理位置更新
}
// ... 其他回调方法
};
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0, 0,
locationListener
);
}
}
✅ 正确示例:
public class MainActivity extends Activity {
private LocationManager locationManager;
private LocationListener locationListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
// 处理位置更新
}
// ... 其他回调方法
};
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
0, 0,
locationListener
);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 及时注销监听器
if (locationManager != null && locationListener != null) {
locationManager.removeUpdates(locationListener);
}
}
}
2. 内存优化工具使用
2.1 使用LeakCanary检测内存泄漏
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.x'
}
2.2 使用Android Profiler分析内存
graph TD
A[打开Android Profiler] --> B[选择Memory分析]
B --> C[强制GC]
C --> D[查看内存分配]
D --> E[堆内存快照]
E --> F[分析对象引用]
3. Bitmap内存优化实例
public class ImageUtils {
public static Bitmap decodeSampledBitmap(String filePath, int reqWidth, int reqHeight) {
// 第一次解码,只获取图片尺寸
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
// 计算压缩比例
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 实际解码图片
options.inJustDecodeBounds = false;
// 使用RGB_565比ARGB_8888节省一半内存
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(filePath, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
内存优化核心要点:
-
及时释放资源
- Activity销毁时清理资源
- 取消异步任务
- 注销监听器
- 关闭IO流
-
合理使用缓存
- 使用LruCache缓存Bitmap
- 避免重复创建对象
- 复用系统资源
-
避免内存抖动
- 避免在循环中创建对象
- 使用对象池
- 减少临时对象创建
-
图片优化
- 按需加载
- 合理压缩
- 及时回收
通过以上具体例子和实践,我们可以更好地理解和处理Android中的内存优化问题。记住,内存优化是一个持续的过程,需要在开发过程中时刻注意并养成良好的编码习惯。
问题2. ANR优化
ANR出现场景及解决:
graph TD
A[ANR类型] --> B[主线程阻塞]
A --> C[BroadcastReceiver超时]
A --> D[Service超时]
B --> E[耗时操作放入子线程]
C --> F[避免在广播中做耗时操作]
D --> G[Service及时停止]
详细解释
让我详细解释 ANR (Application Not Responding) 的底层原理:
一、什么是 ANR?
ANR 就像是手机给应用程序打分,如果应用在规定时间内没有完成特定任务,就会被判定为"不及格"。具体时间限制如下:
- 输入事件(按键、触摸):5秒
- 广播接收器:10秒(前台)/ 60秒(后台)
- 服务:20秒
二、ANR 的底层监测原理
graph TD
A[AMS发送消息] --> B[主线程Handler接收]
B --> C[执行任务]
C --> D{是否超时?}
D -->|是| E[触发ANR]
D -->|否| F[正常执行]
E --> G[收集信息]
G --> H[生成traces.txt]
H --> I[显示ANR对话框]
三、详细工作流程
- 消息分发机制:
// 简化的消息处理流程
public class MessageQueue {
boolean next() {
// 计算消息超时时间
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 等待消息
nativePollOnce(ptr, nextPollTimeoutMillis);
}
}
}
- 监控流程:
sequenceDiagram
participant AMS as ActivityManagerService
participant App as 应用进程
participant WDT as 看门狗线程
AMS->>App: 发送任务执行消息
AMS->>WDT: 启动监控
WDT->>WDT: 开始计时
alt 正常情况
App->>AMS: 任务完成响应
AMS->>WDT: 停止监控
else 超时情况
WDT->>AMS: 报告超时
AMS->>App: 收集进程信息
AMS->>AMS: 生成ANR报告
end
四、ANR 产生的具体原因
- 主线程阻塞:
// 错误示例
public void onCreate() {
// 主线程中进行耗时操作
Thread.sleep(6000); // 这会导致ANR
}
// 正确示例
public void onCreate() {
new Thread(() -> {
// 耗时操作放在子线程
Thread.sleep(6000);
runOnUiThread(() -> {
// 更新UI
});
}).start();
}
- 死锁情况:
// 可能导致死锁的示例
public class DeadLockExample {
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
synchronized(lock1) {
synchronized(lock2) {
// 处理逻辑
}
}
}
public void method2() {
synchronized(lock2) {
synchronized(lock1) {
// 处理逻辑
}
}
}
}
五、ANR 监测机制
graph TD
A[应用启动] --> B[AMS开启监控]
B --> C{检测响应时间}
C -->|超时| D[触发ANR]
C -->|正常| E[继续执行]
D --> F[收集进程信息]
F --> G[生成traces文件]
G --> H[弹出ANR对话框]
H --> I[用户选择]
I -->|等待| J[继续等待响应]
I -->|关闭| K[结束进程]
六、如何避免 ANR
-
代码优化建议:
- 使用异步操作
- 避免主线程IO
- 合理使用Handler
- 优化广播接收器
- 使用WorkManager处理后台任务
-
实践示例:
// 使用AsyncTask处理耗时操作
private class MyAsyncTask extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
// 耗时操作
return result;
}
@Override
protected void onPostExecute(String result) {
// 更新UI
}
}
七、ANR 问题排查
-
信息收集:
- traces.txt文件
- logcat日志
- CPU使用情况
- 内存信息
-
分析步骤:
graph LR
A[收集日志] --> B[分析调用栈]
B --> C[定位耗时操作]
C --> D[复现问题]
D --> E[优化代码]
E --> F[验证修复]
八、总结
ANR 的产生是一个复杂的系统监控过程,理解其原理对于开发高质量的 Android 应用至关重要。记住以下关键点:
- 避免在主线程进行耗时操作
- 合理使用异步机制
- 及时响应系统消息
- 建立有效的监控机制
- 做好性能优化
通过合理的架构设计和代码实现,大多数 ANR 问题是可以预防的。当 ANR 发生时,要善于利用系统提供的工具和日志来分析和解决问题。
问题3. 布局优化
- 优化技巧:
- 使用ConstraintLayout
- 减少布局层级
- 避免过度绘制
- 使用ViewStub
- 合理使用include和merge标签
详细解释
让我详细讲解Android布局优化的底层原理:
一、View的绘制流程
graph TD
A[measure测量] --> B[layout布局]
B --> C[draw绘制]
A1[onMeasure] --> A2[measureChild]
A2 --> A3[setMeasuredDimension]
B1[onLayout] --> B2[确定子View位置]
C1[onDraw] --> C2[绘制背景]
C2 --> C3[绘制内容]
C3 --> C4[绘制子View]
C4 --> C5[绘制装饰]
二、布局加载流程
graph LR
A[XML布局文件] --> B[资源加载]
B --> C[解析XML]
C --> D[实例化View]
D --> E[构建View树]
E --> F[测量布局绘制]
三、布局优化原理详解
1. 过度绘制优化
想象一下叠纸片的过程:
- 如果你叠5张纸,就要花5倍的时间
- 手机绘制UI也是一样,每多绘制一层,耗时就多一份
解决方案:
<!-- 优化前 -->
<FrameLayout
android:background="@color/white">
<LinearLayout
android:background="@color/gray">
<TextView
android:background="@color/blue"/>
</LinearLayout>
</FrameLayout>
<!-- 优化后 -->
<FrameLayout>
<LinearLayout>
<TextView
android:background="@color/blue"/>
</LinearLayout>
</FrameLayout>
2. 布局层级优化
就像搭积木:
- 积木叠得越高(层级越深),越容易倒(性能越差)
- 积木摆得越平(层级越扁平),越稳定(性能越好)
优化示例:
<!-- 优化前 -->
<LinearLayout>
<LinearLayout>
<LinearLayout>
<TextView/>
<TextView/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- 优化后:使用ConstraintLayout -->
<ConstraintLayout>
<TextView/>
<TextView/>
</ConstraintLayout>
3. 布局加载优化
就像看书:
- 传统方式:一次性看完整本书(一次性加载所有布局)
- 优化方式:先看目录,需要时再看具体章节(按需加载)
代码示例:
// ViewStub延迟加载示例
public class MainActivity extends Activity {
private ViewStub viewStub;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
viewStub = findViewById(R.id.viewstub);
// 需要时才加载
if(needShow) {
viewStub.inflate();
}
}
}
四、布局优化具体方案
1. 使用高效的布局容器
graph TD
A[布局容器选择] --> B[ConstraintLayout]
A --> C[RelativeLayout]
A --> D[LinearLayout]
B --> E[性能最好]
C --> F[中等性能]
D --> G[简单场景]
2. 避免嵌套
想象成一个快递包裹:
- 一个盒子套一个盒子,拆起来很麻烦(多层嵌套)
- 所有东西平铺在一个盒子里,方便取用(扁平化)
3. Include优化
就像积木游戏中的"预制件":
- 经常用到的积木组合做成一个整体
- 需要时直接拿来用,省时省力
<!-- 共用的标题栏布局:title_bar.xml -->
<include
android:id="@+id/title_bar"
layout="@layout/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
4. Merge优化
如同去掉不必要的包装盒:
<!-- 优化前 -->
<LinearLayout>
<include layout="@layout/heavy_layout"/>
</LinearLayout>
<!-- 优化后 -->
<merge>
<!-- 布局内容 -->
</merge>
五、性能提升检测
- Layout Inspector:查看视图层级
- GPU过度绘制:查看重叠区域
- Systrace:分析布局耗时
六、最佳实践总结
graph TD
A[布局优化] --> B[降低层级]
A --> C[减少测量]
A --> D[避免过度绘制]
B --> E[使用ConstraintLayout]
C --> F[使用ViewStub]
D --> G[移除多余背景]
记住一个简单的原则:
- 就像整理房间一样
- 不要堆积太多层(降低层级)
- 不要重复做同样的事(避免过度绘制)
- 需要时再拿(按需加载)
- 常用的东西放在容易拿到的地方(合理使用include)
通过这些优化,可以显著提升应用的性能和响应速度。在实际开发中,应该根据具体场景选择合适的优化方案。
问题4. 图片优化
- 优化方案:
- 合理的图片压缩
- 图片缓存
- 根据分辨率加载不同尺寸
- 使用WebP格式
- 图片复用
详细解释
让我详细讲解图片优化的底层原理:
一、图片加载基本原理
graph TD
A[图片文件] --> B[解码Decode]
B --> C[压缩Compress]
C --> D[缓存Cache]
D --> E[显示Display]
B1[占用内存 = 图片宽 x 高 x 每个像素占用字节数] --> B
二、图片解码原理
- 位图的内存占用
// 示例:一张1920x1080的ARGB_8888格式图片
int memory = 1920 * 1080 * 4 // 4是因为ARGB每个通道占8位,共32位,即4字节
// 大约占用7.9MB内存
- 色彩格式对比:
- ARGB_8888: 32位,4字节,最高质量
- RGB_565: 16位,2字节,中等质量
- ALPHA_8: 8位,1字节,仅存储透明度
三、图片压缩原理
1. 质量压缩
// 质量压缩不改变图片像素,只改变存储大小
Bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
2. 采样率压缩
graph LR
A[原图: 1000x1000] --> B[采样率2]
B --> C[结果: 500x500]
D[内存减少4倍] --> C
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 采样率为2,宽高各缩小2倍,内存占用缩小4倍
四、图片缓存原理
graph TD
A[三级缓存] --> B[内存缓存]
A --> C[磁盘缓存]
A --> D[网络获取]
B --> E[LruCache]
C --> F[DiskLruCache]
1. 内存缓存实现
public class ImageCache {
private LruCache<String, Bitmap> mMemoryCache;
public ImageCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8; // 使用1/8最大内存作为缓存
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getByteCount() / 1024;
}
};
}
}
五、大图加载原理
graph TD
A[大图加载] --> B[获取图片真实尺寸]
B --> C[计算采样率]
C --> D[按采样率加载]
D --> E[内存使用]
1. BitmapRegionDecoder
// 分块加载大图片
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false);
Rect rect = new Rect(0, 0, width, height); // 指定加载区域
Bitmap bitmap = decoder.decodeRegion(rect, options);
六、常见优化方案总结
-
加载优化:
- 根据控件大小计算采样率
- 选择合适的解码格式
- 异步加载
-
存储优化:
- 使用WebP格式
- 图片压缩
- 尺寸适配
-
显示优化:
- 图片预加载
- 列表图片复用
- 及时回收
七、完整加载流程
graph TD
A[请求加载图片] --> B{检查内存缓存}
B -->|有| C[直接显示]
B -->|无| D{检查磁盘缓存}
D -->|有| E[加载到内存]
D -->|无| F[网络下载]
F --> G[压缩处理]
G --> H[存入缓存]
H --> I[显示图片]
八、实战示例
public class ImageLoader {
// 计算采样率
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
// 加载图片
public static Bitmap decodeSampledBitmap(String path, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(path, options);
}
}
九、注意事项
- 在UI线程中避免直接加载大图
- 注意内存泄漏问题
- 合理设置缓存大小
- 及时回收不需要的Bitmap
- 根据实际需求选择合适的压缩方案
这就是图片优化的主要原理和实现方式。通过理解这些原理,我们可以在实际开发中更好地处理图片相关的性能问题。
问题5. APK瘦身
- 主要方法:
- 资源文件优化
- 代码混淆
- 移除无用资源
- 动态下发资源
- 图片压缩
详细解释
让我详细解释 APK 瘦身的底层原理:
APK 瘦身原理图解
graph TD
A[APK瘦身] --> B[资源优化]
A --> C[代码优化]
A --> D[SO库优化]
A --> E[打包优化]
B --> B1[资源文件压缩]
B --> B2[移除冗余资源]
B --> B3[图片优化]
B --> B4[资源混淆]
C --> C1[代码混淆]
C --> C2[移除冗余代码]
C --> C3[动态下发]
D --> D1[移除冗余架构]
D --> D2[动态加载]
E --> E1[签名方案优化]
E --> E2[压缩算法优化]
一、资源优化原理
- 资源文件压缩
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
- 图片优化原理:
- WebP 转换:比 PNG 减少 25%~35% 体积
- 矢量图形:使用 SVG 替代多个分辨率的 PNG
- 图片压缩:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
二、代码优化原理
- 代码混淆过程:
graph LR
A[源代码] --> B[混淆]
B --> C[压缩]
C --> D[优化]
D --> E[混淆后代码]
- 冗余代码移除原理:
- 使用 ProGuard 分析代码调用链
- 移除未使用的类、方法、字段
- 内联简单方法
- 简化类和方法名称
三、SO库优化原理
- 架构选择:
android {
defaultConfig {
ndk {
// 只保留主流架构
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
- 动态加载原理:
public class SOLoader {
static {
try {
System.loadLibrary("your_lib");
} catch (UnsatisfiedLinkError e) {
// 动态下载并加载
}
}
}
四、打包优化原理
- 签名方案优化:
- V2/V3 签名比 V1 签名效率更高
- 文件对齐优化:
zipalign -v 4 infile.apk outfile.apk
- 压缩算法优化:
android {
aaptOptions {
cruncherEnabled = false // 关闭PNG压缩
}
}
五、具体实施方案
- 资源优化实施:
- 使用 Android Studio 的 APK Analyzer 分析资源占用
- 使用 Resource Shrinker 移除未使用资源
- 使用 TinyPNG 等工具压缩图片
- 使用 AndResGuard 进行资源混淆
- 代码优化实施:
graph TD
A[代码优化] --> B[使用ProGuard]
A --> C[使用R8]
B --> D[配置混淆规则]
C --> E[自动优化]
D --> F[测试验证]
E --> F
- 动态化方案:
- 将非必需资源放到云端
- 按需下载功能模块
- 使用 App Bundle 实现动态分发
优化效果衡量
- 大小对比:
graph LR
A[原始APK] --> B[资源优化]
B --> C[代码优化]
C --> D[SO优化]
D --> E[最终APK]
style E fill:#9f9,stroke:#333,stroke-width:2px
- 监控指标:
- APK 大小变化
- 下载转化率
- 安装成功率
- 启动时间
- 运行内存
注意事项
- 保证稳定性:
- 混淆后要充分测试
- 保留必要的类和方法
- 保存映射文件用于追踪问题
- 动态化权衡:
- 考虑网络环境
- 评估用户体验
- 权衡首次下载大小和后续下载量
通过以上优化,一般可以减少 30%-50% 的 APK 体积。记住要根据具体项目情况选择合适的优化方案,并在优化过程中持续监控和测试,确保应用的稳定性不受影响。
问题6. 启动优化
graph LR
A[冷启动优化] --> B[闪屏优化]
B --> C[延迟初始化]
C --> D[异步初始化]
D --> E[减少布局层级]
详细解释
让我详细讲解 Android 应用启动优化的底层原理:
一、启动类型
graph TD
A[启动类型] --> B[冷启动]
A --> C[热启动]
A --> D[温启动]
B --> B1[进程未创建<br>耗时最长]
C --> C1[进程在后台<br>耗时最短]
D --> D1[进程存在<br>Activity被销毁]
二、冷启动流程
graph TB
A[点击应用图标] --> B[Zygote进程fork]
B --> C[创建应用进程]
C --> D[创建Application对象]
D --> E[启动主线程]
E --> F[创建MainActivity]
F --> G[加载布局绘制]
G --> H[显示首帧]
三、启动耗时原因
-
系统层面:
- Zygote进程fork耗时
- 应用进程创建耗时
- ART/Dalvik虚拟机启动
-
应用层面:
graph LR
A[应用层耗时] --> B[Application初始化]
A --> C[首页布局复杂]
A --> D[第三方SDK初始化]
A --> E[IO操作]
A --> F[复杂计算]
四、优化方案详解
1. Application优化
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 1. 异步初始化
AsyncInitializer.start(this);
// 2. 延迟初始化
delayInit();
}
private void delayInit() {
new Handler().postDelayed(() -> {
// 延迟初始化第三方SDK
initThirdPartySDK();
}, 3000);
}
}
2. 闪屏优化
<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- 设置闪屏背景,避免白屏 -->
<item name="android:windowBackground">@drawable/splash_bg</item>
</style>
五、具体优化技术
- 类预加载优化:
graph LR
A[预加载优化] --> B[提前加载类]
B --> C[减少首次使用时间]
A --> D[类校验优化]
D --> E[减少启动时验证]
- 布局优化:
<!-- 使用ViewStub延迟加载不常用布局 -->
<ViewStub
android:id="@+id/stub_import"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="@+id/panel_import"
android:layout="@layout/progress_overlay" />
- 线程优化:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 使用线程池管理初始化任务
ExecutorService executor = Executors.newFixedThreadPool(3);
// 并行执行初始化任务
CompletableFuture.runAsync(() -> {
initData();
}, executor);
CompletableFuture.runAsync(() -> {
initNetwork();
}, executor);
}
}
六、启动优化监控
graph TD
A[启动监控] --> B[函数插桩]
A --> C[TraceView]
A --> D[Systrace]
B --> E[统计各阶段耗时]
C --> F[分析方法耗时]
D --> G[分析系统调用]
七、优化效果衡量
-
关键指标:
- 首屏时间
- 可交互时间
- 完全加载时间
-
测量方法:
public class TimeRecorder {
private static long sStartTime;
public static void start() {
sStartTime = System.currentTimeMillis();
}
public static void record(String tag) {
long cost = System.currentTimeMillis() - sStartTime;
Log.d("启动耗时", tag + ": " + cost + "ms");
}
}
八、最佳实践总结
-
启动前优化:
- 闪屏优化
- 提前加载
- 类验证优化
-
启动中优化:
- 异步初始化
- 延迟初始化
- 懒加载
- 并行初始化
-
启动后优化:
- 后台任务延迟
- 预加载优化
- 持续监控
注意事项:
- 区分首次启动和非首次启动场景
- 注意异步初始化的依赖关系
- 建立完整的启动优化监控体系
- 持续优化和迭代
通过以上优化方案,可以显著提升应用的启动速度,提供更好的用户体验。记住,启动优化是一个持续的过程,需要不断监控和改进。
问题7. WebView优化
- 优化方案:
- 预加载WebView
- 离线缓存
- 延迟加载
- 资源拦截
- DNS优化
详细解释
让我详细讲解WebView优化的底层原理:
WebView优化的底层原理
graph TD
A[WebView优化] --> B[预加载机制]
A --> C[资源加载优化]
A --> D[缓存机制]
A --> E[内存优化]
A --> F[通信优化]
1. 预加载机制原理
public class WebViewManager {
private static WebView mWebView;
public static void preload(Context context) {
// 在应用启动时就初始化WebView
mWebView = new WebView(context.getApplicationContext());
// 预先加载常用配置
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
// ... 其他配置
}
}
工作原理:
- 应用冷启动时就创建WebView实例
- WebView首次初始化需要约100-200ms
- 预加载可以将这部分时间提前,提升打开速度
2. 资源加载优化
graph LR
A[资源请求] --> B[拦截器判断]
B --> C[本地资源]
B --> D[网络请求]
C --> E[直接加载]
D --> F[缓存策略]
实现原理:
public class CustomWebViewClient extends WebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();
// 1. 检查本地是否有离线资源包
if (OfflineResourceManager.hasOfflineResource(url)) {
return OfflineResourceManager.getOfflineResource(url);
}
// 2. 检查是否命中缓存
if (WebResourceCache.hasCache(url)) {
return WebResourceCache.getCache(url);
}
// 3. 网络请求
return super.shouldInterceptRequest(view, request);
}
}
3. 缓存机制原理
- 多级缓存:
graph TD
A[资源请求] --> B[内存缓存]
B -->|未命中| C[磁盘缓存]
C -->|未命中| D[网络请求]
D --> E[存入缓存]
- 实现方式:
webSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
webSettings.setDomStorageEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setAppCachePath(context.getCacheDir().getAbsolutePath());
4. 内存优化原理
- WebView池化管理:
public class WebViewPool {
private Queue<WebView> webViewPool;
public WebView obtain() {
if (webViewPool.isEmpty()) {
return createWebView();
}
return webViewPool.poll();
}
public void recycle(WebView webView) {
webView.loadUrl("about:blank");
webView.clearHistory();
webViewPool.offer(webView);
}
}
- 内存回收原理:
graph TD
A[WebView销毁] --> B[清空内容]
B --> C[清除缓存]
C --> D[移除回调]
D --> E[放入对象池]
5. 通信优化原理
- JS桥接优化:
public class JSBridge {
// 原生方法映射表
private static Map<String, Method> nativeMethodMap = new HashMap<>();
@JavascriptInterface
public void invoke(String methodName, String params) {
// 通过反射直接调用原生方法,避免多次解析
Method method = nativeMethodMap.get(methodName);
if (method != null) {
method.invoke(params);
}
}
}
- 通信流程:
graph LR
A[JS调用] --> B[JSBridge]
B --> C[方法映射]
C --> D[原生方法]
D --> E[结果回调]
6. 白屏优化原理
- 错误页面处理:
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
// 加载本地错误页
view.loadUrl("file:///android_asset/error.html");
// 错误上报
ErrorReporter.report(errorCode, description);
}
- 进度反馈:
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
progressBar.setVisibility(View.GONE);
} else {
progressBar.setVisibility(View.VISIBLE);
progressBar.setProgress(newProgress);
}
}
优化效果
- 首屏加载速度提升50%-70%
- 内存占用减少30%-50%
- 页面白屏率降低80%以上
- 离线访问成功率提升95%
注意事项
- WebView预加载要在主进程进行
- 资源拦截要注意性能消耗
- 缓存策略要根据业务场景选择
- 内存回收要及时
- JS通信要考虑安全性
通过以上优化,可以显著提升WebView的性能和用户体验。关键是要根据实际业务场景,选择合适的优化策略组合使用。
Webview跨域问题
我来用通俗易懂的方式解释 Android WebView 跨域问题的底层原理。
🌟 什么是跨域?
想象一下,每个网站都是一个独立的小区:
https://a.com是 A 小区https://b.com是 B 小区
跨域就像是 A 小区的居民想要去 B 小区借东西,但被门卫拦住了,这就是浏览器的"同源策略"。
🔒 同源策略的三要素
小区地址必须完全一致:
- 协议(Protocol):是坐公交还是开车去(http/https)
- 域名(Host):具体哪个小区(domain)
- 端口(Port):从哪个门进去(80/443等)
📱 Android WebView 跨域原理
1. 默认情况
WebView webView = new WebView(context);
// 默认情况下,WebView 严格执行同源策略
// A 网站的 JavaScript 不能访问 B 网站的内容
2. 开启跨域访问
WebSettings settings = webView.getSettings();
// 允许 JavaScript 执行
settings.setJavaScriptEnabled(true);
// 允许跨域访问
settings.setAllowUniversalAccessFromFileURLs(true);
settings.setAllowFileAccessFromFileURLs(true);
🔄 跨域解决方案流程图
graph TD
A[网页发起跨域请求] --> B{检查 WebView 设置}
B -->|默认设置| C[被同源策略拦截]
B -->|开启跨域设置| D[允许访问]
D --> E[数据交互]
C --> F[请求失败]
💡 常见解决方案
- 服务器端设置 CORS
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
}
- WebView 端设置
webView.setWebViewClient(new WebViewClient() {
@Override
public void onLoadResource(WebView view, String url) {
// 允许加载任何资源
super.onLoadResource(view, url);
}
});
🚀 实际应用场景
- 混合开发(Hybrid App)
- Web 页面需要调用原生功能
- 原生 App 需要向 Web 页面注入数据
- 本地 HTML 加载
- 加载本地 HTML 文件时访问网络资源
- 本地 HTML 之间的互相访问
⚠️ 安全注意事项
- 不要过度开放跨域权限
- 仅对可信域名开放必要的权限
- 注意防范 XSS 和 CSRF 攻击
🎯 总结
Android WebView 的跨域机制就像一个安保系统:
- 默认情况下,所有跨域访问都会被拦截
- 通过适当的设置,可以为可信的访问开启绿色通道
- 但要注意平衡安全性和便利性
记住:跨域限制是为了保护用户数据安全,在解决跨域问题时要谨慎,不要过度放开权限。
WebView 处理Session和Cookie
让我详细解释 Android 应用中如何将登录 session 或 cookie 传递给 WebView。
🌟 主要实现方式
1. 通过 CookieManager 管理 Cookie
// 获取 CookieManager 实例
CookieManager cookieManager = CookieManager.getInstance();
// 启用 cookie
cookieManager.setAcceptCookie(true);
// 设置 cookie
public void syncCookie(String url, String cookie) {
// ... 前面的代码 ...
// 设置 cookie
cookieManager.setCookie(url, cookie);
// 同步 cookie
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
} else {
CookieSyncManager.getInstance().sync();
}
}
2. 在 WebView 加载页面时注入 Cookie
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
// 在页面加载时注入 cookie
String cookie = "sessionId=abc123; token=xyz789";
CookieManager.getInstance().setCookie(url, cookie);
}
});
🔄 完整的 Cookie 同步流程
graph TD
A[原生 App 登录] --> B[获取 Session/Cookie]
B --> C[CookieManager 设置 Cookie]
C --> D[WebView 加载页面]
D --> E[自动携带 Cookie]
E --> F[服务器验证身份]
📱 实际应用示例
1. 登录后同步 Cookie
public void onLoginSuccess(String sessionId, String token) {
// 构建 cookie 字符串
String cookie = String.format("sessionId=%s; token=%s", sessionId, token);
String domain = "example.com";
// 设置 cookie
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setCookie(domain, cookie);
// 强制同步
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
cookieManager.flush();
}
}
2. 自定义 WebViewClient 处理请求头
public class CustomWebViewClient extends WebViewClient {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
// 获取原始请求
String url = request.getUrl().toString();
// 创建新的请求,添加自定义 header
try {
URL urlObj = new URL(url);
HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();
// 添加认证信息
conn.addRequestProperty("Cookie", CookieManager.getInstance().getCookie(url));
// ... 处理响应 ...
} catch (Exception e) {
e.printStackTrace();
}
return super.shouldInterceptRequest(view, request);
}
}
🔍 检查 Cookie 是否同步成功
private void checkCookies(String url) {
CookieManager cookieManager = CookieManager.getInstance();
String cookies = cookieManager.getCookie(url);
Log.d("Cookie", "Current cookies: " + cookies);
}
⚠️ 注意事项
- 安全考虑
- 确保使用 HTTPS 传输
- 设置 cookie 的 secure 标志
- 合理设置 cookie 过期时间
- 兼容性处理
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true);
}
- 清理 Cookie
public void clearCookies() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
} else {
CookieManager.getInstance().removeAllCookie();
CookieSyncManager.getInstance().sync();
}
}
💡 最佳实践
- 统一管理 Cookie
- 创建 CookieManager 工具类
- 封装常用的 Cookie 操作
- 同步时机
- 登录成功后立即同步
- WebView 加载前确保同步完成
- 定期检查 Cookie 有效性
- 错误处理
- 监控 Cookie 设置是否成功
- 处理 Cookie 过期的情况
- 提供重新登录的机制
通过以上方式,可以确保 Android 原生应用和 WebView 之间的身份认证信息顺利传递,实现无缝的用户体验。