Android 基础---第二部

3 阅读1小时+

第六章:ContentProvider(18 题)

6.1 ContentProvider是什么?它的作用是什么?

答案:

ContentProvider是Android的数据共享组件,用于在不同应用间共享数据。

ContentProvider的定义:

  • 提供统一的数据访问接口
  • 封装数据访问细节
  • 支持跨应用数据共享

主要作用:

  1. 数据共享

    • 应用间数据共享
    • 统一的数据访问接口
  2. 数据封装

    • 封装数据存储细节
    • 提供标准CRUD接口
  3. 数据安全

    • 通过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操作
  • 管理数据访问

主要方法:

  1. query():查询数据
  2. insert():插入数据
  3. update():更新数据
  4. 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操作。

实现方式:

  1. 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;
}
  1. 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;
}
  1. 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;
}
  1. 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,确定操作的数据类型。

匹配规则:

  1. 定义匹配码
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);
}
  1. 匹配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/user
  • user/#:匹配content://authority/user/1(#表示数字)
  • user/*:匹配content://authority/user/任意字符串(*表示任意字符串)

6.6 UriMatcher的作用是什么?

答案:

UriMatcher用于匹配URI,确定ContentProvider操作的数据类型。

作用:

  • 匹配URI模式
  • 确定操作类型
  • 提取URI参数

使用方式:

  1. 创建UriMatcher
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  1. 添加URI模式
static {
    uriMatcher.addURI("com.example.provider", "user", USER);
    uriMatcher.addURI("com.example.provider", "user/#", USER_ID);
}
  1. 匹配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并实现必要方法。

实现步骤:

  1. 创建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类型
    }
}
  1. 在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()方法实现数据修改操作。

实现方式:

  1. 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);
    }
}
  1. 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);
    }
}
  1. 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设计性能优化安全性等。

最佳实践:

  1. URI设计

    • 使用清晰的URI结构
    • 遵循URI命名规范
    • 使用UriMatcher匹配
  2. 性能优化

    • 使用索引优化查询
    • 避免全表扫描
    • 使用批量操作
  3. 数据通知

    • 操作后通知数据变化
    • 使用notifyChange()通知
  4. 线程安全

    • 使用同步机制
    • 避免并发问题
  5. 权限控制

    • 设置适当的权限
    • 保护敏感数据

总结:

  • 合理设计URI
  • 优化性能
  • 及时通知变化
  • 注意线程安全

6.12 如何实现跨应用数据共享?

答案:

跨应用数据共享通过ContentProvider实现,需要设置权限。

实现步骤:

  1. 创建ContentProvider
public class MyContentProvider extends ContentProvider {
    // 实现CRUD方法
}
  1. 在AndroidManifest.xml中注册并设置权限
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:permission="com.example.PERMISSION" />
  1. 定义权限
<permission
    android:name="com.example.PERMISSION"
    android:protectionLevel="normal" />
  1. 其他应用访问
<!-- 声明使用权限 -->
<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中的权限设置控制访问。

设置方式:

  1. 设置读取权限
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:readPermission="com.example.READ_PERMISSION" />
  1. 设置写入权限
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:writePermission="com.example.WRITE_PERMISSION" />
  1. 设置路径权限
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider">
    <path-permission
        android:path="/user"
        android:permission="com.example.USER_PERMISSION" />
</provider>
  1. 定义权限
<permission
    android:name="com.example.READ_PERMISSION"
    android:protectionLevel="normal" />

权限级别:

  • normal:自动授予
  • dangerous:需要用户授权
  • signature:相同签名自动授予

6.14 ContentProvider的性能优化有哪些?

答案:

ContentProvider性能优化可以从查询优化批量操作索引等方面优化。

优化策略:

  1. 查询优化

    • 使用索引
    • 避免全表扫描
    • 限制返回数据量
  2. 批量操作

    • 使用bulkInsert()批量插入
    • 减少数据库操作次数
  3. 异步操作

    • 耗时操作使用异步
    • 避免阻塞主线程
  4. 缓存机制

    • 缓存常用数据
    • 减少数据库查询
  5. 数据库优化

    • 合理设计表结构
    • 使用索引
    • 定期清理数据

最佳实践:

  • 使用索引优化查询
  • 使用批量操作
  • 避免全表扫描
  • 合理设计数据库

6.15 ContentProvider的线程安全问题如何解决?

答案:

ContentProvider的线程安全需要使用同步机制保护共享资源。

解决方案:

  1. 使用synchronized
private static final Object lock = new Object();

@Override
public Cursor query(Uri uri, String[] projection, String selection,
                   String[] selectionArgs, String sortOrder) {
    synchronized (lock) {
        // 查询操作
    }
}
  1. 使用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();
    }
}
  1. 使用单例数据库
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()方法。

使用步骤:

  1. 创建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
    }
};
  1. 注册观察者
Uri uri = Uri.parse("content://com.example.provider/user");
getContentResolver().registerContentObserver(uri, true, observer);
  1. 注销观察者
@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
  • 支持显式和隐式调用

主要作用:

  1. 启动组件

    • 启动Activity
    • 启动Service
    • 发送广播
  2. 传递数据

    • 组件间数据传递
    • 携带参数和结果
  3. 隐式调用

    • 通过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
指定方式ComponentAction/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:

  1. FLAG_ACTIVITY_NEW_TASK

    • 在新任务栈中启动Activity
    • 类似singleTask
  2. FLAG_ACTIVITY_SINGLE_TOP

    • 如果Activity在栈顶,复用实例
    • 类似singleTop
  3. FLAG_ACTIVITY_CLEAR_TOP

    • 清除栈顶Activity
    • 类似singleTask
  4. FLAG_ACTIVITY_CLEAR_TASK

    • 清除整个任务栈
    • 启动新任务栈
  5. 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
  • 包含包名和类名

使用方式:

  1. 通过构造函数指定
Intent intent = new Intent(this, SecondActivity.class);
  1. 通过setComponent()指定
ComponentName component = new ComponentName("com.example", 
                                            "com.example.SecondActivity");
Intent intent = new Intent();
intent.setComponent(component);
  1. 通过setClassName()指定
Intent intent = new Intent();
intent.setClassName("com.example", "com.example.SecondActivity");

特点:

  • 直接指定目标,不经过匹配
  • 更安全,避免误匹配
  • 适合应用内调用

7.6 Intent的Extras如何传递数据?

答案:

Extras用于传递额外数据,通过Bundle存储键值对。

传递方式:

  1. putExtra()方法
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("name", "张三");
intent.putExtra("age", 25);
intent.putExtra("isStudent", true);
startActivity(intent);
  1. putExtras()方法(传递Bundle)
Bundle bundle = new Bundle();
bundle.putString("name", "张三");
bundle.putInt("age", 25);
intent.putExtras(bundle);
  1. 接收数据
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用于应用内组件调用,明确指定目标组件。

使用场景:

  1. 应用内Activity跳转
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
  1. 启动Service
Intent intent = new Intent(this, MyService.class);
startService(intent);
  1. 发送广播给指定接收者
Intent intent = new Intent(this, MyReceiver.class);
sendBroadcast(intent);

优势:

  • 明确指定目标,安全
  • 不经过系统匹配,效率高
  • 适合应用内调用

注意事项:

  • 必须知道目标组件的完整类名
  • 只能调用同一应用的组件(除非跨应用)

7.8 隐式Intent的使用场景是什么?

答案:

隐式Intent用于跨应用调用系统功能调用,通过匹配选择组件。

使用场景:

  1. 调用系统功能
// 打开网页
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);
  1. 跨应用调用
// 调用其他应用的功能
Intent intent = new Intent("com.example.ACTION");
startActivity(intent);
  1. 选择器(多个应用匹配时)
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。

匹配规则:

  1. Action匹配

    • Intent的Action必须在IntentFilter中声明
    • 至少匹配一个Action
  2. Category匹配

    • Intent的所有Category都必须在IntentFilter中声明
    • IntentFilter可以声明额外的Category
  3. 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时,系统会显示选择器让用户选择。

处理方式:

  1. 自动显示选择器
Intent intent = new Intent(Intent.ACTION_SEND);
// 如果多个应用匹配,系统自动显示选择器
startActivity(intent);
  1. 手动创建选择器
Intent intent = new Intent(Intent.ACTION_SEND);
Intent chooser = Intent.createChooser(intent, "选择应用");
startActivity(chooser);
  1. 设置默认应用
    • 用户可以选择"始终使用此应用"
    • 系统记住用户选择

避免选择器:

  • 使用显式Intent
  • 设置更具体的IntentFilter
  • 使用包名限制

注意事项:

  • 选择器提升用户体验
  • 允许用户选择应用
  • 可以设置默认应用

7.12 Intent传递数据的方式有哪些?

答案:

Intent传递数据的方式包括ExtrasDataClipData等。

传递方式:

  1. Extras(键值对)
intent.putExtra("key", "value");
  1. Data(URI)
intent.setData(Uri.parse("content://provider/data"));
  1. ClipData(大文本或URI列表)
ClipData clipData = ClipData.newPlainText("label", "text");
intent.setClipData(clipData);
  1. Bundle(批量传递)
Bundle bundle = new Bundle();
bundle.putString("key", "value");
intent.putExtras(bundle);

选择建议:

  • 简单数据:使用Extras
  • URI数据:使用Data
  • 大文本:使用ClipData
  • 批量数据:使用Bundle

7.13 Intent传递大数据如何优化?

答案:

Intent传递大数据需要优化策略,避免性能问题和限制。

优化策略:

  1. 使用URI传递
// 不传递数据本身,传递URI
intent.setData(Uri.parse("content://provider/data/1"));
  1. 使用全局变量
// 使用Application或单例存储数据
DataManager.getInstance().setData(data);
intent.putExtra("id", id);
  1. 使用文件传递
// 写入文件,传递文件路径
File file = writeToFile(data);
intent.putExtra("filePath", file.getAbsolutePath());
  1. 使用数据库
// 存入数据库,传递ID
long id = saveToDatabase(data);
intent.putExtra("id", id);

注意事项:

  • Intent数据大小限制约1MB
  • 大数据使用URI或全局变量
  • 避免传递过大数据

7.14 Intent传递对象如何实现?

答案:

Intent传递对象需要对象实现ParcelableSerializable接口。

方式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

解决方案:

  1. 使用URI传递
intent.setData(Uri.parse("content://provider/data/1"));
  1. 使用全局变量
DataManager.getInstance().setData(data);
intent.putExtra("id", id);
  1. 使用文件
File file = writeToFile(data);
intent.putExtra("filePath", file.getAbsolutePath());

注意事项:

  • 避免传递过大数据
  • 使用替代方案
  • 注意异常处理

7.16 Bundle的作用是什么?

答案:

Bundle是数据容器,用于存储键值对数据,常用于Intent传递数据。

作用:

  • 存储键值对数据
  • Intent传递数据的容器
  • Activity状态保存和恢复

使用方式:

  1. 创建和添加数据
Bundle bundle = new Bundle();
bundle.putString("name", "张三");
bundle.putInt("age", 25);
  1. 通过Intent传递
intent.putExtras(bundle);
  1. 获取数据
Bundle bundle = getIntent().getExtras();
String name = bundle.getString("name");
int age = bundle.getInt("age");
  1. 状态保存
@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
);

使用场景:

  1. 通知点击
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentIntent(pendingIntent)
    .build();
  1. 定时任务
AlarmManager alarmManager = getSystemService(AlarmManager.class);
alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
  1. 桌面快捷方式
Intent shortcutIntent = new Intent(this, MainActivity.class);
PendingIntent shortcutPendingIntent = PendingIntent.getActivity(
    this, 0, shortcutIntent, 0
);

7.18 PendingIntent的使用场景是什么?

答案:

PendingIntent用于延迟执行Intent的场景。

使用场景:

  1. 通知(Notification)
PendingIntent pendingIntent = PendingIntent.getActivity(
    this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setContentIntent(pendingIntent)
    .build();
  1. 定时任务(AlarmManager)
AlarmManager alarmManager = getSystemService(AlarmManager.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
    this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingIntent);
  1. 桌面快捷方式
Intent shortcutIntent = new Intent(this, MainActivity.class);
PendingIntent shortcutPendingIntent = PendingIntent.getActivity(
    this, 0, shortcutIntent, 0
);
  1. 应用小部件(App Widget)
RemoteViews views = new RemoteViews(getPackageName(), R.layout.widget);
views.setOnClickPendingIntent(R.id.button, pendingIntent);

特点:

  • 允许其他应用执行
  • 延迟执行
  • 保持执行权限

7.19 PendingIntent的Flags有哪些?

答案:

PendingIntent的Flags用于控制PendingIntent的行为

常用Flags:

  1. FLAG_ONE_SHOT

    • PendingIntent只能使用一次
    • 使用后自动取消
  2. FLAG_NO_CREATE

    • 如果PendingIntent不存在,返回null
    • 不创建新的PendingIntent
  3. FLAG_CANCEL_CURRENT

    • 取消现有的PendingIntent
    • 创建新的PendingIntent
  4. FLAG_UPDATE_CURRENT

    • 如果PendingIntent存在,更新其Intent
    • 如果不存在,创建新的
  5. 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安全问题主要包括未授权访问数据泄露

安全问题:

  1. 未授权访问

    • 其他应用可能启动组件
    • 需要设置权限
  2. 数据泄露

    • Intent数据可能被拦截
    • 需要加密敏感数据

解决方案:

  1. 使用显式Intent
// 应用内调用使用显式Intent
Intent intent = new Intent(this, SecondActivity.class);
  1. 设置权限
<activity
    android:name=".MyActivity"
    android:permission="com.example.PERMISSION" />
  1. 验证数据
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Intent intent = getIntent();
    // 验证数据来源
    if (!isValidIntent(intent)) {
        finish();
        return;
    }
}
  1. 加密敏感数据
String encryptedData = encrypt(data);
intent.putExtra("data", encryptedData);

推荐:

  • 应用内使用显式Intent
  • 设置适当权限
  • 验证数据来源
  • 加密敏感数据

7.21 Intent的最佳实践有哪些?

答案:

Intent最佳实践包括使用方式数据传递安全性等。

最佳实践:

  1. 选择合适的Intent类型

    • 应用内调用:使用显式Intent
    • 跨应用调用:使用隐式Intent
  2. 合理传递数据

    • 避免传递过大数据
    • 使用URI或全局变量传递大数据
    • 对象实现Parcelable
  3. 注意安全性

    • 设置适当权限
    • 验证数据来源
    • 加密敏感数据
  4. 处理异常情况

    • 处理匹配不到组件的情况
    • 处理多个匹配的情况
  5. 性能优化

    • 避免传递过大数据
    • 使用合适的Flags

总结:

  • 选择合适的Intent类型
  • 合理传递数据
  • 注意安全性
  • 处理异常情况

7.22 Intent的测试如何进行?

答案:

Intent测试可以使用单元测试集成测试

测试方式:

  1. 单元测试
@Test
public void testIntentCreation() {
    Intent intent = new Intent(context, SecondActivity.class);
    intent.putExtra("key", "value");
    assertEquals("value", intent.getStringExtra("key"));
}
  1. 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"));
        });
    }
}
  1. 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的定义:

  • 应用启动时创建
  • 应用生命周期内唯一实例
  • 全局可访问

主要作用:

  1. 全局初始化

    • 应用启动时初始化
    • 初始化全局资源
  2. 全局数据存储

    • 存储全局变量
    • 共享数据
  3. 生命周期管理

    • 监听应用生命周期
    • 管理全局资源

使用方式:

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用于全局初始化和数据管理

使用场景:

  1. 全局初始化
@Override
public void onCreate() {
    super.onCreate();
    // 初始化第三方SDK
    // 初始化全局配置
}
  1. 全局数据存储
private String globalData;

public String getGlobalData() {
    return globalData;
}

public void setGlobalData(String data) {
    this.globalData = data;
}
  1. 生命周期监听
@Override
public void onLowMemory() {
    super.onLowMemory();
    // 释放资源
}
  1. 获取Application实例
MyApplication app = (MyApplication) getApplication();

注意事项:

  • 避免存储过多数据
  • 注意内存管理
  • 避免内存泄漏

8.5 Context是什么?它的作用是什么?

答案:

Context是Android的上下文对象,提供应用环境和系统服务访问。

Context的定义:

  • 应用的上下文环境
  • 提供系统服务访问
  • 提供资源访问

主要作用:

  1. 访问系统服务
LocationManager locationManager = (LocationManager) 
    getSystemService(Context.LOCATION_SERVICE);
  1. 访问资源
String string = getString(R.string.app_name);
Drawable drawable = getDrawable(R.drawable.icon);
  1. 启动组件
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
  1. 文件操作
File file = new File(getFilesDir(), "file.txt");

特点:

  • 所有组件都有Context
  • 提供统一的环境接口
  • 系统服务访问入口

8.6 Context的类型有哪些?

答案:

Context主要有Activity ContextApplication Context两种类型。

Context类型:

  1. Activity Context

    • Activity的Context
    • 生命周期与Activity绑定
    • 可以启动Activity、显示Dialog等
  2. Application Context

    • Application的Context
    • 生命周期与应用绑定
    • 不能启动Activity、显示Dialog等
  3. Service Context

    • Service的Context
    • 生命周期与Service绑定
  4. 其他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 ContextApplication 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的场景:

  1. UI相关操作

    • 启动Activity
    • 显示Dialog
    • 绑定Layout
  2. 生命周期相关

    • 需要Activity生命周期
    • 需要Activity的配置

使用Application Context的场景:

  1. 非UI操作

    • 启动Service
    • 发送广播
    • 访问系统服务
  2. 长时间持有

    • 静态变量
    • 单例对象
    • 异步任务

示例:

// 使用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内存泄漏的常见原因和避免方法。

常见泄漏场景:

  1. 静态引用Activity Context
// 错误
static Context context;

// 正确:使用Application Context
static Context context = getApplicationContext();
  1. 单例持有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();
    }
}
  1. 异步任务持有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最佳实践包括选择合适类型避免内存泄漏合理使用等。

最佳实践:

  1. 选择合适的Context类型

    • UI操作使用Activity Context
    • 非UI操作使用Application Context
    • 长时间持有使用Application Context
  2. 避免内存泄漏

    • 避免静态引用Activity Context
    • 单例使用Application Context
    • 及时清理引用
  3. 合理使用

    • 不要滥用Context
    • 避免传递Context过多
    • 使用依赖注入
  4. 性能优化

    • 缓存Application Context
    • 避免重复获取

总结:

  • 选择合适的Context类型
  • 注意内存管理
  • 合理使用Context
  • 优化性能

第九章:Window 和 WindowManager(12 题)

9.1 Window是什么?它的作用是什么?

答案:

Window是Android的窗口抽象类,用于管理窗口的显示和交互。

Window的定义:

  • 窗口的抽象表示
  • 每个Activity都有一个Window
  • 管理窗口的显示和交互

主要作用:

  1. 窗口管理

    • 管理窗口的显示
    • 管理窗口的层级
  2. View容器

    • 作为View的容器
    • 管理View的显示
  3. 事件分发

    • 分发触摸事件
    • 分发键盘事件

特点:

  • 抽象类,不能直接实例化
  • 通过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类型:

  1. 应用Window(TYPE_APPLICATION)

    • Activity的Window
    • 层级范围:1-99
    • 最常用的类型
  2. 子Window(TYPE_APPLICATION_SUB)

    • 依附于应用Window
    • 层级范围:1000-1999
    • 如Dialog、PopupWindow
  3. 系统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的添加、移除和更新

作用:

  1. 添加Window

    • 将Window添加到屏幕
    • 设置Window参数
  2. 移除Window

    • 从屏幕移除Window
    • 释放资源
  3. 更新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()。

实现步骤:

  1. 获取WindowManager
WindowManager windowManager = (WindowManager) 
    getSystemService(Context.WINDOW_SERVICE);
  1. 创建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;
  1. 添加View
View view = LayoutInflater.from(this).inflate(R.layout.floating_view, null);
windowManager.addView(view, params);
  1. 移除View
windowManager.removeView(view);

注意事项:

  • 需要申请悬浮窗权限(系统Window)
  • 及时移除View,避免内存泄漏
  • 注意Window类型和权限

9.7 系统Window和应用Window的区别是什么?

答案:

系统Window和应用Window在权限层级使用场景上有区别。

主要区别:

特性应用Window系统Window
类型TYPE_APPLICATIONTYPE_SYSTEM_*
层级1-992000-2999
权限不需要需要系统权限
使用场景Activity、Dialog悬浮窗、系统UI

应用Window:

  • Activity的Window
  • 不需要特殊权限
  • 层级较低

系统Window:

  • 系统级Window
  • 需要系统权限或悬浮窗权限
  • 层级较高,可以覆盖其他Window

注意事项:

  • 系统Window需要特殊权限
  • Android 6.0+需要动态申请悬浮窗权限
  • 系统Window不易被系统回收

9.8 Window的层级(Z-order)如何管理?

答案:

Window的层级通过LayoutParams的type属性控制,数值越大层级越高。

层级管理:

  1. 设置Window类型
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_APPLICATION;
// 或
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
  1. 层级范围

    • 应用Window:1-99
    • 子Window:1000-1999
    • 系统Window:2000-2999
  2. 层级效果

    • 高层级Window覆盖低层级Window
    • 系统Window层级最高

注意事项:

  • 不能随意设置系统Window类型
  • 需要相应权限
  • 层级影响Window的显示顺序

9.9 悬浮窗如何实现?

答案:

悬浮窗通过WindowManager添加系统Window实现。

实现步骤:

  1. 申请权限
<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;
    }
}
  1. 创建悬浮View
View floatingView = LayoutInflater.from(this)
    .inflate(R.layout.floating_view, null);
  1. 设置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;
  1. 添加悬浮View
WindowManager windowManager = (WindowManager) 
    getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(floatingView, params);
  1. 移除悬浮View
windowManager.removeView(floatingView);

注意事项:

  • Android 8.0+使用TYPE_APPLICATION_OVERLAY
  • 需要动态申请权限
  • 及时移除,避免内存泄漏

9.10 Window的权限如何申请?

答案:

系统Window需要申请悬浮窗权限,Android 6.0+需要动态申请。

申请步骤:

  1. 声明权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
  1. 检查权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (!Settings.canDrawOverlays(this)) {
        // 没有权限,申请权限
    }
}
  1. 申请权限
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);
  1. 检查权限结果
@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最佳实践包括权限管理内存管理性能优化等。

最佳实践:

  1. 权限管理

    • 动态申请悬浮窗权限
    • 处理权限被拒绝的情况
    • 检查权限状态
  2. 内存管理

    • 及时移除Window
    • 避免内存泄漏
    • 在合适的时机添加和移除
  3. 性能优化

    • 避免频繁添加和移除
    • 复用Window
    • 优化View层级
  4. 用户体验

    • 提供关闭按钮
    • 支持拖拽移动
    • 合理设置位置和大小

总结:

  • 注意权限管理
  • 注意内存管理
  • 优化性能
  • 提升用户体验

9.12 Window的内存泄漏如何避免?

答案:

Window内存泄漏的常见原因和避免方法。

常见泄漏场景:

  1. 未移除Window
// 错误:添加后未移除
windowManager.addView(view, params);

// 正确:及时移除
windowManager.addView(view, params);
// 在合适的时机移除
windowManager.removeView(view);
  1. 持有Context引用
// 错误:持有Activity Context
private Context context;

// 正确:使用Application Context
private Context context = getApplicationContext();
  1. 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,提供任务切换和返回功能。

主要作用:

  1. 组织Activity

    • 将相关Activity组织在一起
    • 形成完整的任务流程
  2. 管理返回

    • 通过返回键返回上一个Activity
    • 按照后进先出原则
  3. 任务切换

    • 用户可以切换不同任务
    • 系统管理多个Task
  4. 生命周期管理

    • 系统管理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)
管理对象FragmentActivity
作用范围Activity内系统级
管理方式FragmentManagerActivityManager
使用场景Fragment导航Activity导航

返回栈:

  • 管理Fragment
  • 在Activity内
  • FragmentManager管理

任务栈:

  • 管理Activity
  • 系统级
  • ActivityManager管理

关系:

  • 一个Task可以包含多个Activity
  • 一个Activity可以包含多个Fragment
  • 两者都是后进先出

10.7 返回键的处理流程是什么?

答案:

返回键的处理流程涉及Fragment返回栈Activity任务栈

处理流程:

  1. 检查Fragment返回栈

    • 如果Fragment返回栈不为空,弹出栈顶Fragment
    • 如果Fragment返回栈为空,继续下一步
  2. 检查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()**控制返回栈行为。

控制方式:

  1. 添加到返回栈
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null); // 添加到返回栈
transaction.commit();
  1. 弹出返回栈
fragmentManager.popBackStack(); // 弹出栈顶
fragmentManager.popBackStack("name", 0); // 弹出到指定名称
fragmentManager.popBackStackImmediate(); // 立即弹出
  1. 自定义返回处理
@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?

答案:

可以通过TaskAffinityIntent 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。

关系说明:

  1. standard

    • 每次启动都创建新实例
    • 正常加入Task
  2. singleTop

    • 如果Activity在栈顶,复用实例
    • 否则创建新实例,正常加入Task
  3. singleTask

    • 如果Activity在Task中存在,复用并清除其上Activity
    • 如果不存在,创建新实例
    • 可以在指定Task中(通过TaskAffinity)
  4. singleInstance

    • Activity独占一个Task
    • 该Task中只有这一个Activity

影响:

  • launchMode决定Activity如何加入Task
  • 影响Task的结构
  • 影响返回键的行为

10.13 任务栈的最佳实践有哪些?

答案:

任务栈最佳实践包括合理设计用户体验性能优化等。

最佳实践:

  1. 合理设计Task结构

    • 相关Activity组织在同一Task
    • 避免Task过多
    • 合理使用启动模式
  2. 优化用户体验

    • 提供清晰的导航
    • 合理使用返回栈
    • 避免Task混乱
  3. 性能优化

    • 避免Task过多
    • 及时清理不需要的Task
    • 优化Task切换
  4. 安全性

    • 合理使用TaskAffinity
    • 避免Task被恶意利用

总结:

  • 合理设计Task结构
  • 优化用户体验
  • 注意性能和安全

10.14 任务栈的常见问题有哪些?

答案:

任务栈的常见问题包括Task混乱返回键异常Activity重复等。

常见问题:

  1. Task混乱

    • Activity被放入错误的Task
    • 多个Task包含相同Activity
    • 解决:合理使用启动模式和TaskAffinity
  2. 返回键异常

    • 返回键行为不符合预期
    • 返回键无法返回
    • 解决:检查返回栈和任务栈
  3. Activity重复

    • 同一个Activity有多个实例
    • 解决:使用singleTop或singleTask
  4. Task无法切换

    • 无法切换到其他Task
    • 解决:检查TaskAffinity和启动模式

调试方法:

  • 使用adb命令查看Task
  • 检查启动模式
  • 检查TaskAffinity

10.15 如何调试任务栈问题?

答案:

可以通过adb命令日志代码调试任务栈问题。

调试方法:

  1. adb命令
# 查看所有Task
adb shell dumpsys activity activities

# 查看特定Task
adb shell dumpsys activity activities | grep "Task"
  1. 代码调试
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());
}
  1. 日志分析
    • 查看Activity生命周期日志
    • 查看Task切换日志
    • 分析Task结构

注意事项:

  • getRunningTasks()需要权限
  • Android 5.0+限制使用
  • 主要用于开发和调试

第十一章:进程和线程(18 题)

11.1 Android的进程模型是什么?

答案:

Android采用多进程模型,每个应用运行在独立的进程中。

进程模型:

  • 每个应用有独立的进程
  • 进程间通过Binder IPC通信
  • 系统统一管理进程生命周期

特点:

  • 进程隔离,提高安全性
  • 进程独立,一个进程崩溃不影响其他进程
  • 系统可以回收进程释放资源

进程管理:

  • 系统根据优先级管理进程
  • 资源不足时回收低优先级进程
  • 前台进程优先级最高

11.2 进程和线程的区别是什么?

答案:

进程和线程在资源分配通信方式独立性上有区别。

主要区别:

特性进程线程
资源分配独立内存空间共享内存空间
通信方式IPC(Binder等)共享内存
独立性完全独立依赖进程
开销
崩溃影响不影响其他进程影响同一进程

进程:

  • 独立的内存空间
  • 进程间通信需要IPC
  • 开销大,但更安全

线程:

  • 共享进程内存空间
  • 线程间可以直接通信
  • 开销小,但需要注意同步

11.3 Android应用进程的优先级有哪些?

答案:

Android应用进程有5个优先级,从高到低。

进程优先级:

  1. 前台进程(Foreground)

    • 用户正在交互的进程
    • 优先级最高,最后被回收
  2. 可见进程(Visible)

    • 用户可见但不可交互的进程
    • 优先级较高
  3. 服务进程(Service)

    • 运行Service的进程
    • 优先级中等
  4. 后台进程(Background)

    • 不可见的进程
    • 优先级较低,容易被回收
  5. 空进程(Empty)

    • 没有Activity的进程
    • 优先级最低,最先被回收

优先级影响:

  • 系统根据优先级回收进程
  • 低优先级进程更容易被回收
  • 前台进程最后被回收

11.4 进程的启动方式有哪些?

答案:

Android进程通过Zygote进程fork启动。

启动流程:

  1. 用户启动应用
  2. ActivityManagerService检查进程
  3. 如果进程不存在,从Zygote fork新进程
  4. 加载应用代码
  5. 创建Application
  6. 启动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 多进程的优缺点是什么?

答案:

多进程有优点缺点,需要根据场景选择。

优点:

  1. 内存隔离

    • 进程独立,内存不共享
    • 一个进程崩溃不影响其他进程
  2. 安全性

    • 进程隔离,提高安全性
    • 数据不共享
  3. 性能

    • 可以利用多核CPU
    • 独立的内存管理

缺点:

  1. 内存占用

    • 每个进程独立内存
    • 内存占用增加
  2. 通信复杂

    • 进程间通信需要IPC
    • 实现复杂
  3. 数据共享

    • 数据不共享,需要特殊处理
    • 同步复杂

11.7 多进程的数据共享如何实现?

答案:

多进程数据共享可以通过ContentProviderSharedPreferences文件等方式。

实现方式:

  1. ContentProvider
// 通过ContentProvider共享数据
Uri uri = Uri.parse("content://com.example.provider/data");
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, null, null, null, null);
  1. SharedPreferences(MODE_MULTI_PROCESS)
SharedPreferences prefs = getSharedPreferences("prefs", 
    Context.MODE_MULTI_PROCESS);
  1. 文件
// 通过文件共享数据
File file = new File(getExternalFilesDir(null), "data.txt");
  1. Binder IPC
// 通过AIDL或Messenger通信

注意事项:

  • MODE_MULTI_PROCESS已废弃
  • 推荐使用ContentProvider
  • 注意数据同步

11.8 多进程的常见问题有哪些?

答案:

多进程的常见问题包括数据不同步单例失效静态变量失效等。

常见问题:

  1. 数据不同步

    • 每个进程有独立内存
    • 数据不共享
    • 解决:使用ContentProvider或文件共享
  2. 单例失效

    • 每个进程有独立的单例
    • 解决:使用进程间通信
  3. 静态变量失效

    • 每个进程有独立的静态变量
    • 解决:使用进程间通信
  4. Application多次创建

    • 每个进程都会创建Application
    • 解决:在onCreate()中判断进程

解决方案:

  • 使用ContentProvider共享数据
  • 使用进程间通信
  • 在Application中判断进程

11.9 Android的主线程是什么?

答案:

Android的主线程是UI线程,负责UI更新和事件处理。

主线程特点:

  • 应用启动时创建
  • 负责UI更新
  • 处理用户交互
  • 执行生命周期方法

主线程限制:

  • 不能执行耗时操作
  • 耗时操作会导致ANR
  • 必须在子线程执行耗时操作

主线程使用:

  • UI更新必须在主线程
  • 事件处理在主线程
  • 生命周期方法在主线程执行

11.10 为什么不能在主线程执行耗时操作?

答案:

主线程执行耗时操作会导致ANR(Application Not Responding)

原因:

  1. ANR超时

    • Activity:5秒
    • BroadcastReceiver:10秒
    • Service:20秒
  2. UI阻塞

    • 耗时操作阻塞UI更新
    • 用户无法交互
  3. 系统限制

    • 系统检测到主线程阻塞
    • 弹出ANR对话框

解决方案:

  • 耗时操作在子线程执行
  • 使用AsyncTask、Thread、线程池等
  • 使用Kotlin协程、RxJava等

11.11 线程的创建方式有哪些?

答案:

线程创建方式包括ThreadRunnable线程池等。

创建方式:

  1. 继承Thread
class MyThread extends Thread {
    @Override
    public void run() {
        // 执行任务
    }
}
MyThread thread = new MyThread();
thread.start();
  1. 实现Runnable
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        // 执行任务
    }
};
Thread thread = new Thread(runnable);
thread.start();
  1. 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(runnable);

推荐:

  • 使用线程池管理线程
  • 避免频繁创建线程
  • 注意线程安全

11.12 线程的同步机制有哪些?

答案:

线程同步机制包括synchronizedLockvolatile等。

同步机制:

  1. synchronized
synchronized (lock) {
    // 同步代码块
}
  1. ReentrantLock
Lock lock = new ReentrantLock();
lock.lock();
try {
    // 同步代码块
} finally {
    lock.unlock();
}
  1. volatile
private volatile boolean flag;
  1. Atomic类
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

选择建议:

  • 简单场景使用synchronized
  • 复杂场景使用Lock
  • 原子操作使用Atomic类

11.13 进程的保活机制有哪些?

答案:

进程保活机制包括前台Service双进程守护等,但需要注意合规性。

保活机制:

  1. 前台Service

    • 提升进程优先级
    • 必须显示通知
  2. START_STICKY

    • Service被杀死后自动重启
  3. 双进程守护

    • 两个进程互相守护
    • 不推荐,可能被系统限制
  4. JobScheduler/WorkManager

    • 系统统一调度
    • 推荐方式

注意事项:

  • Android 8.0+限制后台服务
  • 过度保活可能影响用户体验
  • 推荐使用系统推荐方式

11.14 进程的优先级如何提升?

答案:

可以通过前台Service通知等方式提升进程优先级。

提升方式:

  1. 前台Service
startForeground(1, notification);
  1. 通知
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(1, notification);
  1. Activity可见
    • 保持Activity可见
    • 提升进程优先级

注意事项:

  • 不能随意提升优先级
  • 需要合理使用
  • 注意用户体验

11.15 进程的内存管理如何实现?

答案:

进程内存管理通过系统回收机制应用优化实现。

内存管理:

  1. 系统回收

    • 系统根据优先级回收进程
    • 低优先级进程优先回收
  2. 应用优化

    • 及时释放资源
    • 避免内存泄漏
    • 使用内存分析工具
  3. 内存监控

    • 监控内存使用
    • 及时处理内存问题

最佳实践:

  • 及时释放资源
  • 避免内存泄漏
  • 使用内存分析工具
  • 优化内存使用

11.16 进程的最佳实践有哪些?

答案:

进程最佳实践包括合理设计内存管理性能优化等。

最佳实践:

  1. 合理设计

    • 避免不必要的多进程
    • 合理使用多进程
  2. 内存管理

    • 及时释放资源
    • 避免内存泄漏
  3. 性能优化

    • 优化进程启动
    • 减少内存占用
  4. 用户体验

    • 避免过度保活
    • 合理使用前台Service

总结:

  • 合理设计进程结构
  • 注意内存管理
  • 优化性能
  • 提升用户体验

11.17 线程的最佳实践有哪些?

答案:

线程最佳实践包括使用线程池线程安全避免泄漏等。

最佳实践:

  1. 使用线程池

    • 避免频繁创建线程
    • 统一管理线程
  2. 线程安全

    • 使用同步机制
    • 避免竞态条件
  3. 避免泄漏

    • 及时停止线程
    • 避免持有Context引用
  4. 性能优化

    • 合理使用线程
    • 避免线程过多

总结:

  • 使用线程池
  • 注意线程安全
  • 避免内存泄漏
  • 优化性能

11.18 进程和线程的监控如何实现?

答案:

可以通过系统API工具等方式监控进程和线程。

监控方式:

  1. 系统API
ActivityManager activityManager = (ActivityManager) 
    getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
  1. 工具监控

    • Android Studio Profiler
    • LeakCanary
    • Systrace
  2. 日志监控

    • 查看logcat日志
    • 分析进程和线程信息

监控内容:

  • 进程内存使用
  • 线程数量
  • CPU使用率
  • 内存泄漏

第十二章:Binder 机制(15 题)

12.1 Binder是什么?它的作用是什么?

答案:

Binder是Android的进程间通信(IPC)机制,用于跨进程通信。

Binder的定义:

  • Android特有的IPC机制
  • 基于Linux内核的Binder驱动
  • 用于跨进程通信

主要作用:

  1. 进程间通信

    • 应用间通信
    • 应用与系统服务通信
  2. 数据传递

    • 传递基本类型
    • 传递Parcelable对象
  3. 方法调用

    • 跨进程方法调用
    • 类似本地方法调用

特点:

  • 高性能(一次拷贝)
  • 安全性好
  • 系统统一管理

12.2 为什么Android使用Binder而不是其他IPC机制?

答案:

Android选择Binder主要因为性能安全性易用性

优势:

  1. 性能好

    • 一次拷贝(比Socket的两次拷贝快)
    • 内存映射,效率高
  2. 安全性高

    • 基于Linux内核
    • 系统统一管理
    • 支持权限控制
  3. 易用性好

    • 类似本地方法调用
    • 支持AIDL自动生成代码

与其他IPC对比:

  • Socket:性能较差,需要序列化
  • 共享内存:安全性较差
  • 管道:功能有限

总结:

  • Binder在性能、安全性和易用性上都有优势
  • 是Android的最佳选择

12.3 Binder的通信原理是什么?

答案:

Binder通信基于Linux内核的Binder驱动,通过内存映射实现。

通信流程:

  1. Client进程通过Binder驱动发送请求
  2. Binder驱动将请求转发到Server进程
  3. Server进程处理请求并返回结果
  4. Binder驱动将结果返回给Client进程

关键组件:

  • Binder驱动:Linux内核模块,管理IPC
  • ServiceManager:系统服务管理器
  • Binder对象:进程间通信的代理

优势:

  • 一次拷贝,性能好
  • 内存映射,效率高
  • 系统统一管理

12.4 Binder的架构是什么?

答案:

Binder架构包括ClientServerBinder驱动ServiceManager

架构层次:

Client进程
  ↓
Binder代理(Proxy)
  ↓
Binder驱动(Kernel)
  ↓
Binder实现(Stub)
  ↓
Server进程

组件说明:

  • Client:调用方
  • Server:服务提供方
  • Binder驱动:内核模块,管理IPC
  • ServiceManager:系统服务管理器

工作流程:

  1. Client通过Proxy调用方法
  2. Proxy通过Binder驱动发送请求
  3. Binder驱动转发到Server
  4. Server的Stub处理请求
  5. 结果通过Binder驱动返回

12.5 Binder的客户端如何实现?

答案:

Binder客户端通过Proxy调用服务端方法。

实现方式:

// 通过AIDL生成Proxy
IMyService myService = IMyService.Stub.asInterface(binder);
myService.doSomething("data");

流程:

  1. 绑定Service获取Binder
  2. 通过Stub.asInterface()获取Proxy
  3. 通过Proxy调用方法
  4. 方法调用通过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;
}

流程:

  1. 实现Stub接口
  2. 在onBind()中返回Binder
  3. 系统通过Binder驱动转发请求
  4. 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使用

使用流程:

  1. 定义AIDL接口
  2. 系统自动生成Proxy和Stub
  3. 实现Stub
  4. 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性能优化可以从数据传输调用频率等方面优化。

优化策略:

  1. 减少数据传输

    • 只传递必要数据
    • 避免传递大数据
  2. 批量操作

    • 合并多个调用
    • 减少调用次数
  3. 异步调用

    • 使用异步调用
    • 避免阻塞

注意事项:

  • 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最佳实践包括接口设计性能优化安全性等。

最佳实践:

  1. 合理设计接口

    • 接口简洁
    • 避免复杂参数
  2. 性能优化

    • 减少数据传输
    • 避免频繁调用
  3. 安全性

    • 设置适当权限
    • 验证数据

总结:

  • 合理设计接口
  • 优化性能
  • 注意安全性

第十三章:ANR 和 Crash(15 题)

13.1 ANR是什么?它的原因是什么?

答案:

ANR(Application Not Responding)是应用无响应,用户无法与应用交互。

ANR的原因:

  1. 主线程阻塞

    • 主线程执行耗时操作
    • 超过超时时间
  2. 超时时间

    • Activity:5秒
    • BroadcastReceiver:10秒
    • Service:20秒
  3. 常见场景

    • 网络请求在主线程
    • 文件读写在主线程
    • 数据库操作在主线程

解决方案:

  • 耗时操作在子线程执行
  • 使用异步操作
  • 优化主线程性能

13.2 ANR的类型有哪些?

答案:

ANR主要有三种类型,对应不同的超时时间。

ANR类型:

  1. Activity ANR

    • 超时时间:5秒
    • 原因:Activity生命周期方法阻塞
  2. BroadcastReceiver ANR

    • 超时时间:10秒
    • 原因:onReceive()方法阻塞
  3. Service ANR

    • 超时时间:20秒
    • 原因:Service方法阻塞

共同特点:

  • 都是主线程阻塞
  • 超过超时时间
  • 用户无法交互

13.3 ANR的超时时间是多少?

答案:

ANR的超时时间根据组件类型不同而不同。

超时时间:

  • Activity:5秒
  • BroadcastReceiver:10秒
  • Service:20秒

超时机制:

  • 系统监控主线程
  • 超过超时时间弹出ANR对话框
  • 用户可以选择等待或关闭应用

注意事项:

  • 超时时间不能修改
  • 必须在超时时间内完成操作
  • 耗时操作必须在子线程

13.4 如何避免ANR?

答案:

避免ANR需要优化主线程使用异步操作等。

避免方法:

  1. 耗时操作在子线程
new Thread(() -> {
    // 耗时操作
}).start();
  1. 使用异步框架

    • AsyncTask
    • Handler
    • 线程池
    • Kotlin协程
  2. 优化主线程

    • 减少主线程工作量
    • 优化布局
    • 减少计算

最佳实践:

  • 所有耗时操作在子线程
  • 主线程只处理UI更新
  • 使用异步框架

13.5 ANR的检测方法有哪些?

答案:

ANR检测可以通过日志分析工具监控等方式。

检测方法:

  1. 日志分析

    • 查看logcat中的ANR日志
    • 分析主线程堆栈
  2. 工具监控

    • Android Studio Profiler
    • StrictMode
    • ANR WatchDog
  3. 代码检测

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyLog()
    .build());

注意事项:

  • 及时分析ANR日志
  • 使用工具监控
  • 优化主线程性能

13.6 ANR的日志如何分析?

答案:

ANR日志包含主线程堆栈系统信息等,需要分析阻塞原因。

日志位置:

  • /data/anr/traces.txt
  • logcat中的ANR日志

分析要点:

  1. 查找主线程

    • 找到"main"线程
    • 查看堆栈信息
  2. 分析阻塞点

    • 找到耗时操作
    • 分析阻塞原因
  3. 优化建议

    • 将耗时操作移到子线程
    • 优化代码逻辑

示例:

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自定义监控等方式实现。

监控方式:

  1. StrictMode
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyLog()
    .build());
  1. 自定义监控
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
    @Override
    public void run() {
        // 检测主线程是否阻塞
    }
}, 5000);
  1. 第三方库
    • ANR WatchDog
    • BlockCanary

注意事项:

  • 开发环境使用
  • 生产环境谨慎使用
  • 注意性能影响

13.8 ANR的最佳实践有哪些?

答案:

ANR最佳实践包括优化主线程使用异步监控检测等。

最佳实践:

  1. 优化主线程

    • 减少主线程工作量
    • 避免耗时操作
  2. 使用异步

    • 耗时操作在子线程
    • 使用异步框架
  3. 监控检测

    • 使用工具监控
    • 及时分析日志

总结:

  • 优化主线程性能
  • 使用异步操作
  • 监控和检测

13.9 Crash的类型有哪些?

答案:

Crash主要有运行时异常空指针异常内存溢出等类型。

常见类型:

  1. NullPointerException

    • 空指针异常
    • 最常见
  2. IndexOutOfBoundsException

    • 数组越界
    • 集合越界
  3. OutOfMemoryError

    • 内存溢出
    • 内存不足
  4. 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日志包含异常类型堆栈信息系统信息等。

分析要点:

  1. 异常类型

    • 确定异常类型
    • 了解异常原因
  2. 堆栈信息

    • 找到崩溃位置
    • 分析调用链
  3. 系统信息

    • Android版本
    • 设备信息
    • 内存信息

分析工具:

  • Android Studio
  • Crashlytics
  • Bugly

13.12 如何避免Crash?

答案:

避免Crash需要防御性编程异常处理测试等。

避免方法:

  1. 防御性编程
if (object != null) {
    object.doSomething();
}
  1. 异常处理
try {
    // 可能崩溃的代码
} catch (Exception e) {
    // 处理异常
}
  1. 测试
    • 单元测试
    • 集成测试
    • 压力测试

最佳实践:

  • 防御性编程
  • 异常处理
  • 充分测试

13.13 Crash的监控如何实现?

答案:

Crash监控可以通过第三方SDK自定义监控等方式实现。

监控方式:

  1. 第三方SDK

    • Firebase Crashlytics
    • Bugly
    • Sentry
  2. 自定义监控

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        // 上报崩溃信息
        reportCrash(e);
    }
});

监控内容:

  • 崩溃类型
  • 堆栈信息
  • 设备信息
  • 用户信息

13.14 Crash的上报如何实现?

答案:

Crash上报可以通过第三方SDK自定义上报等方式实现。

上报方式:

  1. 第三方SDK
FirebaseCrashlytics.getInstance().recordException(e);
  1. 自定义上报
private void reportCrash(Throwable e) {
    // 收集崩溃信息
    CrashInfo crashInfo = collectCrashInfo(e);
    
    // 上报到服务器
    uploadCrashInfo(crashInfo);
}

上报内容:

  • 异常类型和堆栈
  • 设备信息
  • 应用版本
  • 用户信息

13.15 Crash的最佳实践有哪些?

答案:

Crash最佳实践包括防御性编程异常处理监控上报等。

最佳实践:

  1. 防御性编程

    • 空值检查
    • 边界检查
  2. 异常处理

    • 捕获异常
    • 记录日志
  3. 监控上报

    • 使用监控工具
    • 及时上报

总结:

  • 防御性编程
  • 异常处理
  • 监控和上报

第十四章:AndroidManifest 和资源管理(12 题)

14.1 AndroidManifest.xml的作用是什么?

答案:

AndroidManifest.xml是应用的配置文件,声明应用的基本信息和组件。

主要作用:

  1. 声明组件

    • Activity、Service、BroadcastReceiver、ContentProvider
  2. 配置权限

    • 声明需要的权限
    • 声明自定义权限
  3. 应用信息

    • 包名、版本号
    • 应用名称、图标
  4. 系统配置

    • 最低SDK版本
    • 目标SDK版本

14.2 AndroidManifest.xml的常用标签有哪些?

答案:

AndroidManifest.xml的常用标签包括manifestapplicationactivity等。

常用标签:

  1. <manifest>:根标签
  2. <application>:应用配置
  3. <activity>:Activity声明
  4. <service>:Service声明
  5. <receiver>:BroadcastReceiver声明
  6. <provider>:ContentProvider声明
  7. <uses-permission>:权限声明
  8. <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资源类型包括布局字符串图片颜色等。

资源类型:

  1. 布局资源:layout/
  2. 字符串资源:values/strings.xml
  3. 图片资源:drawable/
  4. 颜色资源:values/colors.xml
  5. 样式资源:values/styles.xml
  6. 菜单资源:menu/
  7. 动画资源:anim/

14.6 资源的查找机制是什么?

答案:

资源查找按照配置限定符匹配最合适的资源。

查找流程:

  1. 根据配置限定符匹配
  2. 选择最匹配的资源
  3. 如果找不到,使用默认资源

配置限定符:

  • 语言: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 资源的动态加载如何实现?

答案:

资源动态加载可以通过AssetManagerResources实现。

实现方式:

AssetManager assetManager = getAssets();
InputStream is = assetManager.open("file.txt");

Resources resources = getResources();
Drawable drawable = resources.getDrawable(R.drawable.icon);

14.10 资源的性能优化有哪些?

答案:

资源性能优化包括图片优化资源压缩延迟加载等。

优化策略:

  1. 图片优化

    • 使用WebP格式
    • 压缩图片大小
  2. 资源压缩

    • 使用ProGuard
    • 移除未使用资源
  3. 延迟加载

    • 按需加载资源
    • 使用ViewStub

14.11 资源的最佳实践有哪些?

答案:

资源最佳实践包括合理组织多语言支持性能优化等。

最佳实践:

  1. 合理组织

    • 按类型组织
    • 使用限定符
  2. 多语言支持

    • 支持多语言
    • 提供默认资源
  3. 性能优化

    • 优化图片
    • 压缩资源

14.12 资源的常见问题有哪些?

答案:

资源常见问题包括找不到资源资源冲突内存占用等。

常见问题:

  1. 找不到资源

    • 检查资源名称
    • 检查限定符
  2. 资源冲突

    • 检查资源ID
    • 检查包名
  3. 内存占用

    • 优化图片大小
    • 及时释放资源

第十五章:应用签名和版本适配(10 题)

15.1 应用签名的作用是什么?

答案:

应用签名用于验证应用身份保证应用完整性

作用:

  1. 身份验证

    • 标识应用开发者
    • 防止应用被篡改
  2. 完整性保证

    • 验证应用未被修改
    • 保证应用安全
  3. 权限控制

    • 相同签名可以共享数据
    • 系统服务需要签名

15.2 如何生成签名文件?

答案:

签名文件通过keytoolAndroid 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 签名的类型有哪些?

答案:

签名类型包括调试签名发布签名

类型:

  1. 调试签名

    • 自动生成
    • 用于开发测试
  2. 发布签名

    • 手动生成
    • 用于发布应用

15.4 签名的验证机制是什么?

答案:

签名验证在应用安装时进行,系统验证签名有效性。

验证流程:

  1. 检查签名文件
  2. 验证签名有效性
  3. 检查签名是否匹配
  4. 安装或拒绝

15.5 Android版本适配的挑战是什么?

答案:

版本适配挑战包括API变化行为变化权限变化等。

挑战:

  1. API变化

    • 新API需要检查版本
    • 废弃API需要替换
  2. 行为变化

    • 系统行为变化
    • 需要适配处理
  3. 权限变化

    • 权限模型变化
    • 需要动态申请

15.6 如何适配不同Android版本?

答案:

版本适配通过版本检查兼容库条件编译等方式。

适配方式:

  1. 版本检查
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // 使用新API
} else {
    // 使用旧API
}
  1. 兼容库

    • AndroidX库
    • Support库
  2. 条件编译

    • 使用@TargetApi注解
    • 使用@RequiresApi注解

15.7 版本适配的最佳实践有哪些?

答案:

版本适配最佳实践包括使用兼容库充分测试渐进式适配等。

最佳实践:

  1. 使用兼容库

    • AndroidX
    • Support库
  2. 充分测试

    • 在不同版本测试
    • 覆盖主要版本
  3. 渐进式适配

    • 逐步适配新版本
    • 保持向后兼容

15.8 如何测试版本兼容性?

答案:

版本兼容性测试可以通过多设备测试模拟器测试等方式。

测试方式:

  1. 多设备测试

    • 使用不同版本设备
    • 覆盖主要版本
  2. 模拟器测试

    • 创建不同版本模拟器
    • 自动化测试
  3. 云测试平台

    • Firebase Test Lab
    • 其他云测试平台

15.9 应用安装和卸载的流程是什么?

答案:

应用安装和卸载由PackageManager管理。

安装流程:

  1. 验证签名
  2. 检查权限
  3. 安装应用
  4. 注册组件

卸载流程:

  1. 停止应用
  2. 删除文件
  3. 注销组件

15.10 应用更新的最佳实践有哪些?

答案:

应用更新最佳实践包括版本管理数据迁移用户体验等。

最佳实践:

  1. 版本管理

    • 合理版本号
    • 版本说明
  2. 数据迁移

    • 处理数据升级
    • 保持数据兼容
  3. 用户体验

    • 平滑更新
    • 减少影响

第十六章:Handler 消息机制(20 题)

16.1 Handler是什么?它的作用是什么?

答案:

Handler是Android的消息处理机制,用于线程间通信和UI更新。

作用:

  1. 线程间通信

    • 子线程向主线程发送消息
    • 主线程处理消息
  2. UI更新

    • 在子线程更新UI
    • 通过Handler切换到主线程
  3. 延迟执行

    • 延迟执行任务
    • 定时任务

16.2 Handler、Looper、MessageQueue的关系是什么?

答案:

Handler、Looper、MessageQueue是消息机制的核心组件

关系:

  • Looper:消息循环,从MessageQueue取消息
  • MessageQueue:消息队列,存储消息
  • Handler:消息处理器,发送和处理消息

工作流程:

Handler发送消息 → MessageQueue → Looper循环取消息 → Handler处理消息

16.3 Handler的消息机制是什么?

答案:

Handler消息机制通过消息队列消息循环实现。

机制:

  1. Handler发送消息到MessageQueue
  2. Looper从MessageQueue取消息
  3. 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层

原理:

  1. 有消息时处理

    • 处理消息队列中的消息
    • 执行Handler的handleMessage()
    • 处理用户交互和系统事件
  2. 无消息时阻塞

    • 阻塞在nativePollOnce()
    • 进入native层等待
    • 不占用CPU资源
    • 等待新消息唤醒
  3. 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最佳实践包括避免泄漏合理使用性能优化等。

最佳实践:

  1. 避免泄漏

    • 使用静态内部类
    • 使用WeakReference
  2. 合理使用

    • 及时移除消息
    • 避免消息堆积
  3. 性能优化

    • 使用obtain()获取Message
    • 避免频繁创建Handler

16.18 Handler的线程安全问题有哪些?

答案:

Handler的线程安全问题主要包括消息队列的线程安全Handler使用的线程安全

线程安全问题:

  1. MessageQueue线程安全

    • MessageQueue使用锁保护
    • 消息入队和出队是线程安全的
    • 多个线程可以安全地向同一个MessageQueue发送消息
  2. Handler使用线程安全

    • Handler可以在任何线程创建
    • 但必须绑定到有Looper的线程
    • 消息处理在Looper所在线程执行
  3. 潜在问题

    • 如果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协程RxJavaExecutor等。

替代方案:

  1. Kotlin协程
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 后台任务
    }
    // 主线程更新UI
}
  1. RxJava
Observable.fromCallable(() -> {
    // 后台任务
    return result;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
    // 更新UI
});
  1. 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个生命周期方法,按顺序调用。

生命周期方法:

  1. onPreExecute()

    • 在主线程执行
    • 任务执行前调用
    • 用于初始化UI
  2. doInBackground(Params... params)

    • 在后台线程执行
    • 执行耗时任务
    • 可以调用publishProgress()更新进度
  3. onProgressUpdate(Progress... values)

    • 在主线程执行
    • 更新进度时调用
    • 更新UI进度
  4. onPostExecute(Result result)

    • 在主线程执行
    • 任务完成后调用
    • 处理结果并更新UI

执行流程:

onPreExecute() → doInBackground() → onPostExecute()
                      ↓
                onProgressUpdate() (可选)

17.3 AsyncTask的执行流程是什么?

答案:

AsyncTask执行流程包括创建执行更新完成等步骤。

执行流程:

  1. 创建AsyncTask实例
MyAsyncTask task = new MyAsyncTask();
  1. 调用execute()
task.execute(params);
  1. 系统调用生命周期方法

    • onPreExecute():主线程,执行前
    • doInBackground():后台线程,执行任务
    • onProgressUpdate():主线程,更新进度(可选)
    • onPostExecute():主线程,执行完成
  2. 任务完成

    • 结果传递给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最佳实践包括避免泄漏及时取消合理使用等。

最佳实践:

  1. 避免内存泄漏

    • 使用静态内部类
    • 使用WeakReference
  2. 及时取消

    • 在onDestroy()中取消
    • 检查isCancelled()
  3. 合理使用

    • 避免长时间任务
    • 注意生命周期

注意:

  • AsyncTask已废弃
  • 推荐使用替代方案

17.8 AsyncTask为什么被废弃?替代方案是什么?

答案:

AsyncTask被废弃因为设计问题更好的替代方案

废弃原因:

  1. 设计问题

    • 容易造成内存泄漏
    • 线程池配置不灵活
    • 生命周期管理复杂
  2. 更好的替代方案

    • 线程池更灵活
    • Kotlin协程更现代
    • ViewModel + LiveData更符合架构

替代方案:

  1. 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> {
    // 后台任务
    runOnUiThread(() -> {
        // 更新UI
    });
});
  1. Kotlin协程
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 后台任务
    }
    // 更新UI
}
  1. ViewModel + LiveData
    • ViewModel管理数据
    • LiveData观察变化
    • 自动处理生命周期

17.9 Loader是什么?它的作用是什么?

答案:

Loader是数据加载器,用于异步加载数据(已废弃)。

作用:

  • 异步加载数据
  • 管理数据生命周期
  • 自动处理配置变更

注意:

  • 已废弃
  • 推荐使用ViewModel + LiveData

17.10 LoaderManager的作用是什么?

答案:

LoaderManager用于管理Loader的生命周期

作用:

  1. 创建Loader

    • 通过initLoader()创建
    • 管理Loader实例
  2. 重启Loader

    • 通过restartLoader()重启
    • 重新加载数据
  3. 销毁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生命周期包括创建启动停止重置等。

生命周期:

  1. onCreateLoader()

    • 创建Loader实例
    • 初始化Loader
  2. onLoadFinished()

    • 数据加载完成
    • 更新UI
  3. onLoaderReset()

    • Loader重置
    • 清理数据

生命周期管理:

  • LoaderManager自动管理
  • 配置变更时保持
  • Activity销毁时清理

17.12 Loader的使用场景有哪些?

答案:

Loader主要用于数据加载场景。

使用场景:

  1. 数据库查询

    • 加载Cursor数据
    • 自动更新
  2. 网络数据加载

    • 加载网络数据
    • 缓存管理
  3. 配置变更保持

    • 横竖屏切换保持
    • 自动重新加载

注意事项:

  • Loader已废弃
  • 推荐使用ViewModel + LiveData

17.13 Loader的最佳实践有哪些?

答案:

Loader最佳实践包括合理使用生命周期管理等。

最佳实践:

  1. 合理使用

    • 适合数据加载场景
    • 利用自动更新
  2. 生命周期管理

    • 让LoaderManager管理
    • 不要手动管理
  3. 处理配置变更

    • 利用自动保持
    • 避免重复加载

注意:

  • Loader已废弃
  • 推荐使用ViewModel + LiveData

17.14 Loader和AsyncTask的区别是什么?

答案:

Loader和AsyncTask都可以异步加载,但设计目的不同

主要区别:

特性LoaderAsyncTask
设计目的数据加载通用异步任务
生命周期自动管理手动管理
配置变更自动保持需要手动处理
数据更新自动更新手动更新
使用场景数据加载通用任务

Loader特点:

  • 专门用于数据加载
  • 自动管理生命周期
  • 配置变更时自动保持
  • 数据变化时自动更新

AsyncTask特点:

  • 通用异步任务
  • 需要手动管理生命周期
  • 配置变更需要手动处理

注意:

  • 两者都已废弃
  • 推荐使用现代方案

17.15 Loader的替代方案是什么?

答案:

Loader的替代方案包括ViewModel + LiveDataRoom + LiveData等。

替代方案:

  1. 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;
    }
}
  1. Room + LiveData
@Dao
public interface DataDao {
    @Query("SELECT * FROM data")
    LiveData<List<Data>> getAllData();
}
  1. Kotlin协程 + Flow
fun loadData(): Flow<List<Data>> = flow {
    emit(loadDataFromSource())
}.flowOn(Dispatchers.IO)

优势:

  • 更好的生命周期管理
  • 更符合现代架构
  • 更灵活的使用方式

答案:

AsyncTask和Loader的替代方案包括线程池Kotlin协程ViewModel + LiveData等。

替代方案:

  1. 线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(runnable);
  1. Kotlin协程
lifecycleScope.launch {
    val result = withContext(Dispatchers.IO) {
        // 后台任务
    }
    // 更新UI
}
  1. ViewModel + LiveData
    • ViewModel管理数据
    • LiveData观察数据变化