第六章:ContentProvider(18 题)
6.1 ContentProvider是什么?它的作用是什么?
答案:
ContentProvider是Android的数据共享组件,用于在不同应用间共享数据。
ContentProvider的定义:
- 提供统一的数据访问接口
- 封装数据访问细节
- 支持跨应用数据共享
主要作用:
-
数据共享
- 应用间数据共享
- 统一的数据访问接口
-
数据封装
- 封装数据存储细节
- 提供标准CRUD接口
-
数据安全
- 通过URI和权限控制访问
- 保护数据安全
使用场景:
- 应用间数据共享
- 系统数据访问(联系人、媒体等)
- 数据统一管理
6.2 ContentProvider的URI是什么?
答案:
URI(Uniform Resource Identifier)是ContentProvider的数据标识符,用于定位数据。
URI格式:
content://authority/path/id
组成部分:
- scheme:固定为
content:// - authority:ContentProvider的标识(通常是包名)
- path:数据路径
- id:具体数据ID(可选)
示例:
content://com.example.provider/user/1
content://com.android.contacts/contacts
content://media/external/images/media/123
URI类型:
- 集合URI:指向一组数据(如
content://provider/user) - 单项URI:指向单个数据(如
content://provider/user/1)
使用方式:
Uri uri = Uri.parse("content://com.example.provider/user/1");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
6.3 ContentResolver的作用是什么?
答案:
ContentResolver是ContentProvider的客户端,用于访问ContentProvider提供的数据。
作用:
- 通过URI访问ContentProvider
- 执行CRUD操作
- 管理数据访问
主要方法:
- query():查询数据
- insert():插入数据
- update():更新数据
- delete():删除数据
使用示例:
ContentResolver resolver = getContentResolver();
// 查询
Uri uri = Uri.parse("content://com.example.provider/user");
Cursor cursor = resolver.query(uri, null, null, null, null);
// 插入
ContentValues values = new ContentValues();
values.put("name", "张三");
resolver.insert(uri, values);
// 更新
resolver.update(uri, values, "id=?", new String[]{"1"});
// 删除
resolver.delete(uri, "id=?", new String[]{"1"});
特点:
- 系统统一管理
- 通过URI访问
- 支持权限控制
6.4 ContentProvider的CRUD操作如何实现?
答案:
ContentProvider通过实现**query()、insert()、update()、delete()**方法提供CRUD操作。
实现方式:
- query() - 查询
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query("user", projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
- insert() - 插入
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long id = db.insert("user", null, values);
Uri newUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
- update() - 更新
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = db.update("user", values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
- delete() - 删除
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = db.delete("user", selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
注意事项:
- 操作后通知数据变化(notifyChange)
- 处理URI匹配
- 返回正确的数据类型
6.5 ContentProvider的URI匹配规则是什么?
答案:
ContentProvider通过UriMatcher匹配URI,确定操作的数据类型。
匹配规则:
- 定义匹配码
private static final int USER = 1;
private static final int USER_ID = 2;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI("com.example.provider", "user", USER);
uriMatcher.addURI("com.example.provider", "user/#", USER_ID);
}
- 匹配URI
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case USER:
// 查询所有用户
break;
case USER_ID:
// 查询指定ID的用户
String id = uri.getPathSegments().get(1);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
URI模式:
user:匹配content://authority/useruser/#:匹配content://authority/user/1(#表示数字)user/*:匹配content://authority/user/任意字符串(*表示任意字符串)
6.6 UriMatcher的作用是什么?
答案:
UriMatcher用于匹配URI,确定ContentProvider操作的数据类型。
作用:
- 匹配URI模式
- 确定操作类型
- 提取URI参数
使用方式:
- 创建UriMatcher
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- 添加URI模式
static {
uriMatcher.addURI("com.example.provider", "user", USER);
uriMatcher.addURI("com.example.provider", "user/#", USER_ID);
}
- 匹配URI
int matchCode = uriMatcher.match(uri);
switch (matchCode) {
case USER:
// 处理集合URI
break;
case USER_ID:
// 处理单项URI
String id = uri.getPathSegments().get(1);
break;
}
优势:
- 简化URI匹配
- 支持通配符
- 提取URI参数
6.7 如何自定义ContentProvider?
答案:
自定义ContentProvider需要继承ContentProvider并实现必要方法。
实现步骤:
- 创建ContentProvider类
public class MyContentProvider extends ContentProvider {
private static final String AUTHORITY = "com.example.provider";
private static final String TABLE_USER = "user";
private static final int USER = 1;
private static final int USER_ID = 2;
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, TABLE_USER, USER);
uriMatcher.addURI(AUTHORITY, TABLE_USER + "/#", USER_ID);
}
private DatabaseHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 实现查询
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// 实现插入
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// 实现更新
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 实现删除
}
@Override
public String getType(Uri uri) {
// 返回MIME类型
}
}
- 在AndroidManifest.xml中注册
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:exported="true" />
6.8 ContentProvider的onCreate()什么时候调用?
答案:
ContentProvider的onCreate()在ContentProvider创建时调用,早于Application的onCreate()。
调用时机:
- ContentProvider首次被访问时
- 在Application的onCreate()之前
- 只调用一次
特点:
- 在主线程调用
- 不能执行耗时操作
- 用于初始化数据库等
使用示例:
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true; // 返回true表示初始化成功
}
注意事项:
- 避免耗时操作
- 及时初始化必要资源
- 返回true表示初始化成功
6.9 ContentProvider的查询方法如何实现?
答案:
ContentProvider的query()方法用于查询数据,返回Cursor。
实现方式:
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (uriMatcher.match(uri)) {
case USER:
// 查询所有用户
return db.query("user", projection, selection,
selectionArgs, null, null, sortOrder);
case USER_ID:
// 查询指定ID的用户
String id = uri.getPathSegments().get(1);
String where = "id=?";
String[] whereArgs = new String[]{id};
return db.query("user", projection, where,
whereArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
参数说明:
- projection:要查询的列
- selection:查询条件
- selectionArgs:查询参数
- sortOrder:排序方式
注意事项:
- 返回Cursor
- 通知数据变化
- 处理URI匹配
6.10 ContentProvider的插入、更新、删除如何实现?
答案:
ContentProvider的insert()、update()、delete()方法实现数据修改操作。
实现方式:
- insert() - 插入
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case USER:
long id = db.insert("user", null, values);
Uri newUri = ContentUris.withAppendedId(uri, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
- update() - 更新
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case USER:
case USER_ID:
int count = db.update("user", values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
- delete() - 删除
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
switch (uriMatcher.match(uri)) {
case USER:
case USER_ID:
int count = db.delete("user", selection, selectionArgs);
getContext().getContentResolver().notifyChange(uri, null);
return count;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
注意事项:
- 操作后通知数据变化
- 返回正确的数据类型
- 处理URI匹配
6.11 ContentProvider的最佳实践有哪些?
答案:
ContentProvider最佳实践包括URI设计、性能优化、安全性等。
最佳实践:
-
URI设计
- 使用清晰的URI结构
- 遵循URI命名规范
- 使用UriMatcher匹配
-
性能优化
- 使用索引优化查询
- 避免全表扫描
- 使用批量操作
-
数据通知
- 操作后通知数据变化
- 使用notifyChange()通知
-
线程安全
- 使用同步机制
- 避免并发问题
-
权限控制
- 设置适当的权限
- 保护敏感数据
总结:
- 合理设计URI
- 优化性能
- 及时通知变化
- 注意线程安全
6.12 如何实现跨应用数据共享?
答案:
跨应用数据共享通过ContentProvider实现,需要设置权限。
实现步骤:
- 创建ContentProvider
public class MyContentProvider extends ContentProvider {
// 实现CRUD方法
}
- 在AndroidManifest.xml中注册并设置权限
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:exported="true"
android:permission="com.example.PERMISSION" />
- 定义权限
<permission
android:name="com.example.PERMISSION"
android:protectionLevel="normal" />
- 其他应用访问
<!-- 声明使用权限 -->
<uses-permission android:name="com.example.PERMISSION" />
// 访问ContentProvider
Uri uri = Uri.parse("content://com.example.provider/user");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
注意事项:
- 设置适当的权限
- 保护敏感数据
- 注意性能
6.13 ContentProvider的权限控制如何实现?
答案:
ContentProvider通过AndroidManifest.xml中的权限设置控制访问。
设置方式:
- 设置读取权限
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:readPermission="com.example.READ_PERMISSION" />
- 设置写入权限
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:writePermission="com.example.WRITE_PERMISSION" />
- 设置路径权限
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider">
<path-permission
android:path="/user"
android:permission="com.example.USER_PERMISSION" />
</provider>
- 定义权限
<permission
android:name="com.example.READ_PERMISSION"
android:protectionLevel="normal" />
权限级别:
- normal:自动授予
- dangerous:需要用户授权
- signature:相同签名自动授予
6.14 ContentProvider的性能优化有哪些?
答案:
ContentProvider性能优化可以从查询优化、批量操作、索引等方面优化。
优化策略:
-
查询优化
- 使用索引
- 避免全表扫描
- 限制返回数据量
-
批量操作
- 使用bulkInsert()批量插入
- 减少数据库操作次数
-
异步操作
- 耗时操作使用异步
- 避免阻塞主线程
-
缓存机制
- 缓存常用数据
- 减少数据库查询
-
数据库优化
- 合理设计表结构
- 使用索引
- 定期清理数据
最佳实践:
- 使用索引优化查询
- 使用批量操作
- 避免全表扫描
- 合理设计数据库
6.15 ContentProvider的线程安全问题如何解决?
答案:
ContentProvider的线程安全需要使用同步机制保护共享资源。
解决方案:
- 使用synchronized
private static final Object lock = new Object();
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
synchronized (lock) {
// 查询操作
}
}
- 使用ReentrantReadWriteLock
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
lock.readLock().lock();
try {
// 查询操作
} finally {
lock.readLock().unlock();
}
}
- 使用单例数据库
private static DatabaseHelper instance;
public static synchronized DatabaseHelper getInstance(Context context) {
if (instance == null) {
instance = new DatabaseHelper(context);
}
return instance;
}
注意事项:
- ContentProvider方法可能被多线程调用
- 需要保护共享资源
- 使用适当的同步机制
6.16 ContentObserver的作用是什么?
答案:
ContentObserver用于监听ContentProvider数据变化,当数据变化时收到通知。
作用:
- 监听数据变化
- 自动更新UI
- 响应数据修改
使用方式:
ContentObserver observer = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
// 数据变化时的处理
loadData();
}
};
Uri uri = Uri.parse("content://com.example.provider/user");
getContentResolver().registerContentObserver(uri, true, observer);
// 注销
getContentResolver().unregisterContentObserver(observer);
使用场景:
- 监听联系人变化
- 监听媒体文件变化
- 监听自定义数据变化
注意事项:
- 及时注销,避免内存泄漏
- 在合适的生命周期中注册和注销
6.17 如何使用ContentObserver监听数据变化?
答案:
使用ContentObserver监听数据变化需要注册观察者并实现onChange()方法。
使用步骤:
- 创建ContentObserver
ContentObserver observer = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
// 数据变化时的处理
loadData();
}
@Override
public void onChange(boolean selfChange, Uri uri) {
super.onChange(selfChange, uri);
// 可以获取变化的URI
}
};
- 注册观察者
Uri uri = Uri.parse("content://com.example.provider/user");
getContentResolver().registerContentObserver(uri, true, observer);
- 注销观察者
@Override
protected void onDestroy() {
super.onDestroy();
getContentResolver().unregisterContentObserver(observer);
}
参数说明:
- uri:要监听的URI
- notifyForDescendants:是否监听子URI的变化
注意事项:
- 及时注销
- 在合适的生命周期中注册
- 避免内存泄漏
6.18 ContentProvider和数据库的关系是什么?
答案:
ContentProvider是数据库的封装层,提供统一的数据访问接口。
关系说明:
- ContentProvider封装数据库访问
- 通过URI访问数据库
- 提供标准CRUD接口
典型实现:
public class MyContentProvider extends ContentProvider {
private DatabaseHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
return db.query("user", projection, selection,
selectionArgs, null, null, sortOrder);
}
}
优势:
- 封装数据库细节
- 提供统一接口
- 支持跨应用访问
- 支持权限控制
注意事项:
- ContentProvider不一定是数据库
- 可以是文件、网络等数据源
- 提供统一的数据访问接口
第七章:Intent(22 题)
7.1 Intent是什么?它的作用是什么?
答案:
Intent是Android的消息传递机制,用于组件间通信和启动组件。
Intent的定义:
- 消息对象,包含操作和数据
- 用于启动Activity、Service、BroadcastReceiver
- 支持显式和隐式调用
主要作用:
-
启动组件
- 启动Activity
- 启动Service
- 发送广播
-
传递数据
- 组件间数据传递
- 携带参数和结果
-
隐式调用
- 通过Action匹配组件
- 系统选择合适的组件
使用场景:
- 启动Activity
- 启动Service
- 发送广播
- 组件间通信
7.2 显式Intent和隐式Intent的区别是什么?
答案:
显式Intent和隐式Intent在指定方式和使用场景上有区别。
显式Intent:
- 特点:明确指定目标组件
- 指定方式:通过Component指定
- 使用场景:应用内调用
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
隐式Intent:
- 特点:通过Action、Category、Data匹配
- 指定方式:通过IntentFilter匹配
- 使用场景:跨应用调用、系统功能
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);
主要区别:
| 特性 | 显式Intent | 隐式Intent |
|---|---|---|
| 指定方式 | Component | Action/Category/Data |
| 匹配方式 | 直接指定 | 系统匹配 |
| 使用场景 | 应用内 | 跨应用 |
| 安全性 | 高 | 低 |
选择建议:
- 应用内调用:使用显式Intent
- 跨应用调用:使用隐式Intent
- 系统功能:使用隐式Intent
7.3 Intent的Action、Category、Data的作用是什么?
答案:
Action、Category、Data是Intent的匹配属性,用于隐式Intent匹配。
Action(动作):
- 作用:指定要执行的动作
- 示例:ACTION_VIEW、ACTION_SEND、ACTION_EDIT
- 使用:描述要做什么
Intent intent = new Intent(Intent.ACTION_VIEW);
Category(类别):
- 作用:指定Intent的类别
- 示例:CATEGORY_DEFAULT、CATEGORY_BROWSABLE
- 使用:进一步描述Intent
intent.addCategory(Intent.CATEGORY_DEFAULT);
Data(数据):
- 作用:指定要操作的数据URI
- 示例:http://、content://、file://
- 使用:描述要操作的数据
intent.setData(Uri.parse("https://www.example.com"));
匹配规则:
- Action必须匹配
- Category必须匹配(至少一个)
- Data必须匹配(如果指定)
7.4 Intent的Flags有哪些?它们的作用是什么?
答案:
Intent的Flags用于控制Activity的启动行为和任务栈管理。
常用Flags:
-
FLAG_ACTIVITY_NEW_TASK
- 在新任务栈中启动Activity
- 类似singleTask
-
FLAG_ACTIVITY_SINGLE_TOP
- 如果Activity在栈顶,复用实例
- 类似singleTop
-
FLAG_ACTIVITY_CLEAR_TOP
- 清除栈顶Activity
- 类似singleTask
-
FLAG_ACTIVITY_CLEAR_TASK
- 清除整个任务栈
- 启动新任务栈
-
FLAG_ACTIVITY_NO_HISTORY
- Activity不加入任务栈
- 返回时直接销毁
使用示例:
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
注意事项:
- Flags可以组合使用
- 某些Flags组合有特定含义
- 代码设置会覆盖AndroidManifest中的设置
7.5 Intent的Component的作用是什么?
答案:
Component用于显式指定目标组件,用于显式Intent。
作用:
- 明确指定目标组件(Activity、Service等)
- 用于显式Intent
- 包含包名和类名
使用方式:
- 通过构造函数指定
Intent intent = new Intent(this, SecondActivity.class);
- 通过setComponent()指定
ComponentName component = new ComponentName("com.example",
"com.example.SecondActivity");
Intent intent = new Intent();
intent.setComponent(component);
- 通过setClassName()指定
Intent intent = new Intent();
intent.setClassName("com.example", "com.example.SecondActivity");
特点:
- 直接指定目标,不经过匹配
- 更安全,避免误匹配
- 适合应用内调用
7.6 Intent的Extras如何传递数据?
答案:
Extras用于传递额外数据,通过Bundle存储键值对。
传递方式:
- putExtra()方法
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("name", "张三");
intent.putExtra("age", 25);
intent.putExtra("isStudent", true);
startActivity(intent);
- putExtras()方法(传递Bundle)
Bundle bundle = new Bundle();
bundle.putString("name", "张三");
bundle.putInt("age", 25);
intent.putExtras(bundle);
- 接收数据
Intent intent = getIntent();
String name = intent.getStringExtra("name");
int age = intent.getIntExtra("age", 0);
boolean isStudent = intent.getBooleanExtra("isStudent", false);
支持的数据类型:
- 基本类型(int、long、float、double、boolean等)
- String、CharSequence
- Parcelable对象
- Serializable对象
注意事项:
- 数据大小有限制(约1MB)
- 复杂对象需要实现Parcelable或Serializable
7.7 显式Intent的使用场景是什么?
答案:
显式Intent用于应用内组件调用,明确指定目标组件。
使用场景:
- 应用内Activity跳转
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
- 启动Service
Intent intent = new Intent(this, MyService.class);
startService(intent);
- 发送广播给指定接收者
Intent intent = new Intent(this, MyReceiver.class);
sendBroadcast(intent);
优势:
- 明确指定目标,安全
- 不经过系统匹配,效率高
- 适合应用内调用
注意事项:
- 必须知道目标组件的完整类名
- 只能调用同一应用的组件(除非跨应用)
7.8 隐式Intent的使用场景是什么?
答案:
隐式Intent用于跨应用调用和系统功能调用,通过匹配选择组件。
使用场景:
- 调用系统功能
// 打开网页
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
startActivity(intent);
// 发送短信
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "消息内容");
startActivity(intent);
- 跨应用调用
// 调用其他应用的功能
Intent intent = new Intent("com.example.ACTION");
startActivity(intent);
- 选择器(多个应用匹配时)
Intent intent = new Intent(Intent.ACTION_SEND);
Intent chooser = Intent.createChooser(intent, "选择应用");
startActivity(chooser);
优势:
- 灵活,不依赖具体实现
- 支持跨应用调用
- 系统自动匹配
注意事项:
- 可能匹配不到组件(需要处理)
- 可能匹配多个组件(需要选择器)
7.9 IntentFilter的作用是什么?
答案:
IntentFilter用于声明组件可以处理的Intent,用于隐式Intent匹配。
作用:
- 声明组件支持的Action、Category、Data
- 用于隐式Intent匹配
- 在AndroidManifest.xml中声明
声明方式:
<activity android:name=".MyActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
匹配规则:
- Action必须匹配
- Category必须匹配(至少一个)
- Data必须匹配(如果指定)
使用场景:
- 声明Activity可以处理的Intent
- 声明Service可以处理的Intent
- 声明BroadcastReceiver可以接收的广播
7.10 IntentFilter如何匹配Intent?
答案:
IntentFilter通过Action、Category、Data匹配Intent。
匹配规则:
-
Action匹配
- Intent的Action必须在IntentFilter中声明
- 至少匹配一个Action
-
Category匹配
- Intent的所有Category都必须在IntentFilter中声明
- IntentFilter可以声明额外的Category
-
Data匹配
- 如果Intent指定了Data,必须匹配IntentFilter的Data
- 匹配scheme、host、path等
匹配示例:
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"
android:host="www.example.com" />
</intent-filter>
匹配优先级:
- 完全匹配 > 部分匹配
- 多个匹配时,系统选择最佳匹配
7.11 多个Activity匹配同一个Intent时如何处理?
答案:
多个Activity匹配同一个Intent时,系统会显示选择器让用户选择。
处理方式:
- 自动显示选择器
Intent intent = new Intent(Intent.ACTION_SEND);
// 如果多个应用匹配,系统自动显示选择器
startActivity(intent);
- 手动创建选择器
Intent intent = new Intent(Intent.ACTION_SEND);
Intent chooser = Intent.createChooser(intent, "选择应用");
startActivity(chooser);
- 设置默认应用
- 用户可以选择"始终使用此应用"
- 系统记住用户选择
避免选择器:
- 使用显式Intent
- 设置更具体的IntentFilter
- 使用包名限制
注意事项:
- 选择器提升用户体验
- 允许用户选择应用
- 可以设置默认应用
7.12 Intent传递数据的方式有哪些?
答案:
Intent传递数据的方式包括Extras、Data、ClipData等。
传递方式:
- Extras(键值对)
intent.putExtra("key", "value");
- Data(URI)
intent.setData(Uri.parse("content://provider/data"));
- ClipData(大文本或URI列表)
ClipData clipData = ClipData.newPlainText("label", "text");
intent.setClipData(clipData);
- Bundle(批量传递)
Bundle bundle = new Bundle();
bundle.putString("key", "value");
intent.putExtras(bundle);
选择建议:
- 简单数据:使用Extras
- URI数据:使用Data
- 大文本:使用ClipData
- 批量数据:使用Bundle
7.13 Intent传递大数据如何优化?
答案:
Intent传递大数据需要优化策略,避免性能问题和限制。
优化策略:
- 使用URI传递
// 不传递数据本身,传递URI
intent.setData(Uri.parse("content://provider/data/1"));
- 使用全局变量
// 使用Application或单例存储数据
DataManager.getInstance().setData(data);
intent.putExtra("id", id);
- 使用文件传递
// 写入文件,传递文件路径
File file = writeToFile(data);
intent.putExtra("filePath", file.getAbsolutePath());
- 使用数据库
// 存入数据库,传递ID
long id = saveToDatabase(data);
intent.putExtra("id", id);
注意事项:
- Intent数据大小限制约1MB
- 大数据使用URI或全局变量
- 避免传递过大数据
7.14 Intent传递对象如何实现?
答案:
Intent传递对象需要对象实现Parcelable或Serializable接口。
方式1:Parcelable(推荐)
public class User implements Parcelable {
private String name;
private int age;
// Parcelable实现
protected User(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
}
// 传递
intent.putExtra("user", user);
// 接收
User user = intent.getParcelableExtra("user");
方式2:Serializable
public class User implements Serializable {
private String name;
private int age;
}
// 传递
intent.putExtra("user", user);
// 接收
User user = (User) intent.getSerializableExtra("user");
推荐:
- 使用Parcelable(性能更好)
- Serializable更简单但性能较差
7.15 Intent传递数据的大小限制是什么?
答案:
Intent传递数据有大小限制,约1MB。
限制说明:
- Binder事务缓冲区:约1MB
- 实际可用:略小于1MB(系统开销)
- 超过限制:会抛出TransactionTooLargeException
解决方案:
- 使用URI传递
intent.setData(Uri.parse("content://provider/data/1"));
- 使用全局变量
DataManager.getInstance().setData(data);
intent.putExtra("id", id);
- 使用文件
File file = writeToFile(data);
intent.putExtra("filePath", file.getAbsolutePath());
注意事项:
- 避免传递过大数据
- 使用替代方案
- 注意异常处理
7.16 Bundle的作用是什么?
答案:
Bundle是数据容器,用于存储键值对数据,常用于Intent传递数据。
作用:
- 存储键值对数据
- Intent传递数据的容器
- Activity状态保存和恢复
使用方式:
- 创建和添加数据
Bundle bundle = new Bundle();
bundle.putString("name", "张三");
bundle.putInt("age", 25);
- 通过Intent传递
intent.putExtras(bundle);
- 获取数据
Bundle bundle = getIntent().getExtras();
String name = bundle.getString("name");
int age = bundle.getInt("age");
- 状态保存
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "value");
}
特点:
- 支持多种数据类型
- 可以嵌套Bundle
- 大小限制约1MB
7.17 Intent的PendingIntent是什么?
答案:
PendingIntent是延迟执行的Intent,用于在将来某个时刻执行。
作用:
- 延迟执行Intent
- 允许其他应用以当前应用的身份执行Intent
- 用于通知、Alarm等场景
创建方式:
// 创建PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
);
使用场景:
- 通知点击
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(pendingIntent)
.build();
- 定时任务
AlarmManager alarmManager = getSystemService(AlarmManager.class);
alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- 桌面快捷方式
Intent shortcutIntent = new Intent(this, MainActivity.class);
PendingIntent shortcutPendingIntent = PendingIntent.getActivity(
this, 0, shortcutIntent, 0
);
7.18 PendingIntent的使用场景是什么?
答案:
PendingIntent用于延迟执行Intent的场景。
使用场景:
- 通知(Notification)
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentIntent(pendingIntent)
.build();
- 定时任务(AlarmManager)
AlarmManager alarmManager = getSystemService(AlarmManager.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
- 桌面快捷方式
Intent shortcutIntent = new Intent(this, MainActivity.class);
PendingIntent shortcutPendingIntent = PendingIntent.getActivity(
this, 0, shortcutIntent, 0
);
- 应用小部件(App Widget)
RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
特点:
- 允许其他应用执行
- 延迟执行
- 保持执行权限
7.19 PendingIntent的Flags有哪些?
答案:
PendingIntent的Flags用于控制PendingIntent的行为。
常用Flags:
-
FLAG_ONE_SHOT
- PendingIntent只能使用一次
- 使用后自动取消
-
FLAG_NO_CREATE
- 如果PendingIntent不存在,返回null
- 不创建新的PendingIntent
-
FLAG_CANCEL_CURRENT
- 取消现有的PendingIntent
- 创建新的PendingIntent
-
FLAG_UPDATE_CURRENT
- 如果PendingIntent存在,更新其Intent
- 如果不存在,创建新的
-
FLAG_IMMUTABLE(Android 12+)
- PendingIntent不可变
- 推荐使用,提升安全性
使用示例:
PendingIntent pendingIntent = PendingIntent.getActivity(
this,
requestCode,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
推荐:
- Android 12+使用FLAG_IMMUTABLE
- 更新场景使用FLAG_UPDATE_CURRENT
- 一次性使用FLAG_ONE_SHOT
7.20 Intent的安全问题如何避免?
答案:
Intent安全问题主要包括未授权访问和数据泄露。
安全问题:
-
未授权访问
- 其他应用可能启动组件
- 需要设置权限
-
数据泄露
- Intent数据可能被拦截
- 需要加密敏感数据
解决方案:
- 使用显式Intent
// 应用内调用使用显式Intent
Intent intent = new Intent(this, SecondActivity.class);
- 设置权限
<activity
android:name=".MyActivity"
android:permission="com.example.PERMISSION" />
- 验证数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
// 验证数据来源
if (!isValidIntent(intent)) {
finish();
return;
}
}
- 加密敏感数据
String encryptedData = encrypt(data);
intent.putExtra("data", encryptedData);
推荐:
- 应用内使用显式Intent
- 设置适当权限
- 验证数据来源
- 加密敏感数据
7.21 Intent的最佳实践有哪些?
答案:
Intent最佳实践包括使用方式、数据传递、安全性等。
最佳实践:
-
选择合适的Intent类型
- 应用内调用:使用显式Intent
- 跨应用调用:使用隐式Intent
-
合理传递数据
- 避免传递过大数据
- 使用URI或全局变量传递大数据
- 对象实现Parcelable
-
注意安全性
- 设置适当权限
- 验证数据来源
- 加密敏感数据
-
处理异常情况
- 处理匹配不到组件的情况
- 处理多个匹配的情况
-
性能优化
- 避免传递过大数据
- 使用合适的Flags
总结:
- 选择合适的Intent类型
- 合理传递数据
- 注意安全性
- 处理异常情况
7.22 Intent的测试如何进行?
答案:
Intent测试可以使用单元测试和集成测试。
测试方式:
- 单元测试
@Test
public void testIntentCreation() {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra("key", "value");
assertEquals("value", intent.getStringExtra("key"));
}
- Activity测试
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Test
public void testIntentLaunch() {
Intent intent = new Intent(ApplicationProvider.getApplicationContext(),
SecondActivity.class);
intent.putExtra("key", "value");
ActivityScenario<SecondActivity> scenario =
ActivityScenario.launch(intent);
scenario.onActivity(activity -> {
Intent receivedIntent = activity.getIntent();
assertEquals("value", receivedIntent.getStringExtra("key"));
});
}
}
- IntentFilter测试
@Test
public void testIntentFilter() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://www.example.com"));
List<ResolveInfo> resolveInfos =
getPackageManager().queryIntentActivities(intent, 0);
assertTrue(resolveInfos.size() > 0);
}
测试内容:
- Intent创建和数据传递
- Intent匹配
- IntentFilter声明
- 异常情况处理
第八章:Application 和 Context(10 题)
8.1 Application是什么?它的作用是什么?
答案:
Application是Android应用的全局单例对象,代表整个应用。
Application的定义:
- 应用启动时创建
- 应用生命周期内唯一实例
- 全局可访问
主要作用:
-
全局初始化
- 应用启动时初始化
- 初始化全局资源
-
全局数据存储
- 存储全局变量
- 共享数据
-
生命周期管理
- 监听应用生命周期
- 管理全局资源
使用方式:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 全局初始化
}
}
<application
android:name=".MyApplication"
... />
8.2 Application的生命周期是什么?
答案:
Application的生命周期与应用进程绑定,应用启动时创建,进程结束时销毁。
生命周期:
- onCreate():应用创建时调用,只调用一次
- onTerminate():应用终止时调用(通常不会调用,进程直接杀死)
- onLowMemory():内存不足时调用
- onTrimMemory():系统需要回收内存时调用
生命周期特点:
- 应用启动时创建
- 应用运行期间一直存在
- 进程结束时销毁
注意事项:
- onCreate()在主线程执行
- 避免耗时操作
- onTerminate()通常不会调用
8.3 Application的onCreate()什么时候调用?
答案:
Application的onCreate()在应用进程启动时调用,早于Activity的onCreate()。
调用时机:
- 应用进程启动时
- 在Activity的onCreate()之前
- 只调用一次
调用顺序:
应用进程启动
→ Application.onCreate()
→ Activity.onCreate()
使用场景:
- 初始化全局资源
- 初始化第三方SDK
- 设置全局配置
注意事项:
- 在主线程执行
- 避免耗时操作
- 只调用一次
8.4 Application的使用场景有哪些?
答案:
Application用于全局初始化和数据管理。
使用场景:
- 全局初始化
@Override
public void onCreate() {
super.onCreate();
// 初始化第三方SDK
// 初始化全局配置
}
- 全局数据存储
private String globalData;
public String getGlobalData() {
return globalData;
}
public void setGlobalData(String data) {
this.globalData = data;
}
- 生命周期监听
@Override
public void onLowMemory() {
super.onLowMemory();
// 释放资源
}
- 获取Application实例
MyApplication app = (MyApplication) getApplication();
注意事项:
- 避免存储过多数据
- 注意内存管理
- 避免内存泄漏
8.5 Context是什么?它的作用是什么?
答案:
Context是Android的上下文对象,提供应用环境和系统服务访问。
Context的定义:
- 应用的上下文环境
- 提供系统服务访问
- 提供资源访问
主要作用:
- 访问系统服务
LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
- 访问资源
String string = getString(R.string.app_name);
Drawable drawable = getDrawable(R.drawable.icon);
- 启动组件
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
- 文件操作
File file = new File(getFilesDir(), "file.txt");
特点:
- 所有组件都有Context
- 提供统一的环境接口
- 系统服务访问入口
8.6 Context的类型有哪些?
答案:
Context主要有Activity Context和Application Context两种类型。
Context类型:
-
Activity Context
- Activity的Context
- 生命周期与Activity绑定
- 可以启动Activity、显示Dialog等
-
Application Context
- Application的Context
- 生命周期与应用绑定
- 不能启动Activity、显示Dialog等
-
Service Context
- Service的Context
- 生命周期与Service绑定
-
其他Context
- BroadcastReceiver的Context
- ContentProvider的Context
获取方式:
// Activity Context
Context context = this; // 或 getBaseContext()
// Application Context
Context context = getApplicationContext();
// Service Context
Context context = this; // Service中
选择建议:
- 需要UI相关操作:使用Activity Context
- 不需要UI相关操作:使用Application Context
8.7 Activity Context和Application Context的区别是什么?
答案:
Activity Context和Application Context在生命周期和功能上有重要区别。
主要区别:
| 特性 | Activity Context | Application Context |
|---|---|---|
| 生命周期 | 与Activity绑定 | 与应用绑定 |
| 启动Activity | 可以 | 不可以 |
| 显示Dialog | 可以 | 不可以 |
| 内存泄漏风险 | 高 | 低 |
| 使用场景 | UI相关操作 | 非UI操作 |
Activity Context:
- 生命周期与Activity绑定
- Activity销毁时Context也失效
- 可以启动Activity、显示Dialog
- 容易造成内存泄漏
Application Context:
- 生命周期与应用绑定
- 应用运行期间一直存在
- 不能启动Activity、显示Dialog
- 不容易造成内存泄漏
选择建议:
- UI相关操作:使用Activity Context
- 非UI操作:使用Application Context
- 避免内存泄漏:优先使用Application Context
8.8 什么时候使用Activity Context,什么时候使用Application Context?
答案:
根据使用场景选择合适的Context类型。
使用Activity Context的场景:
-
UI相关操作
- 启动Activity
- 显示Dialog
- 绑定Layout
-
生命周期相关
- 需要Activity生命周期
- 需要Activity的配置
使用Application Context的场景:
-
非UI操作
- 启动Service
- 发送广播
- 访问系统服务
-
长时间持有
- 静态变量
- 单例对象
- 异步任务
示例:
// 使用Activity Context(UI操作)
Dialog dialog = new Dialog(this);
dialog.show();
// 使用Application Context(非UI操作)
Intent intent = new Intent(getApplicationContext(), MyService.class);
startService(intent);
推荐:
- 默认使用Application Context
- UI相关操作使用Activity Context
- 避免长时间持有Activity Context
8.9 Context的内存泄漏如何避免?
答案:
Context内存泄漏的常见原因和避免方法。
常见泄漏场景:
- 静态引用Activity Context
// 错误
static Context context;
// 正确:使用Application Context
static Context context = getApplicationContext();
- 单例持有Activity Context
// 错误
public class Singleton {
private Context context;
public Singleton(Context context) {
this.context = context; // 持有Activity Context
}
}
// 正确:使用Application Context
public class Singleton {
private Context context;
public Singleton(Context context) {
this.context = context.getApplicationContext();
}
}
- 异步任务持有Context
// 错误:AsyncTask持有Activity Context
new AsyncTask() {
// 持有Activity Context
}.execute();
// 正确:使用Application Context或WeakReference
Context appContext = getApplicationContext();
避免方法:
- 避免静态引用Activity Context
- 单例使用Application Context
- 异步任务使用Application Context或WeakReference
- 及时清理引用
8.10 Context的最佳实践有哪些?
答案:
Context最佳实践包括选择合适类型、避免内存泄漏、合理使用等。
最佳实践:
-
选择合适的Context类型
- UI操作使用Activity Context
- 非UI操作使用Application Context
- 长时间持有使用Application Context
-
避免内存泄漏
- 避免静态引用Activity Context
- 单例使用Application Context
- 及时清理引用
-
合理使用
- 不要滥用Context
- 避免传递Context过多
- 使用依赖注入
-
性能优化
- 缓存Application Context
- 避免重复获取
总结:
- 选择合适的Context类型
- 注意内存管理
- 合理使用Context
- 优化性能
第九章:Window 和 WindowManager(12 题)
9.1 Window是什么?它的作用是什么?
答案:
Window是Android的窗口抽象类,用于管理窗口的显示和交互。
Window的定义:
- 窗口的抽象表示
- 每个Activity都有一个Window
- 管理窗口的显示和交互
主要作用:
-
窗口管理
- 管理窗口的显示
- 管理窗口的层级
-
View容器
- 作为View的容器
- 管理View的显示
-
事件分发
- 分发触摸事件
- 分发键盘事件
特点:
- 抽象类,不能直接实例化
- 通过WindowManager管理
- 每个Activity对应一个Window
9.2 Window和View的关系是什么?
答案:
Window是View的容器,View是Window的内容。
关系说明:
- Window包含DecorView(根View)
- DecorView包含用户定义的布局
- View树在Window中显示
层次结构:
Window
└── DecorView
└── ContentView(用户布局)
Window的作用:
- 提供窗口框架
- 管理View的显示
- 处理窗口事件
View的作用:
- 显示具体内容
- 处理用户交互
- 绘制UI
9.3 Window的类型有哪些?
答案:
Window有三种类型:应用Window、子Window和系统Window。
Window类型:
-
应用Window(TYPE_APPLICATION)
- Activity的Window
- 层级范围:1-99
- 最常用的类型
-
子Window(TYPE_APPLICATION_SUB)
- 依附于应用Window
- 层级范围:1000-1999
- 如Dialog、PopupWindow
-
系统Window(TYPE_SYSTEM)
- 系统级Window
- 层级范围:2000-2999
- 需要系统权限
- 如状态栏、导航栏、悬浮窗
层级说明:
- 数值越大,层级越高
- 高层级Window覆盖低层级Window
- 系统Window层级最高
9.4 PhoneWindow的作用是什么?
答案:
PhoneWindow是Window的具体实现类,Activity使用的Window就是PhoneWindow。
作用:
- Window的具体实现
- 管理DecorView
- 处理窗口属性
特点:
- Activity默认使用PhoneWindow
- 封装窗口管理逻辑
- 提供窗口框架
使用方式:
// Activity中获取Window
Window window = getWindow();
// window实际上是PhoneWindow实例
功能:
- 管理DecorView
- 设置窗口属性
- 处理窗口事件
9.5 WindowManager的作用是什么?
答案:
WindowManager用于管理Window的添加、移除和更新。
作用:
-
添加Window
- 将Window添加到屏幕
- 设置Window参数
-
移除Window
- 从屏幕移除Window
- 释放资源
-
更新Window
- 更新Window属性
- 更新Window位置
使用方式:
WindowManager windowManager = getSystemService(WindowManager.class);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
windowManager.addView(view, params);
windowManager.updateViewLayout(view, params);
windowManager.removeView(view);
特点:
- 系统服务
- 统一管理所有Window
- 控制Window的显示
9.6 如何通过WindowManager添加View?
答案:
通过WindowManager添加View需要创建LayoutParams并调用addView()。
实现步骤:
- 获取WindowManager
WindowManager windowManager = (WindowManager)
getSystemService(Context.WINDOW_SERVICE);
- 创建LayoutParams
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP | Gravity.START;
params.x = 100;
params.y = 100;
- 添加View
View view = LayoutInflater.from(this).inflate(R.layout.floating_view, null);
windowManager.addView(view, params);
- 移除View
windowManager.removeView(view);
注意事项:
- 需要申请悬浮窗权限(系统Window)
- 及时移除View,避免内存泄漏
- 注意Window类型和权限
9.7 系统Window和应用Window的区别是什么?
答案:
系统Window和应用Window在权限、层级和使用场景上有区别。
主要区别:
| 特性 | 应用Window | 系统Window |
|---|---|---|
| 类型 | TYPE_APPLICATION | TYPE_SYSTEM_* |
| 层级 | 1-99 | 2000-2999 |
| 权限 | 不需要 | 需要系统权限 |
| 使用场景 | Activity、Dialog | 悬浮窗、系统UI |
应用Window:
- Activity的Window
- 不需要特殊权限
- 层级较低
系统Window:
- 系统级Window
- 需要系统权限或悬浮窗权限
- 层级较高,可以覆盖其他Window
注意事项:
- 系统Window需要特殊权限
- Android 6.0+需要动态申请悬浮窗权限
- 系统Window不易被系统回收
9.8 Window的层级(Z-order)如何管理?
答案:
Window的层级通过LayoutParams的type属性控制,数值越大层级越高。
层级管理:
- 设置Window类型
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
// 或
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
-
层级范围
- 应用Window:1-99
- 子Window:1000-1999
- 系统Window:2000-2999
-
层级效果
- 高层级Window覆盖低层级Window
- 系统Window层级最高
注意事项:
- 不能随意设置系统Window类型
- 需要相应权限
- 层级影响Window的显示顺序
9.9 悬浮窗如何实现?
答案:
悬浮窗通过WindowManager添加系统Window实现。
实现步骤:
- 申请权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
return;
}
}
- 创建悬浮View
View floatingView = LayoutInflater.from(this)
.inflate(R.layout.floating_view, null);
- 设置Window参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, // Android 8.0+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
);
params.gravity = Gravity.TOP | Gravity.START;
params.x = 100;
params.y = 100;
- 添加悬浮View
WindowManager windowManager = (WindowManager)
getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(floatingView, params);
- 移除悬浮View
windowManager.removeView(floatingView);
注意事项:
- Android 8.0+使用TYPE_APPLICATION_OVERLAY
- 需要动态申请权限
- 及时移除,避免内存泄漏
9.10 Window的权限如何申请?
答案:
系统Window需要申请悬浮窗权限,Android 6.0+需要动态申请。
申请步骤:
- 声明权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- 检查权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// 没有权限,申请权限
}
}
- 申请权限
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
- 检查权限结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (Settings.canDrawOverlays(this)) {
// 权限已授予
}
}
}
注意事项:
- Android 6.0+需要动态申请
- 用户可能拒绝权限
- 需要处理权限被拒绝的情况
9.11 Window的最佳实践有哪些?
答案:
Window最佳实践包括权限管理、内存管理、性能优化等。
最佳实践:
-
权限管理
- 动态申请悬浮窗权限
- 处理权限被拒绝的情况
- 检查权限状态
-
内存管理
- 及时移除Window
- 避免内存泄漏
- 在合适的时机添加和移除
-
性能优化
- 避免频繁添加和移除
- 复用Window
- 优化View层级
-
用户体验
- 提供关闭按钮
- 支持拖拽移动
- 合理设置位置和大小
总结:
- 注意权限管理
- 注意内存管理
- 优化性能
- 提升用户体验
9.12 Window的内存泄漏如何避免?
答案:
Window内存泄漏的常见原因和避免方法。
常见泄漏场景:
- 未移除Window
// 错误:添加后未移除
windowManager.addView(view, params);
// 正确:及时移除
windowManager.addView(view, params);
// 在合适的时机移除
windowManager.removeView(view);
- 持有Context引用
// 错误:持有Activity Context
private Context context;
// 正确:使用Application Context
private Context context = getApplicationContext();
- View持有外部引用
// 错误:View持有Activity引用
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 持有Activity引用
}
});
// 正确:使用WeakReference或静态内部类
避免方法:
- 及时移除Window
- 使用Application Context
- 避免View持有外部引用
- 在合适的生命周期中管理
第十章:任务栈和返回栈(15 题)
10.1 Task(任务栈)是什么?
答案:
Task是Activity的集合,用户完成一个任务的所有Activity。
Task的定义:
- Activity的集合
- 按照后进先出(LIFO)管理
- 用户完成一个任务的所有Activity
特点:
- 每个Task有独立的栈
- 通过返回键可以返回上一个Activity
- 系统管理Task的生命周期
Task的作用:
- 组织Activity
- 管理Activity的返回
- 提供任务切换功能
10.2 Task的作用是什么?
答案:
Task用于组织和管理Activity,提供任务切换和返回功能。
主要作用:
-
组织Activity
- 将相关Activity组织在一起
- 形成完整的任务流程
-
管理返回
- 通过返回键返回上一个Activity
- 按照后进先出原则
-
任务切换
- 用户可以切换不同任务
- 系统管理多个Task
-
生命周期管理
- 系统管理Task的生命周期
- 资源不足时可能回收Task
10.3 Task和Activity的关系是什么?
答案:
Task是Activity的容器,Activity是Task的组成部分。
关系说明:
- Task包含多个Activity
- Activity属于某个Task
- 一个Task可以有多个Activity
- 一个Activity只能属于一个Task
Task结构:
Task
├── Activity A
├── Activity B
└── Activity C
管理方式:
- 后进先出(LIFO)
- 新Activity压入栈顶
- 返回键弹出栈顶Activity
10.4 如何查看当前Task栈?
答案:
可以通过adb命令或代码查看当前Task栈。
方式1:adb命令
adb shell dumpsys activity activities
方式2:代码查看
ActivityManager activityManager = (ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(10);
for (ActivityManager.RunningTaskInfo task : tasks) {
Log.d("Task", "Task ID: " + task.id);
Log.d("Task", "Task Count: " + task.numActivities);
}
方式3:开发者选项
- 设置 → 开发者选项 → 显示布局边界
- 可以查看Activity的层级
注意事项:
- getRunningTasks()需要权限
- Android 5.0+限制使用
- 主要用于调试
10.5 Back Stack(返回栈)是什么?
答案:
Back Stack是Fragment的返回栈,用于管理Fragment的返回。
Back Stack的定义:
- Fragment的集合
- 按照后进先出管理
- 用于Fragment的返回
特点:
- 每个Activity可以有多个Back Stack
- 通过返回键返回上一个Fragment
- FragmentManager管理Back Stack
与Task的区别:
- Back Stack管理Fragment
- Task管理Activity
- 两者都是后进先出
10.6 返回栈和任务栈的区别是什么?
答案:
返回栈和任务栈在管理对象和作用范围上有区别。
主要区别:
| 特性 | 返回栈(Back Stack) | 任务栈(Task) |
|---|---|---|
| 管理对象 | Fragment | Activity |
| 作用范围 | Activity内 | 系统级 |
| 管理方式 | FragmentManager | ActivityManager |
| 使用场景 | Fragment导航 | Activity导航 |
返回栈:
- 管理Fragment
- 在Activity内
- FragmentManager管理
任务栈:
- 管理Activity
- 系统级
- ActivityManager管理
关系:
- 一个Task可以包含多个Activity
- 一个Activity可以包含多个Fragment
- 两者都是后进先出
10.7 返回键的处理流程是什么?
答案:
返回键的处理流程涉及Fragment返回栈和Activity任务栈。
处理流程:
-
检查Fragment返回栈
- 如果Fragment返回栈不为空,弹出栈顶Fragment
- 如果Fragment返回栈为空,继续下一步
-
检查Activity任务栈
- 如果Activity任务栈不为空,弹出栈顶Activity
- 如果Activity任务栈为空,应用退出
代码实现:
@Override
public void onBackPressed() {
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStack();
} else {
super.onBackPressed();
}
}
注意事项:
- 可以重写onBackPressed()自定义处理
- Android 10+推荐使用OnBackPressedDispatcher
10.8 如何控制返回栈的行为?
答案:
可以通过FragmentTransaction和**onBackPressed()**控制返回栈行为。
控制方式:
- 添加到返回栈
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null); // 添加到返回栈
transaction.commit();
- 弹出返回栈
fragmentManager.popBackStack(); // 弹出栈顶
fragmentManager.popBackStack("name", 0); // 弹出到指定名称
fragmentManager.popBackStackImmediate(); // 立即弹出
- 自定义返回处理
@Override
public void onBackPressed() {
if (shouldHandleBack()) {
// 自定义处理
} else {
super.onBackPressed();
}
}
注意事项:
- 只有添加到返回栈的Fragment才能返回
- 可以控制返回行为
- 注意Fragment的生命周期
10.9 TaskAffinity的作用是什么?
答案:
TaskAffinity用于指定Activity所属的Task,影响Activity的Task分配。
作用:
- 指定Activity所属的Task
- 影响singleTask的Task分配
- 控制Activity的Task归属
设置方式:
<activity
android:name=".MainActivity"
android:taskAffinity="com.example.main" />
使用场景:
- singleTask启动模式
- 需要将Activity放入特定Task
- 跨应用的Activity共享Task
注意事项:
- 默认是应用的包名
- 同一应用的Activity默认在同一Task
- 可以设置不同的TaskAffinity
10.10 如何将Activity放入不同的Task?
答案:
可以通过TaskAffinity和Intent Flags将Activity放入不同的Task。
方式1:TaskAffinity
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
android:taskAffinity="com.example.second" />
方式2:Intent Flags
Intent intent = new Intent(this, SecondActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
方式3:singleInstance启动模式
<activity
android:name=".ThirdActivity"
android:launchMode="singleInstance" />
注意事项:
- 需要配合启动模式使用
- 可能影响用户体验
- 需要合理设计
10.11 如何清除Task栈?
答案:
可以通过Intent Flags或代码清除Task栈。
方式1:Intent Flags
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
方式2:finishAffinity()
finishAffinity(); // 清除当前Task的所有Activity
方式3:clearTaskOnLaunch
<activity
android:name=".MainActivity"
android:clearTaskOnLaunch="true" />
使用场景:
- 退出登录时清除Task
- 重新启动应用时清除Task
- 需要清理Task的场景
10.12 launchMode和Task的关系是什么?
答案:
launchMode影响Activity在Task中的行为,决定Activity如何加入Task。
关系说明:
-
standard
- 每次启动都创建新实例
- 正常加入Task
-
singleTop
- 如果Activity在栈顶,复用实例
- 否则创建新实例,正常加入Task
-
singleTask
- 如果Activity在Task中存在,复用并清除其上Activity
- 如果不存在,创建新实例
- 可以在指定Task中(通过TaskAffinity)
-
singleInstance
- Activity独占一个Task
- 该Task中只有这一个Activity
影响:
- launchMode决定Activity如何加入Task
- 影响Task的结构
- 影响返回键的行为
10.13 任务栈的最佳实践有哪些?
答案:
任务栈最佳实践包括合理设计、用户体验、性能优化等。
最佳实践:
-
合理设计Task结构
- 相关Activity组织在同一Task
- 避免Task过多
- 合理使用启动模式
-
优化用户体验
- 提供清晰的导航
- 合理使用返回栈
- 避免Task混乱
-
性能优化
- 避免Task过多
- 及时清理不需要的Task
- 优化Task切换
-
安全性
- 合理使用TaskAffinity
- 避免Task被恶意利用
总结:
- 合理设计Task结构
- 优化用户体验
- 注意性能和安全
10.14 任务栈的常见问题有哪些?
答案:
任务栈的常见问题包括Task混乱、返回键异常、Activity重复等。
常见问题:
-
Task混乱
- Activity被放入错误的Task
- 多个Task包含相同Activity
- 解决:合理使用启动模式和TaskAffinity
-
返回键异常
- 返回键行为不符合预期
- 返回键无法返回
- 解决:检查返回栈和任务栈
-
Activity重复
- 同一个Activity有多个实例
- 解决:使用singleTop或singleTask
-
Task无法切换
- 无法切换到其他Task
- 解决:检查TaskAffinity和启动模式
调试方法:
- 使用adb命令查看Task
- 检查启动模式
- 检查TaskAffinity
10.15 如何调试任务栈问题?
答案:
可以通过adb命令、日志、代码调试任务栈问题。
调试方法:
- adb命令
# 查看所有Task
adb shell dumpsys activity activities
# 查看特定Task
adb shell dumpsys activity activities | grep "Task"
- 代码调试
ActivityManager activityManager = (ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(10);
for (ActivityManager.RunningTaskInfo task : tasks) {
Log.d("Task", "Task ID: " + task.id);
Log.d("Task", "Top Activity: " + task.topActivity.getClassName());
}
- 日志分析
- 查看Activity生命周期日志
- 查看Task切换日志
- 分析Task结构
注意事项:
- getRunningTasks()需要权限
- Android 5.0+限制使用
- 主要用于开发和调试
第十一章:进程和线程(18 题)
11.1 Android的进程模型是什么?
答案:
Android采用多进程模型,每个应用运行在独立的进程中。
进程模型:
- 每个应用有独立的进程
- 进程间通过Binder IPC通信
- 系统统一管理进程生命周期
特点:
- 进程隔离,提高安全性
- 进程独立,一个进程崩溃不影响其他进程
- 系统可以回收进程释放资源
进程管理:
- 系统根据优先级管理进程
- 资源不足时回收低优先级进程
- 前台进程优先级最高
11.2 进程和线程的区别是什么?
答案:
进程和线程在资源分配、通信方式和独立性上有区别。
主要区别:
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立内存空间 | 共享内存空间 |
| 通信方式 | IPC(Binder等) | 共享内存 |
| 独立性 | 完全独立 | 依赖进程 |
| 开销 | 大 | 小 |
| 崩溃影响 | 不影响其他进程 | 影响同一进程 |
进程:
- 独立的内存空间
- 进程间通信需要IPC
- 开销大,但更安全
线程:
- 共享进程内存空间
- 线程间可以直接通信
- 开销小,但需要注意同步
11.3 Android应用进程的优先级有哪些?
答案:
Android应用进程有5个优先级,从高到低。
进程优先级:
-
前台进程(Foreground)
- 用户正在交互的进程
- 优先级最高,最后被回收
-
可见进程(Visible)
- 用户可见但不可交互的进程
- 优先级较高
-
服务进程(Service)
- 运行Service的进程
- 优先级中等
-
后台进程(Background)
- 不可见的进程
- 优先级较低,容易被回收
-
空进程(Empty)
- 没有Activity的进程
- 优先级最低,最先被回收
优先级影响:
- 系统根据优先级回收进程
- 低优先级进程更容易被回收
- 前台进程最后被回收
11.4 进程的启动方式有哪些?
答案:
Android进程通过Zygote进程fork启动。
启动流程:
- 用户启动应用
- ActivityManagerService检查进程
- 如果进程不存在,从Zygote fork新进程
- 加载应用代码
- 创建Application
- 启动Activity
特点:
- 从Zygote fork,继承预加载资源
- 快速启动
- 系统统一管理
Zygote的作用:
- 预加载常用类库
- 通过fork创建新进程
- 提升启动速度
11.5 如何实现多进程?
答案:
可以通过AndroidManifest.xml配置实现多进程。
实现方式:
<activity
android:name=".MainActivity"
android:process=":remote" />
<service
android:name=".MyService"
android:process="com.example.remote" />
进程名称:
:remote:私有进程(应用内)com.example.remote:全局进程(可跨应用)
注意事项:
- 多进程需要处理数据共享
- 进程间通信使用Binder
- 注意内存占用
11.6 多进程的优缺点是什么?
答案:
多进程有优点和缺点,需要根据场景选择。
优点:
-
内存隔离
- 进程独立,内存不共享
- 一个进程崩溃不影响其他进程
-
安全性
- 进程隔离,提高安全性
- 数据不共享
-
性能
- 可以利用多核CPU
- 独立的内存管理
缺点:
-
内存占用
- 每个进程独立内存
- 内存占用增加
-
通信复杂
- 进程间通信需要IPC
- 实现复杂
-
数据共享
- 数据不共享,需要特殊处理
- 同步复杂
11.7 多进程的数据共享如何实现?
答案:
多进程数据共享可以通过ContentProvider、SharedPreferences、文件等方式。
实现方式:
- ContentProvider
// 通过ContentProvider共享数据
Uri uri = Uri.parse("content://com.example.provider/data");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
- SharedPreferences(MODE_MULTI_PROCESS)
SharedPreferences prefs = getSharedPreferences("prefs",
Context.MODE_MULTI_PROCESS);
- 文件
// 通过文件共享数据
File file = new File(getExternalFilesDir(null), "data.txt");
- Binder IPC
// 通过AIDL或Messenger通信
注意事项:
- MODE_MULTI_PROCESS已废弃
- 推荐使用ContentProvider
- 注意数据同步
11.8 多进程的常见问题有哪些?
答案:
多进程的常见问题包括数据不同步、单例失效、静态变量失效等。
常见问题:
-
数据不同步
- 每个进程有独立内存
- 数据不共享
- 解决:使用ContentProvider或文件共享
-
单例失效
- 每个进程有独立的单例
- 解决:使用进程间通信
-
静态变量失效
- 每个进程有独立的静态变量
- 解决:使用进程间通信
-
Application多次创建
- 每个进程都会创建Application
- 解决:在onCreate()中判断进程
解决方案:
- 使用ContentProvider共享数据
- 使用进程间通信
- 在Application中判断进程
11.9 Android的主线程是什么?
答案:
Android的主线程是UI线程,负责UI更新和事件处理。
主线程特点:
- 应用启动时创建
- 负责UI更新
- 处理用户交互
- 执行生命周期方法
主线程限制:
- 不能执行耗时操作
- 耗时操作会导致ANR
- 必须在子线程执行耗时操作
主线程使用:
- UI更新必须在主线程
- 事件处理在主线程
- 生命周期方法在主线程执行
11.10 为什么不能在主线程执行耗时操作?
答案:
主线程执行耗时操作会导致ANR(Application Not Responding)。
原因:
-
ANR超时
- Activity:5秒
- BroadcastReceiver:10秒
- Service:20秒
-
UI阻塞
- 耗时操作阻塞UI更新
- 用户无法交互
-
系统限制
- 系统检测到主线程阻塞
- 弹出ANR对话框
解决方案:
- 耗时操作在子线程执行
- 使用AsyncTask、Thread、线程池等
- 使用Kotlin协程、RxJava等
11.11 线程的创建方式有哪些?
答案:
线程创建方式包括Thread、Runnable、线程池等。
创建方式:
- 继承Thread
class MyThread extends Thread {
@Override
public void run() {
// 执行任务
}
}
MyThread thread = new MyThread();
thread.start();
- 实现Runnable
Runnable runnable = new Runnable() {
@Override
public void run() {
// 执行任务
}
};
Thread thread = new Thread(runnable);
thread.start();
- 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(runnable);
推荐:
- 使用线程池管理线程
- 避免频繁创建线程
- 注意线程安全
11.12 线程的同步机制有哪些?
答案:
线程同步机制包括synchronized、Lock、volatile等。
同步机制:
- synchronized
synchronized (lock) {
// 同步代码块
}
- ReentrantLock
Lock lock = new ReentrantLock();
lock.lock();
try {
// 同步代码块
} finally {
lock.unlock();
}
- volatile
private volatile boolean flag;
- Atomic类
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
选择建议:
- 简单场景使用synchronized
- 复杂场景使用Lock
- 原子操作使用Atomic类
11.13 进程的保活机制有哪些?
答案:
进程保活机制包括前台Service、双进程守护等,但需要注意合规性。
保活机制:
-
前台Service
- 提升进程优先级
- 必须显示通知
-
START_STICKY
- Service被杀死后自动重启
-
双进程守护
- 两个进程互相守护
- 不推荐,可能被系统限制
-
JobScheduler/WorkManager
- 系统统一调度
- 推荐方式
注意事项:
- Android 8.0+限制后台服务
- 过度保活可能影响用户体验
- 推荐使用系统推荐方式
11.14 进程的优先级如何提升?
答案:
可以通过前台Service、通知等方式提升进程优先级。
提升方式:
- 前台Service
startForeground(1, notification);
- 通知
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(1, notification);
- Activity可见
- 保持Activity可见
- 提升进程优先级
注意事项:
- 不能随意提升优先级
- 需要合理使用
- 注意用户体验
11.15 进程的内存管理如何实现?
答案:
进程内存管理通过系统回收机制和应用优化实现。
内存管理:
-
系统回收
- 系统根据优先级回收进程
- 低优先级进程优先回收
-
应用优化
- 及时释放资源
- 避免内存泄漏
- 使用内存分析工具
-
内存监控
- 监控内存使用
- 及时处理内存问题
最佳实践:
- 及时释放资源
- 避免内存泄漏
- 使用内存分析工具
- 优化内存使用
11.16 进程的最佳实践有哪些?
答案:
进程最佳实践包括合理设计、内存管理、性能优化等。
最佳实践:
-
合理设计
- 避免不必要的多进程
- 合理使用多进程
-
内存管理
- 及时释放资源
- 避免内存泄漏
-
性能优化
- 优化进程启动
- 减少内存占用
-
用户体验
- 避免过度保活
- 合理使用前台Service
总结:
- 合理设计进程结构
- 注意内存管理
- 优化性能
- 提升用户体验
11.17 线程的最佳实践有哪些?
答案:
线程最佳实践包括使用线程池、线程安全、避免泄漏等。
最佳实践:
-
使用线程池
- 避免频繁创建线程
- 统一管理线程
-
线程安全
- 使用同步机制
- 避免竞态条件
-
避免泄漏
- 及时停止线程
- 避免持有Context引用
-
性能优化
- 合理使用线程
- 避免线程过多
总结:
- 使用线程池
- 注意线程安全
- 避免内存泄漏
- 优化性能
11.18 进程和线程的监控如何实现?
答案:
可以通过系统API、工具等方式监控进程和线程。
监控方式:
- 系统API
ActivityManager activityManager = (ActivityManager)
getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
-
工具监控
- Android Studio Profiler
- LeakCanary
- Systrace
-
日志监控
- 查看logcat日志
- 分析进程和线程信息
监控内容:
- 进程内存使用
- 线程数量
- CPU使用率
- 内存泄漏
第十二章:Binder 机制(15 题)
12.1 Binder是什么?它的作用是什么?
答案:
Binder是Android的进程间通信(IPC)机制,用于跨进程通信。
Binder的定义:
- Android特有的IPC机制
- 基于Linux内核的Binder驱动
- 用于跨进程通信
主要作用:
-
进程间通信
- 应用间通信
- 应用与系统服务通信
-
数据传递
- 传递基本类型
- 传递Parcelable对象
-
方法调用
- 跨进程方法调用
- 类似本地方法调用
特点:
- 高性能(一次拷贝)
- 安全性好
- 系统统一管理
12.2 为什么Android使用Binder而不是其他IPC机制?
答案:
Android选择Binder主要因为性能、安全性和易用性。
优势:
-
性能好
- 一次拷贝(比Socket的两次拷贝快)
- 内存映射,效率高
-
安全性高
- 基于Linux内核
- 系统统一管理
- 支持权限控制
-
易用性好
- 类似本地方法调用
- 支持AIDL自动生成代码
与其他IPC对比:
- Socket:性能较差,需要序列化
- 共享内存:安全性较差
- 管道:功能有限
总结:
- Binder在性能、安全性和易用性上都有优势
- 是Android的最佳选择
12.3 Binder的通信原理是什么?
答案:
Binder通信基于Linux内核的Binder驱动,通过内存映射实现。
通信流程:
- Client进程通过Binder驱动发送请求
- Binder驱动将请求转发到Server进程
- Server进程处理请求并返回结果
- Binder驱动将结果返回给Client进程
关键组件:
- Binder驱动:Linux内核模块,管理IPC
- ServiceManager:系统服务管理器
- Binder对象:进程间通信的代理
优势:
- 一次拷贝,性能好
- 内存映射,效率高
- 系统统一管理
12.4 Binder的架构是什么?
答案:
Binder架构包括Client、Server、Binder驱动和ServiceManager。
架构层次:
Client进程
↓
Binder代理(Proxy)
↓
Binder驱动(Kernel)
↓
Binder实现(Stub)
↓
Server进程
组件说明:
- Client:调用方
- Server:服务提供方
- Binder驱动:内核模块,管理IPC
- ServiceManager:系统服务管理器
工作流程:
- Client通过Proxy调用方法
- Proxy通过Binder驱动发送请求
- Binder驱动转发到Server
- Server的Stub处理请求
- 结果通过Binder驱动返回
12.5 Binder的客户端如何实现?
答案:
Binder客户端通过Proxy调用服务端方法。
实现方式:
// 通过AIDL生成Proxy
IMyService myService = IMyService.Stub.asInterface(binder);
myService.doSomething("data");
流程:
- 绑定Service获取Binder
- 通过Stub.asInterface()获取Proxy
- 通过Proxy调用方法
- 方法调用通过Binder驱动转发到Server
特点:
- 类似本地方法调用
- 自动处理跨进程
- 透明使用
12.6 Binder的服务端如何实现?
答案:
Binder服务端通过Stub实现服务方法。
实现方式:
private IMyService.Stub binder = new IMyService.Stub() {
@Override
public void doSomething(String data) {
// 实现方法
}
};
@Override
public IBinder onBind(Intent intent) {
return binder;
}
流程:
- 实现Stub接口
- 在onBind()中返回Binder
- 系统通过Binder驱动转发请求
- Stub处理请求并返回结果
特点:
- 实现Stub接口
- 处理跨进程请求
- 返回结果
12.7 Binder的代理模式是什么?
答案:
Binder使用代理模式,Client通过Proxy调用Server的方法。
代理模式:
- Proxy:客户端代理,在Client进程
- Stub:服务端实现,在Server进程
- Client通过Proxy调用,实际执行在Server
工作流程:
Client → Proxy → Binder驱动 → Stub → Server
优势:
- 对Client透明
- 类似本地方法调用
- 自动处理跨进程
12.8 Binder的传输数据如何实现?
答案:
Binder通过Parcel传输数据,支持基本类型和Parcelable对象。
传输方式:
// 写入数据
Parcel parcel = Parcel.obtain();
parcel.writeString("data");
parcel.writeInt(100);
// 读取数据
String data = parcel.readString();
int value = parcel.readInt();
支持的数据类型:
- 基本类型(int、long、float、double等)
- String、CharSequence
- Parcelable对象
- Bundle
注意事项:
- 数据大小有限制
- 需要实现Parcelable
- 注意数据顺序
12.9 AIDL和Binder的关系是什么?
答案:
AIDL是Binder的接口定义语言,用于自动生成Binder代码。
关系:
- AIDL定义接口
- 自动生成Proxy和Stub
- 简化Binder使用
使用流程:
- 定义AIDL接口
- 系统自动生成Proxy和Stub
- 实现Stub
- Client通过Proxy调用
优势:
- 简化Binder使用
- 自动生成代码
- 类型安全
12.10 Messenger和Binder的关系是什么?
答案:
Messenger是基于Binder的封装,用于简化跨进程通信。
关系:
- Messenger内部使用Binder
- 封装Message通信
- 简化使用
实现:
Messenger messenger = new Messenger(new Handler() {
@Override
public void handleMessage(Message msg) {
// 处理消息
}
});
特点:
- 基于Binder
- 使用Message通信
- 比AIDL简单
12.11 Binder的性能如何优化?
答案:
Binder性能优化可以从数据传输、调用频率等方面优化。
优化策略:
-
减少数据传输
- 只传递必要数据
- 避免传递大数据
-
批量操作
- 合并多个调用
- 减少调用次数
-
异步调用
- 使用异步调用
- 避免阻塞
注意事项:
- Binder调用有开销
- 避免频繁调用
- 优化数据传输
12.12 Binder的内存管理如何实现?
答案:
Binder内存管理通过引用计数和自动回收实现。
管理机制:
- Binder对象有引用计数
- 引用计数为0时自动回收
- 系统统一管理
注意事项:
- 及时释放Binder引用
- 避免内存泄漏
- 注意生命周期
12.13 Binder的死亡通知如何实现?
答案:
Binder死亡通知通过DeathRecipient实现,当Binder对象死亡时收到通知。
实现方式:
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// Binder死亡时的处理
}
};
binder.linkToDeath(deathRecipient, 0);
使用场景:
- 监听Service死亡
- 重新连接Service
- 清理资源
12.14 Binder的权限控制如何实现?
答案:
Binder权限控制通过AndroidManifest.xml中的权限设置实现。
设置方式:
<service
android:name=".MyService"
android:permission="com.example.PERMISSION" />
权限级别:
- normal:自动授予
- dangerous:需要用户授权
- signature:相同签名自动授予
注意事项:
- 设置适当权限
- 保护服务安全
- 注意权限级别
12.15 Binder的最佳实践有哪些?
答案:
Binder最佳实践包括接口设计、性能优化、安全性等。
最佳实践:
-
合理设计接口
- 接口简洁
- 避免复杂参数
-
性能优化
- 减少数据传输
- 避免频繁调用
-
安全性
- 设置适当权限
- 验证数据
总结:
- 合理设计接口
- 优化性能
- 注意安全性
第十三章:ANR 和 Crash(15 题)
13.1 ANR是什么?它的原因是什么?
答案:
ANR(Application Not Responding)是应用无响应,用户无法与应用交互。
ANR的原因:
-
主线程阻塞
- 主线程执行耗时操作
- 超过超时时间
-
超时时间
- Activity:5秒
- BroadcastReceiver:10秒
- Service:20秒
-
常见场景
- 网络请求在主线程
- 文件读写在主线程
- 数据库操作在主线程
解决方案:
- 耗时操作在子线程执行
- 使用异步操作
- 优化主线程性能
13.2 ANR的类型有哪些?
答案:
ANR主要有三种类型,对应不同的超时时间。
ANR类型:
-
Activity ANR
- 超时时间:5秒
- 原因:Activity生命周期方法阻塞
-
BroadcastReceiver ANR
- 超时时间:10秒
- 原因:onReceive()方法阻塞
-
Service ANR
- 超时时间:20秒
- 原因:Service方法阻塞
共同特点:
- 都是主线程阻塞
- 超过超时时间
- 用户无法交互
13.3 ANR的超时时间是多少?
答案:
ANR的超时时间根据组件类型不同而不同。
超时时间:
- Activity:5秒
- BroadcastReceiver:10秒
- Service:20秒
超时机制:
- 系统监控主线程
- 超过超时时间弹出ANR对话框
- 用户可以选择等待或关闭应用
注意事项:
- 超时时间不能修改
- 必须在超时时间内完成操作
- 耗时操作必须在子线程
13.4 如何避免ANR?
答案:
避免ANR需要优化主线程、使用异步操作等。
避免方法:
- 耗时操作在子线程
new Thread(() -> {
// 耗时操作
}).start();
-
使用异步框架
- AsyncTask
- Handler
- 线程池
- Kotlin协程
-
优化主线程
- 减少主线程工作量
- 优化布局
- 减少计算
最佳实践:
- 所有耗时操作在子线程
- 主线程只处理UI更新
- 使用异步框架
13.5 ANR的检测方法有哪些?
答案:
ANR检测可以通过日志分析、工具监控等方式。
检测方法:
-
日志分析
- 查看logcat中的ANR日志
- 分析主线程堆栈
-
工具监控
- Android Studio Profiler
- StrictMode
- ANR WatchDog
-
代码检测
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
注意事项:
- 及时分析ANR日志
- 使用工具监控
- 优化主线程性能
13.6 ANR的日志如何分析?
答案:
ANR日志包含主线程堆栈、系统信息等,需要分析阻塞原因。
日志位置:
/data/anr/traces.txt- logcat中的ANR日志
分析要点:
-
查找主线程
- 找到"main"线程
- 查看堆栈信息
-
分析阻塞点
- 找到耗时操作
- 分析阻塞原因
-
优化建议
- 将耗时操作移到子线程
- 优化代码逻辑
示例:
main" prio=5 tid=1 Blocked
at java.io.FileInputStream.read(FileInputStream.java)
at com.example.MainActivity.onCreate(MainActivity.java:20)
13.7 ANR的监控如何实现?
答案:
ANR监控可以通过StrictMode、自定义监控等方式实现。
监控方式:
- StrictMode
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
- 自定义监控
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 检测主线程是否阻塞
}
}, 5000);
- 第三方库
- ANR WatchDog
- BlockCanary
注意事项:
- 开发环境使用
- 生产环境谨慎使用
- 注意性能影响
13.8 ANR的最佳实践有哪些?
答案:
ANR最佳实践包括优化主线程、使用异步、监控检测等。
最佳实践:
-
优化主线程
- 减少主线程工作量
- 避免耗时操作
-
使用异步
- 耗时操作在子线程
- 使用异步框架
-
监控检测
- 使用工具监控
- 及时分析日志
总结:
- 优化主线程性能
- 使用异步操作
- 监控和检测
13.9 Crash的类型有哪些?
答案:
Crash主要有运行时异常、空指针异常、内存溢出等类型。
常见类型:
-
NullPointerException
- 空指针异常
- 最常见
-
IndexOutOfBoundsException
- 数组越界
- 集合越界
-
OutOfMemoryError
- 内存溢出
- 内存不足
-
ClassCastException
- 类型转换异常
- 类型不匹配
处理方式:
- 捕获异常
- 记录日志
- 上报崩溃信息
13.10 如何捕获Crash?
答案:
可以通过UncaughtExceptionHandler捕获Crash。
实现方式:
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 记录崩溃信息
Log.e("Crash", "Crash occurred", e);
// 上报崩溃信息
reportCrash(e);
// 重启应用或显示错误页面
restartApp();
}
});
注意事项:
- 及时记录崩溃信息
- 上报崩溃日志
- 提供友好的错误提示
13.11 Crash的日志如何分析?
答案:
Crash日志包含异常类型、堆栈信息、系统信息等。
分析要点:
-
异常类型
- 确定异常类型
- 了解异常原因
-
堆栈信息
- 找到崩溃位置
- 分析调用链
-
系统信息
- Android版本
- 设备信息
- 内存信息
分析工具:
- Android Studio
- Crashlytics
- Bugly
13.12 如何避免Crash?
答案:
避免Crash需要防御性编程、异常处理、测试等。
避免方法:
- 防御性编程
if (object != null) {
object.doSomething();
}
- 异常处理
try {
// 可能崩溃的代码
} catch (Exception e) {
// 处理异常
}
- 测试
- 单元测试
- 集成测试
- 压力测试
最佳实践:
- 防御性编程
- 异常处理
- 充分测试
13.13 Crash的监控如何实现?
答案:
Crash监控可以通过第三方SDK、自定义监控等方式实现。
监控方式:
-
第三方SDK
- Firebase Crashlytics
- Bugly
- Sentry
-
自定义监控
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// 上报崩溃信息
reportCrash(e);
}
});
监控内容:
- 崩溃类型
- 堆栈信息
- 设备信息
- 用户信息
13.14 Crash的上报如何实现?
答案:
Crash上报可以通过第三方SDK、自定义上报等方式实现。
上报方式:
- 第三方SDK
FirebaseCrashlytics.getInstance().recordException(e);
- 自定义上报
private void reportCrash(Throwable e) {
// 收集崩溃信息
CrashInfo crashInfo = collectCrashInfo(e);
// 上报到服务器
uploadCrashInfo(crashInfo);
}
上报内容:
- 异常类型和堆栈
- 设备信息
- 应用版本
- 用户信息
13.15 Crash的最佳实践有哪些?
答案:
Crash最佳实践包括防御性编程、异常处理、监控上报等。
最佳实践:
-
防御性编程
- 空值检查
- 边界检查
-
异常处理
- 捕获异常
- 记录日志
-
监控上报
- 使用监控工具
- 及时上报
总结:
- 防御性编程
- 异常处理
- 监控和上报
第十四章:AndroidManifest 和资源管理(12 题)
14.1 AndroidManifest.xml的作用是什么?
答案:
AndroidManifest.xml是应用的配置文件,声明应用的基本信息和组件。
主要作用:
-
声明组件
- Activity、Service、BroadcastReceiver、ContentProvider
-
配置权限
- 声明需要的权限
- 声明自定义权限
-
应用信息
- 包名、版本号
- 应用名称、图标
-
系统配置
- 最低SDK版本
- 目标SDK版本
14.2 AndroidManifest.xml的常用标签有哪些?
答案:
AndroidManifest.xml的常用标签包括manifest、application、activity等。
常用标签:
<manifest>:根标签<application>:应用配置<activity>:Activity声明<service>:Service声明<receiver>:BroadcastReceiver声明<provider>:ContentProvider声明<uses-permission>:权限声明<permission>:自定义权限
14.3 如何配置四大组件?
答案:
四大组件在AndroidManifest.xml中通过对应标签配置。
配置方式:
<!-- Activity -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="standard" />
<!-- Service -->
<service
android:name=".MyService"
android:enabled="true" />
<!-- BroadcastReceiver -->
<receiver
android:name=".MyReceiver"
android:enabled="true" />
<!-- ContentProvider -->
<provider
android:name=".MyProvider"
android:authorities="com.example.provider" />
14.4 如何配置权限?
答案:
权限通过**<uses-permission>**标签声明。
配置方式:
<!-- 声明需要的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 声明自定义权限 -->
<permission
android:name="com.example.PERMISSION"
android:protectionLevel="normal" />
14.5 Android的资源类型有哪些?
答案:
Android资源类型包括布局、字符串、图片、颜色等。
资源类型:
- 布局资源:layout/
- 字符串资源:values/strings.xml
- 图片资源:drawable/
- 颜色资源:values/colors.xml
- 样式资源:values/styles.xml
- 菜单资源:menu/
- 动画资源:anim/
14.6 资源的查找机制是什么?
答案:
资源查找按照配置限定符匹配最合适的资源。
查找流程:
- 根据配置限定符匹配
- 选择最匹配的资源
- 如果找不到,使用默认资源
配置限定符:
- 语言:values-zh/
- 屏幕密度:drawable-hdpi/
- 屏幕方向:layout-land/
14.7 资源的多语言如何实现?
答案:
多语言通过语言限定符实现,创建不同语言的资源目录。
实现方式:
res/
values/strings.xml (默认)
values-zh/strings.xml (中文)
values-en/strings.xml (英文)
使用:
String text = getString(R.string.app_name);
// 系统根据语言自动选择
14.8 资源的多分辨率如何适配?
答案:
多分辨率通过密度限定符适配,创建不同密度的资源。
实现方式:
res/
drawable-mdpi/icon.png
drawable-hdpi/icon.png
drawable-xhdpi/icon.png
drawable-xxhdpi/icon.png
系统自动选择:
- 根据设备密度选择
- 自动缩放适配
14.9 资源的动态加载如何实现?
答案:
资源动态加载可以通过AssetManager或Resources实现。
实现方式:
AssetManager assetManager = getAssets();
InputStream is = assetManager.open("file.txt");
Resources resources = getResources();
Drawable drawable = resources.getDrawable(R.drawable.icon);
14.10 资源的性能优化有哪些?
答案:
资源性能优化包括图片优化、资源压缩、延迟加载等。
优化策略:
-
图片优化
- 使用WebP格式
- 压缩图片大小
-
资源压缩
- 使用ProGuard
- 移除未使用资源
-
延迟加载
- 按需加载资源
- 使用ViewStub
14.11 资源的最佳实践有哪些?
答案:
资源最佳实践包括合理组织、多语言支持、性能优化等。
最佳实践:
-
合理组织
- 按类型组织
- 使用限定符
-
多语言支持
- 支持多语言
- 提供默认资源
-
性能优化
- 优化图片
- 压缩资源
14.12 资源的常见问题有哪些?
答案:
资源常见问题包括找不到资源、资源冲突、内存占用等。
常见问题:
-
找不到资源
- 检查资源名称
- 检查限定符
-
资源冲突
- 检查资源ID
- 检查包名
-
内存占用
- 优化图片大小
- 及时释放资源
第十五章:应用签名和版本适配(10 题)
15.1 应用签名的作用是什么?
答案:
应用签名用于验证应用身份、保证应用完整性。
作用:
-
身份验证
- 标识应用开发者
- 防止应用被篡改
-
完整性保证
- 验证应用未被修改
- 保证应用安全
-
权限控制
- 相同签名可以共享数据
- 系统服务需要签名
15.2 如何生成签名文件?
答案:
签名文件通过keytool或Android Studio生成。
方式1:keytool
keytool -genkey -v -keystore my-release-key.jks
-alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
方式2:Android Studio
- Build → Generate Signed Bundle/APK
- 创建新的密钥库
15.3 签名的类型有哪些?
答案:
签名类型包括调试签名和发布签名。
类型:
-
调试签名
- 自动生成
- 用于开发测试
-
发布签名
- 手动生成
- 用于发布应用
15.4 签名的验证机制是什么?
答案:
签名验证在应用安装时进行,系统验证签名有效性。
验证流程:
- 检查签名文件
- 验证签名有效性
- 检查签名是否匹配
- 安装或拒绝
15.5 Android版本适配的挑战是什么?
答案:
版本适配挑战包括API变化、行为变化、权限变化等。
挑战:
-
API变化
- 新API需要检查版本
- 废弃API需要替换
-
行为变化
- 系统行为变化
- 需要适配处理
-
权限变化
- 权限模型变化
- 需要动态申请
15.6 如何适配不同Android版本?
答案:
版本适配通过版本检查、兼容库、条件编译等方式。
适配方式:
- 版本检查
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 使用新API
} else {
// 使用旧API
}
-
兼容库
- AndroidX库
- Support库
-
条件编译
- 使用@TargetApi注解
- 使用@RequiresApi注解
15.7 版本适配的最佳实践有哪些?
答案:
版本适配最佳实践包括使用兼容库、充分测试、渐进式适配等。
最佳实践:
-
使用兼容库
- AndroidX
- Support库
-
充分测试
- 在不同版本测试
- 覆盖主要版本
-
渐进式适配
- 逐步适配新版本
- 保持向后兼容
15.8 如何测试版本兼容性?
答案:
版本兼容性测试可以通过多设备测试、模拟器测试等方式。
测试方式:
-
多设备测试
- 使用不同版本设备
- 覆盖主要版本
-
模拟器测试
- 创建不同版本模拟器
- 自动化测试
-
云测试平台
- Firebase Test Lab
- 其他云测试平台
15.9 应用安装和卸载的流程是什么?
答案:
应用安装和卸载由PackageManager管理。
安装流程:
- 验证签名
- 检查权限
- 安装应用
- 注册组件
卸载流程:
- 停止应用
- 删除文件
- 注销组件
15.10 应用更新的最佳实践有哪些?
答案:
应用更新最佳实践包括版本管理、数据迁移、用户体验等。
最佳实践:
-
版本管理
- 合理版本号
- 版本说明
-
数据迁移
- 处理数据升级
- 保持数据兼容
-
用户体验
- 平滑更新
- 减少影响
第十六章:Handler 消息机制(20 题)
16.1 Handler是什么?它的作用是什么?
答案:
Handler是Android的消息处理机制,用于线程间通信和UI更新。
作用:
-
线程间通信
- 子线程向主线程发送消息
- 主线程处理消息
-
UI更新
- 在子线程更新UI
- 通过Handler切换到主线程
-
延迟执行
- 延迟执行任务
- 定时任务
16.2 Handler、Looper、MessageQueue的关系是什么?
答案:
Handler、Looper、MessageQueue是消息机制的核心组件。
关系:
- Looper:消息循环,从MessageQueue取消息
- MessageQueue:消息队列,存储消息
- Handler:消息处理器,发送和处理消息
工作流程:
Handler发送消息 → MessageQueue → Looper循环取消息 → Handler处理消息
16.3 Handler的消息机制是什么?
答案:
Handler消息机制通过消息队列和消息循环实现。
机制:
- Handler发送消息到MessageQueue
- Looper从MessageQueue取消息
- Handler处理消息
特点:
- 异步非阻塞
- 线程安全
- 支持延迟
16.4 Handler为什么可以在子线程更新UI?
答案:
Handler通过消息机制将UI更新切换到主线程执行。
原理:
- Handler绑定主线程的Looper
- 消息在主线程的MessageQueue中
- Looper在主线程处理消息
- 因此可以在主线程更新UI
16.5 Looper的作用是什么?
答案:
Looper用于消息循环,从MessageQueue取消息并分发。
作用:
- 循环从MessageQueue取消息
- 将消息分发给Handler
- 管理消息循环
特点:
- 每个线程只有一个Looper
- 主线程默认有Looper
- 子线程需要手动创建
16.6 MessageQueue的作用是什么?
答案:
MessageQueue是消息队列,存储待处理的消息。
作用:
- 存储消息
- 按时间排序
- 提供消息给Looper
特点:
- 单链表结构
- 按时间排序
- 线程安全
16.7 Looper的创建方式有哪些?
答案:
Looper创建方式:主线程自动创建,子线程手动创建。
创建方式:
// 子线程创建Looper
Looper.prepare();
Looper.loop();
// 或使用HandlerThread
HandlerThread handlerThread = new HandlerThread("MyThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
16.8 Looper.loop()的死循环为什么不会ANR?
答案:
Looper.loop()的死循环不会ANR因为有消息时处理,无消息时阻塞在native层。
原理:
-
有消息时处理
- 处理消息队列中的消息
- 执行Handler的handleMessage()
- 处理用户交互和系统事件
-
无消息时阻塞
- 阻塞在nativePollOnce()
- 进入native层等待
- 不占用CPU资源
- 等待新消息唤醒
-
ANR检测机制
- ANR检测的是主线程是否响应输入事件
- Looper处理消息就是响应事件
- 只要及时处理消息就不会ANR
ANR的真正原因:
- 主线程执行耗时操作(网络、文件IO、复杂计算等)
- 导致消息处理延迟
- 不是死循环本身导致ANR
关键点:
- Looper.loop()本身不会ANR
- 但如果处理消息时执行耗时操作,就会ANR
- 需要在子线程执行耗时操作
16.9 Message的作用是什么?
答案:
Message是消息对象,包含消息内容和处理信息。
作用:
- 携带消息数据
- 指定处理Handler
- 支持延迟
字段:
- what:消息类型
- obj:消息数据
- target:处理Handler
16.10 Message的创建方式有哪些?
答案:
Message创建方式:obtain()(推荐)和new Message()。
创建方式:
// 推荐:从消息池获取
Message msg = Message.obtain();
msg.what = 1;
msg.obj = "data";
// 不推荐:直接创建
Message msg = new Message();
推荐obtain()的原因:
- 从消息池获取,性能好
- 减少对象创建
- 自动回收
16.11 Message的回收机制是什么?
答案:
Message使用消息池机制,处理完后自动回收。
机制:
- Message处理完后回收
- 放入消息池
- 下次obtain()时复用
优势:
- 减少对象创建
- 提升性能
- 自动管理
16.12 Message的obtain()和new Message()的区别是什么?
答案:
obtain()从消息池获取,new Message()直接创建。
区别:
- obtain():从消息池获取,性能好,推荐使用
- new Message():直接创建,性能较差
推荐:
- 使用obtain()获取Message
- 避免直接new Message()
16.13 Handler的post()和sendMessage()的区别是什么?
答案:
post()和sendMessage()都是发送消息,但使用方式不同。
区别:
- post():发送Runnable,简单直接
- sendMessage():发送Message,更灵活
使用:
// post()
handler.post(new Runnable() {
@Override
public void run() {
// 执行任务
}
});
// sendMessage()
Message msg = Message.obtain();
msg.what = 1;
handler.sendMessage(msg);
16.14 Handler的postDelayed()如何实现延迟?
答案:
postDelayed()通过MessageQueue的时间排序实现延迟。
实现:
- 消息按时间排序
- 延迟消息在指定时间后处理
- Looper按时间顺序取消息
使用:
handler.postDelayed(new Runnable() {
@Override
public void run() {
// 延迟执行
}
}, 1000); // 延迟1秒
16.15 Handler如何移除消息?
答案:
Handler通过**removeCallbacks()和removeMessages()**移除消息。
移除方式:
// 移除Runnable
handler.removeCallbacks(runnable);
// 移除Message
handler.removeMessages(what);
// 移除所有消息
handler.removeCallbacksAndMessages(null);
16.16 Handler的内存泄漏如何避免?
答案:
Handler内存泄漏通过静态内部类和WeakReference避免。
避免方法:
// 静态内部类 + WeakReference
private static class MyHandler extends Handler {
private WeakReference<Activity> activityRef;
MyHandler(Activity activity) {
activityRef = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = activityRef.get();
if (activity != null) {
// 处理消息
}
}
}
16.17 Handler的最佳实践有哪些?
答案:
Handler最佳实践包括避免泄漏、合理使用、性能优化等。
最佳实践:
-
避免泄漏
- 使用静态内部类
- 使用WeakReference
-
合理使用
- 及时移除消息
- 避免消息堆积
-
性能优化
- 使用obtain()获取Message
- 避免频繁创建Handler
16.18 Handler的线程安全问题有哪些?
答案:
Handler的线程安全问题主要包括消息队列的线程安全和Handler使用的线程安全。
线程安全问题:
-
MessageQueue线程安全
- MessageQueue使用锁保护
- 消息入队和出队是线程安全的
- 多个线程可以安全地向同一个MessageQueue发送消息
-
Handler使用线程安全
- Handler可以在任何线程创建
- 但必须绑定到有Looper的线程
- 消息处理在Looper所在线程执行
-
潜在问题
- 如果Handler绑定到主线程,所有消息在主线程处理
- 如果Handler绑定到子线程,消息在子线程处理
- 需要注意线程切换
安全机制:
- MessageQueue内部使用锁
- 保证消息操作的原子性
- Handler本身是线程安全的
16.19 HandlerThread的作用是什么?
答案:
HandlerThread是带Looper的Thread,用于创建有消息循环的工作线程。
作用:
- 创建带Looper的线程
- 简化工作线程的消息处理
- 自动管理Looper生命周期
使用方式:
// 创建HandlerThread
HandlerThread handlerThread = new HandlerThread("MyThread");
handlerThread.start();
// 获取Looper
Looper looper = handlerThread.getLooper();
// 创建Handler
Handler handler = new Handler(looper);
// 使用Handler
handler.post(new Runnable() {
@Override
public void run() {
// 在工作线程执行
}
});
// 退出
handlerThread.quit();
特点:
- 自动创建Looper
- 自动管理Looper生命周期
- 简化工作线程使用
使用场景:
- 需要工作线程处理消息
- 需要线程间通信
- 后台任务处理
16.20 Handler的替代方案有哪些?
答案:
Handler的替代方案包括Kotlin协程、RxJava、Executor等。
替代方案:
- Kotlin协程
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
// 后台任务
}
// 主线程更新UI
}
- RxJava
Observable.fromCallable(() -> {
// 后台任务
return result;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
// 更新UI
});
- Executor + Handler
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {
// 后台任务
runOnUiThread(() -> {
// 更新UI
});
});
选择建议:
- Kotlin项目:使用协程
- Java项目:使用Handler或Executor
- 响应式编程:使用RxJava
第十七章:AsyncTask 和 Loader(15 题)
17.1 AsyncTask是什么?它的作用是什么?
答案:
AsyncTask是异步任务类,用于在后台线程执行任务并在主线程更新UI(已废弃)。
作用:
- 在后台线程执行任务
- 在主线程更新UI
- 简化异步操作
注意:
- Android 11+已废弃
- 推荐使用线程池或Kotlin协程
17.2 AsyncTask的生命周期方法有哪些?
答案:
AsyncTask有4个生命周期方法,按顺序调用。
生命周期方法:
-
onPreExecute()
- 在主线程执行
- 任务执行前调用
- 用于初始化UI
-
doInBackground(Params... params)
- 在后台线程执行
- 执行耗时任务
- 可以调用publishProgress()更新进度
-
onProgressUpdate(Progress... values)
- 在主线程执行
- 更新进度时调用
- 更新UI进度
-
onPostExecute(Result result)
- 在主线程执行
- 任务完成后调用
- 处理结果并更新UI
执行流程:
onPreExecute() → doInBackground() → onPostExecute()
↓
onProgressUpdate() (可选)
17.3 AsyncTask的执行流程是什么?
答案:
AsyncTask执行流程包括创建、执行、更新、完成等步骤。
执行流程:
- 创建AsyncTask实例
MyAsyncTask task = new MyAsyncTask();
- 调用execute()
task.execute(params);
-
系统调用生命周期方法
- onPreExecute():主线程,执行前
- doInBackground():后台线程,执行任务
- onProgressUpdate():主线程,更新进度(可选)
- onPostExecute():主线程,执行完成
-
任务完成
- 结果传递给onPostExecute()
- 更新UI
注意事项:
- execute()必须在主线程调用
- 只能执行一次
- 已废弃,推荐使用替代方案
17.4 AsyncTask的线程池如何配置?
答案:
AsyncTask使用内置线程池,可以通过反射修改(不推荐)。
默认线程池:
- AsyncTask使用THREAD_POOL_EXECUTOR
- 核心线程数:CPU核心数 + 1
- 最大线程数:CPU核心数 * 2 + 1
修改方式(不推荐):
// 通过反射修改线程池(不推荐)
Field field = AsyncTask.class.getDeclaredField("THREAD_POOL_EXECUTOR");
field.setAccessible(true);
ThreadPoolExecutor executor = (ThreadPoolExecutor) field.get(null);
executor.setCorePoolSize(5);
注意事项:
- 不推荐修改,可能影响系统
- AsyncTask已废弃
- 推荐使用自定义线程池
17.5 AsyncTask的内存泄漏如何避免?
答案:
AsyncTask内存泄漏通过静态内部类和及时取消避免。
避免方法:
// 静态内部类
private static class MyTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Activity> activityRef;
}
// 及时取消
@Override
protected void onDestroy() {
super.onDestroy();
asyncTask.cancel(true);
}
17.6 AsyncTask的取消如何实现?
答案:
AsyncTask取消通过**cancel()**方法实现。
取消方式:
AsyncTask task = new MyAsyncTask();
task.execute();
// 取消任务
task.cancel(true); // true表示中断正在执行的任务
取消检查:
@Override
protected Result doInBackground(Params... params) {
for (int i = 0; i < 100; i++) {
if (isCancelled()) {
// 任务已取消,停止执行
return null;
}
// 执行任务
}
return result;
}
取消后的处理:
@Override
protected void onCancelled() {
// 任务取消后的处理
super.onCancelled();
}
@Override
protected void onCancelled(Result result) {
// 任务取消后的处理(带结果)
super.onCancelled(result);
}
注意事项:
- cancel()只是标记取消
- 需要在doInBackground()中检查isCancelled()
- 取消后不会调用onPostExecute()
17.7 AsyncTask的最佳实践有哪些?
答案:
AsyncTask最佳实践包括避免泄漏、及时取消、合理使用等。
最佳实践:
-
避免内存泄漏
- 使用静态内部类
- 使用WeakReference
-
及时取消
- 在onDestroy()中取消
- 检查isCancelled()
-
合理使用
- 避免长时间任务
- 注意生命周期
注意:
- AsyncTask已废弃
- 推荐使用替代方案
17.8 AsyncTask为什么被废弃?替代方案是什么?
答案:
AsyncTask被废弃因为设计问题和更好的替代方案。
废弃原因:
-
设计问题
- 容易造成内存泄漏
- 线程池配置不灵活
- 生命周期管理复杂
-
更好的替代方案
- 线程池更灵活
- Kotlin协程更现代
- ViewModel + LiveData更符合架构
替代方案:
- 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {
// 后台任务
runOnUiThread(() -> {
// 更新UI
});
});
- Kotlin协程
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
// 后台任务
}
// 更新UI
}
- ViewModel + LiveData
- ViewModel管理数据
- LiveData观察变化
- 自动处理生命周期
17.9 Loader是什么?它的作用是什么?
答案:
Loader是数据加载器,用于异步加载数据(已废弃)。
作用:
- 异步加载数据
- 管理数据生命周期
- 自动处理配置变更
注意:
- 已废弃
- 推荐使用ViewModel + LiveData
17.10 LoaderManager的作用是什么?
答案:
LoaderManager用于管理Loader的生命周期。
作用:
-
创建Loader
- 通过initLoader()创建
- 管理Loader实例
-
重启Loader
- 通过restartLoader()重启
- 重新加载数据
-
销毁Loader
- 自动管理生命周期
- 配置变更时保持
使用方式:
LoaderManager loaderManager = getSupportLoaderManager();
loaderManager.initLoader(0, null, new LoaderManager.LoaderCallbacks<Cursor>() {
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(context, uri, projection, selection,
selectionArgs, sortOrder);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// 数据加载完成
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Loader重置
}
});
注意事项:
- Loader已废弃
- 推荐使用ViewModel + LiveData
17.11 Loader的生命周期是什么?
答案:
Loader生命周期包括创建、启动、停止、重置等。
生命周期:
-
onCreateLoader()
- 创建Loader实例
- 初始化Loader
-
onLoadFinished()
- 数据加载完成
- 更新UI
-
onLoaderReset()
- Loader重置
- 清理数据
生命周期管理:
- LoaderManager自动管理
- 配置变更时保持
- Activity销毁时清理
17.12 Loader的使用场景有哪些?
答案:
Loader主要用于数据加载场景。
使用场景:
-
数据库查询
- 加载Cursor数据
- 自动更新
-
网络数据加载
- 加载网络数据
- 缓存管理
-
配置变更保持
- 横竖屏切换保持
- 自动重新加载
注意事项:
- Loader已废弃
- 推荐使用ViewModel + LiveData
17.13 Loader的最佳实践有哪些?
答案:
Loader最佳实践包括合理使用、生命周期管理等。
最佳实践:
-
合理使用
- 适合数据加载场景
- 利用自动更新
-
生命周期管理
- 让LoaderManager管理
- 不要手动管理
-
处理配置变更
- 利用自动保持
- 避免重复加载
注意:
- Loader已废弃
- 推荐使用ViewModel + LiveData
17.14 Loader和AsyncTask的区别是什么?
答案:
Loader和AsyncTask都可以异步加载,但设计目的不同。
主要区别:
| 特性 | Loader | AsyncTask |
|---|---|---|
| 设计目的 | 数据加载 | 通用异步任务 |
| 生命周期 | 自动管理 | 手动管理 |
| 配置变更 | 自动保持 | 需要手动处理 |
| 数据更新 | 自动更新 | 手动更新 |
| 使用场景 | 数据加载 | 通用任务 |
Loader特点:
- 专门用于数据加载
- 自动管理生命周期
- 配置变更时自动保持
- 数据变化时自动更新
AsyncTask特点:
- 通用异步任务
- 需要手动管理生命周期
- 配置变更需要手动处理
注意:
- 两者都已废弃
- 推荐使用现代方案
17.15 Loader的替代方案是什么?
答案:
Loader的替代方案包括ViewModel + LiveData、Room + LiveData等。
替代方案:
- ViewModel + LiveData
public class MyViewModel extends ViewModel {
private MutableLiveData<List<Data>> data = new MutableLiveData<>();
public void loadData() {
// 在后台线程加载数据
new Thread(() -> {
List<Data> result = loadDataFromSource();
data.postValue(result);
}).start();
}
public LiveData<List<Data>> getData() {
return data;
}
}
- Room + LiveData
@Dao
public interface DataDao {
@Query("SELECT * FROM data")
LiveData<List<Data>> getAllData();
}
- Kotlin协程 + Flow
fun loadData(): Flow<List<Data>> = flow {
emit(loadDataFromSource())
}.flowOn(Dispatchers.IO)
优势:
- 更好的生命周期管理
- 更符合现代架构
- 更灵活的使用方式
答案:
AsyncTask和Loader的替代方案包括线程池、Kotlin协程、ViewModel + LiveData等。
替代方案:
- 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(runnable);
- Kotlin协程
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
// 后台任务
}
// 更新UI
}
- ViewModel + LiveData
- ViewModel管理数据
- LiveData观察数据变化