我会逐个详细回答这些问题。由于内容较多,我们先回答前几个问题:
1. Android 系统架构组成
Android 系统架构自下而上分为五层:
-
Linux 内核层
- 提供底层驱动
- 内存管理
- 进程管理
- 安全机制
-
硬件抽象层(HAL)
- 为硬件提供标准接口
- 使上层不必关心底层硬件实现
-
原生系统库和Android运行时环境
- 核心库
- ART(Android Runtime)
- 系统C/C++库
-
应用框架层
- Activity Manager
- Window Manager
- Content Provider
- View System
- Package Manager
-
应用层
- 系统应用
- 第三方应用
2. SharedPreferences 线程安全性
SharedPreferences 的线程安全性分析:
// SharedPreferences 的读操作是线程安全的
Map<String, ?> map = sharedPreferences.getAll();
// 写操作需要注意
SharedPreferences.Editor editor = sharedPreferences.edit();
commit() 和 apply() 的区别:
-
commit()
- 同步写入
- 有返回值
- 会阻塞主线程
-
apply()
- 异步写入
- 无返回值
- 不会阻塞主线程
- 先写入内存,后写入磁盘
推荐使用场景:
- 需要立即知道写入结果:使用 commit()
- 一般情况:使用 apply()
3. Serializable 和 Parcelable 的区别
对比表格:
| 特性 | Serializable | Parcelable |
|---|---|---|
| 实现难度 | 简单 | 相对复杂 |
| 效率 | 较低 | 高 |
| 使用场景 | 本地存储、网络传输 | 内存间数据传递 |
| 原理 | 反射 | 序列化接口 |
示例代码:
// Serializable 实现
public class User implements Serializable {
private String name;
private int age;
}
// Parcelable 实现
public class User implements Parcelable {
private String name;
private int age;
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {...}
}
4. Android 7.0 新特性
主要特性:
- 文件共享变更
// 7.0之前
Uri photoUri = Uri.fromFile(file);
// 7.0之后
Uri photoUri = FileProvider.getUriForFile(context,
"包名.fileprovider", file);
- 多窗口支持
<!-- AndroidManifest.xml -->
<activity
android:resizeableActivity="true"
android:supportsPictureInPicture="true"/>
- 通知栏增强
- 后台优化
- 电池优化
- Java 8 特性支持
5. ArrayMap 和 HashMap 的区别
主要区别:
-
数据结构
- HashMap: 哈希表实现
- ArrayMap: 两个数组实现(一个存储hash值,一个存储key-value)
-
内存占用
// HashMap 每个实例占用约32字节
HashMap<String, String> hashMap = new HashMap<>();
// ArrayMap 每个实例占用约24字节
ArrayMap<String, String> arrayMap = new ArrayMap<>();
- 使用场景
- HashMap: 大数据量(1000以上)
- ArrayMap: 小数据量(1000以下)
详细解释
1. 数据结构对比
HashMap 结构:
graph TD
A[HashMap] --> B[数组 + 链表/红黑树]
B --> C[数组]
B --> D[链表]
B --> E[红黑树]
C --> F[当链表长度>8时]
F --> G[转换为红黑树]
ArrayMap 结构:
graph TD
A[ArrayMap] --> B[两个数组]
B --> C[mHashes数组]
B --> D[mArray数组]
C --> E[存储key的hash值]
D --> F[存储key-value对]
2. 工作原理详解
HashMap 工作原理:
// 简化的HashMap原理示意
public class HashMap<K,V> {
// 存储数据的数组
Node<K,V>[] table;
public V put(K key, V value) {
// 1. 计算hash值
int hash = hash(key);
// 2. 计算数组下标
int index = hash & (table.length - 1);
// 3. 如果发生碰撞,形成链表或红黑树
if (collision) {
// 处理碰撞
}
}
}
存储示意图:
索引: 0 1 2 3 4 5
+----+----+----+----+----+----+
| ↓ | | ↓ | | ↓ | |
+----+----+----+----+----+----+
| | |
↓ ↓ ↓
[k1,v1] [k2,v2] [k3,v3]
↓ ↓
[k4,v4] [k5,v5]
ArrayMap 工作原理:
public class ArrayMap<K,V> {
// 存储hash值的数组
int[] mHashes;
// 存储key-value的数组
Object[] mArray;
public V put(K key, V value) {
// 1. 计算hash值
int hash = key == null ? 0 : key.hashCode();
// 2. 二分查找位置
int index = binarySearch(mHashes, hash);
// 3. 插入数据
// hash值存入mHashes
// key-value存入mArray
}
}
存储示意图:
mHashes数组:
+----+----+----+----+
| h1 | h2 | h3 | h4 | (hash值按大小排序)
+----+----+----+----+
mArray数组:
+----+----+----+----+----+----+----+----+
| k1 | v1 | k2 | v2 | k3 | v3 | k4 | v4 |
+----+----+----+----+----+----+----+----+
3. 主要区别总结
- 内存占用:
// HashMap每个节点都需要额外的空间存储链表信息
class Node<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; // 额外的引用
}
// ArrayMap直接使用数组,更节省内存
int[] mHashes; // 仅存储hash值
Object[] mArray; // 直接存储key-value
- 查找效率:
HashMap查找] --> O(1)时间复杂度
ArrayMap查找 --> O(log n)时间复杂度
- 扩容机制:
// HashMap扩容
newCapacity = oldCapacity << 1 // 扩容为2倍
// ArrayMap扩容
if (size < 4) newCapacity = 4;
else if (size < 8) newCapacity = 8;
else newCapacity = size + (size >> 1); // 扩容为1.5倍
4. 使用建议
// 数据量小(<1000)时使用ArrayMap
ArrayMap<String, User> userMap = new ArrayMap<>(); // 省内存
// 数据量大时使用HashMap
HashMap<String, User> userMap = new HashMap<>(); // 查询快
性能对比:
- 数据量小时:ArrayMap 内存占用更少,性能接近
- 数据量大时:HashMap 查询性能更好
5. 实际应用场景
// 场景1:配置信息存储
ArrayMap<String, String> config = new ArrayMap<>();
config.put("theme", "dark");
config.put("language", "zh");
// 场景2:大量数据缓存
HashMap<String, Bitmap> imageCache = new HashMap<>();
imageCache.put("url1", bitmap1);
imageCache.put("url2", bitmap2);
这样的解释是否清晰易懂?如果还有任何疑问,请随时询问。
7. SparseArray 代替 HashMap 的原因
主要优势:
-
内存效率
- 避免装箱拆箱
- 数组实现,内存占用更小
-
适用场景
// 不推荐
HashMap<Integer, Object> map = new HashMap<>();
// 推荐
SparseArray<Object> array = new SparseArray<>();
详细讲解
让我用通俗易懂的方式详细解释 SparseArray 的底层原理。
1. 数据结构对比
想象一下两个不同的储物柜:
HashMap 的储物柜:
[门牌号(key)] -> [物品(value)]
1001 -> "手机"
1002 -> "钱包"
1003 -> "钥匙"
SparseArray 的储物柜:
位置索引: 0 1 2
键数组: [1001, 1002, 1003] // 按顺序排列的int数组
值数组: ["手机","钱包","钥匙"] // 对应的Object数组
2. 核心实现原理
public class SparseArray<E> {
// 两个基本数组
private int[] mKeys; // 存储键(必须是int类型)
private Object[] mValues; // 存储值
private int mSize; // 实际大小
// 二分查找实现
private int binarySearch(int[] array, int size, int value) {
int lo = 0;
int hi = size - 1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
int midVal = array[mid];
if (midVal < value) {
lo = mid + 1;
} else if (midVal > value) {
hi = mid - 1;
} else {
return mid;
}
}
return ~lo; // 找不到时返回应该插入的位置
}
}
3. 工作流程图
graph TD
A[查找/插入key] --> B{是否为空?}
B -->|是| C[直接插入到数组末尾]
B -->|否| D[二分查找key位置]
D --> E{是否找到?}
E -->|是| F[更新对应位置的value]
E -->|否| G[移动数组元素并插入]
4. 性能优势
- 内存效率
// HashMap 的一个整数键值对
class Entry<K,V> {
final int hash; // 4字节
final Integer key; // 16字节(对象)
V value; // 4字节起
Entry<K,V> next; // 4字节
}
// 总计:至少28字节/条
// SparseArray 的一个整数键值对
int key; // 4字节
Object value; // 4字节
// 总计:8字节/条
- 避免装箱拆箱
// HashMap 需要装箱
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "value"); // 1 被装箱为 Integer 对象
// SparseArray 直接使用基本类型
SparseArray<String> array = new SparseArray<>();
array.put(1, "value"); // 直接使用 int
5. 使用场景示例
// 推荐场景:键为int的小数据量场景
SparseArray<User> userArray = new SparseArray<>();
userArray.put(userId, user); // 插入
User user = userArray.get(userId); // 查找
userArray.remove(userId); // 删除
// 特别适合的场景
SparseArray<View> viewCache = new SparseArray<>();
viewCache.put(R.id.button, buttonView); // 视图缓存
6. 性能对比总结
| 操作 | HashMap | SparseArray |
|---|---|---|
| 内存占用 | 大 | 小 |
| 随机访问 | O(1) | O(log n) |
| 插入操作 | O(1) | O(n) |
| 删除操作 | O(1) | O(n) |
| 适用场景 | 大数据量 | 小数据量(<1000) |
7. 最佳实践建议
// 好的使用方式
private SparseArray<View> viewCache = new SparseArray<>(10); // 指定初始容量
viewCache.put(viewId, view);
// 避免频繁增删的场景
for (int i = 0; i < 1000; i++) {
// 不推荐在循环中频繁操作 SparseArray
sparseArray.put(i, value); // 每次插入都可能需要数组移动
}
8. 关键优势总结
-
内存效率高
- 避免了HashMap的Entry对象开销
- 避免了Integer的装箱开销
-
适合特定场景
- key为int类型
- 数据量较小
- 内存敏感的场景
-
Android系统优化
- Android系统内部大量使用
- 针对移动设备优化
- 与Android系统配合更好
通过以上详细解释,我们可以看到 SparseArray 是Android系统专门针对内存优化设计的一个数据结构,特别适合移动设备中键为整型的小数据量场景。虽然在某些操作上性能不如 HashMap,但在特定场景下的内存优势使它成为了Android开发中的重要工具。
8. PathClassLoader 和 DexClassLoader 区别
对比:
| 类加载器 | 加载路径 | 使用场景 |
|---|---|---|
| PathClassLoader | /data/app | 系统类和已安装应用 |
| DexClassLoader | 自定义路径 | 动态加载dex/jar/apk |
示例代码:
// DexClassLoader 使用示例
DexClassLoader dexClassLoader = new DexClassLoader(
dexPath, // dex文件路径
optimizedDirectory, // 优化后的dex文件存放路径
librarySearchPath, // native库的搜索路径
parent // 父类加载器
);
9. HttpClient 与 HttpUrlConnection 的区别
主要区别:
-
API设计
- HttpClient: 面向对象,功能丰富
- HttpURLConnection: 简单轻量
-
使用示例
// HttpURLConnection (推荐)
URL url = new URL("http://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int responseCode = conn.getResponseCode();
// HttpClient (已废弃)
HttpClient client = new DefaultHttpClient();
HttpGet request = new HttpGet("http://example.com");
HttpResponse response = client.execute(request);
替代原因:
- 维护成本高
- 版本兼容性问题
- 体积较大
10. Lifecycle 原理和使用场景
Lifecycle 是 Android Jetpack 组件之一,用于管理生命周期。
原理图:
onCreate()
↓
onStart()
↓
onResume()
↓
onPause()
↓
onStop()
↓
onDestroy()
使用示例:
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
// 处理 onResume 事件
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
// 处理 onPause 事件
}
}
// 在 Activity 中使用
lifecycle.addObserver(MyObserver())
使用场景:
- 生命周期感知组件
- 避免内存泄漏
- 数据持久化
- 后台任务管理
11. Android 签名机制
Android 签名版本对比:
| 版本 | 特点 | 算法 |
|---|---|---|
| V1(JAR签名) | Android 7.0以下 | SHA1withRSA |
| V2(APK签名) | Android 7.0引入 | 整个文件块验证 |
| V3 | Android 9.0引入 | 支持密钥轮替 |
| V4 | Android 11引入 | 支持外部存储 |
签名过程:
graph LR
A[生成密钥对] --> B[生成keystore文件]
B --> C[使用私钥签名]
C --> D[使用公钥验证]
12. Android APK构建流程
构建流程图:
graph TD
A[Java源码] --> B[Java编译器]
B --> C[.class文件]
C --> D[dx工具]
D --> E[.dex文件]
E --> F[apkbuilder]
F --> G[未签名的APK]
G --> H[签名工具]
H --> I[最终APK]
关键步骤代码示例:
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
signingConfig signingConfigs.release
}
}
}
13. Android 8.0、9.0新特性
Android 8.0:
- 通知渠道
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"通知渠道名称",
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
- 后台限制
- 画中画模式
- 自适应图标
Android 9.0:
- 刘海屏适配
<meta-data
android:name="android.notch_support"
android:value="true"/>
- 限制后台应用访问传感器
- 限制非SDK接口访问
14. Android 10适配要点
主要变更:
- 暗黑模式
<!-- 主题支持 -->
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
- 分区存储
// 适配示例
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 使用 MediaStore API
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "图片.jpg");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri uri = context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
- 后台定位限制
- 设备唯一标识符限制
15. APK安装过程
安装流程:
graph TD
A[复制APK到指定目录] --> B[解析AndroidManifest.xml]
B --> C[验证签名]
C --> D[dex优化]
D --> E[注册四大组件]
E --> F[完成安装]
16. Java与JS互调
Android调用JS:
// 方式1:通过WebView
webView.loadUrl("javascript:function()");
// 方式2:通过接口注入
webView.addJavascriptInterface(new JsInterface(), "android");
JS调用Android:
// 方式1:通过接口调用
window.android.method();
// 方式2:通过URL Scheme
location.href = "myapp://method?param=value";
优化方案:
- 使用WebView池
- 预加载WebView
- 数据预加载
17. JNI实现Java与C++互调
基本流程:
// Java代码
public class NativeClass {
static {
System.loadLibrary("native-lib");
}
// 声明native方法
public native String stringFromJNI();
}
// C++代码
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_NativeClass_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
return env->NewStringUTF("Hello from C++");
}
18. App启动流程
启动流程图:
graph TD
A[点击图标] --> B[Launcher进程]
B --> C[AMS]
C --> D[Zygote进程]
D --> E[fork新进程]
E --> F[ActivityThread]
F --> G[Application onCreate]
G --> H[Activity onCreate]
关键代码:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 应用初始化
}
}
优化建议:
- 延迟初始化
- 异步初始化
- 减少主线程耗时操作
- 使用启动器