Android 基础---第三部

5 阅读22分钟

第十八章:Notification 通知系统(12 题)

18.1 Notification是什么?它的作用是什么?

答案:

Notification是Android的通知组件,用于在状态栏显示通知信息。

Notification的定义:

  • 系统级UI组件
  • 在状态栏显示
  • 用户可以点击查看

主要作用:

  1. 信息提醒

    • 显示应用信息
    • 提醒用户关注
  2. 后台通知

    • 应用在后台时通知
    • 保持用户连接
  3. 用户交互

    • 点击通知打开应用
    • 执行操作

18.2 Notification的创建方式有哪些?

答案:

Notification通过NotificationCompat.Builder创建(推荐)。

创建方式:

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setContentTitle("通知标题")
    .setContentText("通知内容")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT);

Notification notification = builder.build();

Android 8.0+要求:

  • 必须创建通知渠道(Channel)
  • 通过渠道发送通知

18.3 Notification的渠道(Channel)是什么?

答案:

Notification Channel是通知分类,Android 8.0+必须使用。

Channel的作用:

  • 分类通知
  • 用户可单独控制
  • 设置通知行为

创建Channel:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(
        CHANNEL_ID,
        "渠道名称",
        NotificationManager.IMPORTANCE_DEFAULT
    );
    channel.setDescription("渠道描述");
    
    NotificationManager manager = getSystemService(NotificationManager.class);
    manager.createNotificationChannel(channel);
}

重要性级别:

  • IMPORTANCE_HIGH:高优先级
  • IMPORTANCE_DEFAULT:默认
  • IMPORTANCE_LOW:低优先级
  • IMPORTANCE_MIN:最低

18.4 Notification的优先级如何设置?

答案:

Notification优先级通过setPriority()Channel重要性设置。

设置方式:

  1. Android 7.1及以下
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
  1. Android 8.0+
// 通过Channel设置
channel.setImportance(NotificationManager.IMPORTANCE_HIGH);

优先级级别:

  • PRIORITY_MAX / IMPORTANCE_HIGH:最高
  • PRIORITY_HIGH / IMPORTANCE_DEFAULT:高
  • PRIORITY_DEFAULT / IMPORTANCE_LOW:默认
  • PRIORITY_LOW / IMPORTANCE_MIN:低

18.5 如何创建普通通知?

答案:

普通通知通过NotificationCompat.Builder创建。

创建步骤:

// 1. 创建通知渠道(Android 8.0+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(
        CHANNEL_ID, "渠道名称", NotificationManager.IMPORTANCE_DEFAULT);
    NotificationManager manager = getSystemService(NotificationManager.class);
    manager.createNotificationChannel(channel);
}

// 2. 创建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setContentTitle("通知标题")
    .setContentText("通知内容")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT);

// 3. 显示通知
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(1, builder.build());

18.6 如何创建大文本通知?

答案:

大文本通知使用BigTextStyle显示更多内容。

创建方式:

NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
bigTextStyle.bigText("这是大文本通知的内容,可以显示更多文字...");

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setContentTitle("大文本通知")
    .setStyle(bigTextStyle);

特点:

  • 可以显示更多文字
  • 展开后显示完整内容
  • 提升用户体验

18.7 如何创建进度通知?

答案:

进度通知使用**setProgress()**显示进度。

创建方式:

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setContentTitle("下载中")
    .setProgress(100, progress, false); // 最大值、当前值、不确定

// 更新进度
builder.setProgress(100, newProgress, false);
manager.notify(1, builder.build());

// 完成时
builder.setProgress(0, 0, false)
    .setContentText("下载完成");
manager.notify(1, builder.build());

参数说明:

  • 最大值:进度条最大值
  • 当前值:当前进度
  • 不确定:true表示不确定进度

18.8 如何创建自定义通知?

答案:

自定义通知使用RemoteViews自定义布局。

创建方式:

RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.custom_notification);
remoteViews.setTextViewText(R.id.title, "自定义标题");
remoteViews.setTextViewText(R.id.text, "自定义内容");

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setCustomContentView(remoteViews) // 收起时显示
    .setCustomBigContentView(remoteViews); // 展开时显示

注意事项:

  • 自定义布局有限制
  • 某些View不支持
  • 注意兼容性

18.9 Notification的点击事件如何处理?

答案:

Notification点击事件通过PendingIntent处理。

实现方式:

Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
    this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
    .setSmallIcon(R.drawable.icon)
    .setContentTitle("通知")
    .setContentIntent(pendingIntent) // 点击事件
    .setAutoCancel(true); // 点击后自动取消

操作按钮:

Intent actionIntent = new Intent(this, ActionReceiver.class);
PendingIntent actionPendingIntent = PendingIntent.getBroadcast(
    this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT);

builder.addAction(R.drawable.icon, "操作", actionPendingIntent);

18.10 Notification的取消如何实现?

答案:

Notification取消可以通过自动取消手动取消

取消方式:

  1. 自动取消
builder.setAutoCancel(true); // 点击后自动取消
  1. 手动取消
NotificationManager manager = getSystemService(NotificationManager.class);
manager.cancel(notificationId); // 取消指定通知
manager.cancelAll(); // 取消所有通知
  1. 程序化取消
// 在通知中设置取消操作
Intent cancelIntent = new Intent(this, CancelReceiver.class);
PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(
    this, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.addAction(R.drawable.cancel, "取消", cancelPendingIntent);

18.11 Notification的最佳实践有哪些?

答案:

Notification最佳实践包括合理使用用户体验性能优化等。

最佳实践:

  1. 合理使用

    • 不要过度通知
    • 提供关闭选项
  2. 用户体验

    • 清晰的标题和内容
    • 合适的优先级
    • 提供操作按钮
  3. 性能优化

    • 及时取消通知
    • 避免通知堆积
  4. 适配新版本

    • 创建通知渠道
    • 适配Android 8.0+

18.12 Android 8.0对通知的限制是什么?

答案:

Android 8.0(API 26)要求必须使用通知渠道

限制内容:

  • 所有通知必须指定Channel
  • 不能直接发送通知
  • 必须创建Channel

适配方式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = new NotificationChannel(
        CHANNEL_ID, "渠道名称", NotificationManager.IMPORTANCE_DEFAULT);
    NotificationManager manager = getSystemService(NotificationManager.class);
    manager.createNotificationChannel(channel);
}

注意事项:

  • Channel创建后不能修改重要性
  • 可以删除后重新创建
  • 用户可以在设置中控制

第十九章:Dialog 对话框(10 题)

19.1 Dialog是什么?它的作用是什么?

答案:

Dialog是对话框组件,用于显示临时信息和用户交互。

Dialog的定义:

  • 临时UI组件
  • 覆盖在当前界面之上
  • 用户必须处理才能继续

主要作用:

  1. 信息提示

    • 显示重要信息
    • 提示用户操作
  2. 用户交互

    • 确认操作
    • 输入信息
    • 选择选项
  3. 临时界面

    • 显示临时内容
    • 不占用Activity空间

19.2 Dialog的类型有哪些?

答案:

Dialog主要有AlertDialogProgressDialog自定义Dialog等类型。

Dialog类型:

  1. AlertDialog

    • 标准对话框
    • 最常用
  2. ProgressDialog

    • 进度对话框
    • 已废弃,推荐使用ProgressBar
  3. DatePickerDialog

    • 日期选择对话框
  4. TimePickerDialog

    • 时间选择对话框
  5. 自定义Dialog

    • 自定义布局
    • 灵活定制

19.3 AlertDialog的特点是什么?

答案:

AlertDialog是标准对话框,功能丰富,使用简单。

特点:

  • 支持标题、内容、按钮
  • 支持列表选择
  • 支持多选
  • 支持自定义布局

使用简单:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("标题")
    .setMessage("内容")
    .setPositiveButton("确定", null)
    .setNegativeButton("取消", null)
    .show();

19.4 DialogFragment的作用是什么?

答案:

DialogFragment是Fragment形式的Dialog,推荐使用。

作用:

  • 管理Dialog生命周期
  • 处理配置变更
  • 更好的生命周期管理

优势:

  • 生命周期管理更好
  • 配置变更时自动重建
  • 可以添加到回退栈

使用方式:

public class MyDialogFragment extends DialogFragment {
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        // 构建Dialog
        return builder.create();
    }
}

// 显示
MyDialogFragment dialog = new MyDialogFragment();
dialog.show(getSupportFragmentManager(), "tag");

19.5 如何创建AlertDialog?

答案:

AlertDialog通过AlertDialog.Builder创建。

创建方式:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("标题")
    .setMessage("内容")
    .setPositiveButton("确定", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // 确定按钮点击
        }
    })
    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // 取消按钮点击
        }
    })
    .setNeutralButton("中立", null)
    .show();

列表Dialog:

String[] items = {"选项1", "选项2", "选项3"};
builder.setItems(items, new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        // 处理选择
    }
});

19.6 如何创建自定义Dialog?

答案:

自定义Dialog通过自定义布局创建。

创建方式:

// 1. 创建布局文件 custom_dialog.xml

// 2. 创建Dialog
Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.custom_dialog);
dialog.setTitle("自定义Dialog");

// 3. 设置内容
TextView textView = dialog.findViewById(R.id.text);
textView.setText("自定义内容");

// 4. 显示
dialog.show();

使用AlertDialog自定义:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.custom_dialog, null);
builder.setView(view)
    .setPositiveButton("确定", null)
    .show();

19.7 Dialog的显示和隐藏如何控制?

答案:

Dialog通过**show()dismiss()**控制显示和隐藏。

显示和隐藏:

// 显示
dialog.show();

// 隐藏
dialog.dismiss();

// 取消(带动画)
dialog.cancel();

DialogFragment:

// 显示
dialogFragment.show(getSupportFragmentManager(), "tag");

// 隐藏
dialogFragment.dismiss();

注意事项:

  • 及时dismiss(),避免内存泄漏
  • 注意生命周期
  • 配置变更时自动处理

19.8 Dialog的生命周期是什么?

答案:

Dialog生命周期包括创建显示隐藏销毁等。

生命周期:

  1. 创建:new Dialog()或onCreateDialog()
  2. 显示:show()
  3. 隐藏:dismiss()
  4. 销毁:系统回收

DialogFragment生命周期:

  • onCreate()
  • onCreateDialog()
  • onStart()
  • onResume()
  • onPause()
  • onStop()
  • onDestroy()

注意事项:

  • DialogFragment生命周期更好
  • 推荐使用DialogFragment

19.9 Dialog的内存泄漏如何避免?

答案:

Dialog内存泄漏通过及时dismiss()使用DialogFragment避免。

避免方法:

  1. 及时dismiss()
@Override
protected void onDestroy() {
    super.onDestroy();
    if (dialog != null && dialog.isShowing()) {
        dialog.dismiss();
    }
}
  1. 使用DialogFragment

    • DialogFragment自动管理生命周期
    • 配置变更时自动处理
  2. 避免持有Context

    • 使用Application Context
    • 避免静态引用

19.10 Dialog的最佳实践有哪些?

答案:

Dialog最佳实践包括使用DialogFragment及时dismiss()用户体验等。

最佳实践:

  1. 使用DialogFragment

    • 更好的生命周期管理
    • 处理配置变更
  2. 及时dismiss()

    • 避免内存泄漏
    • 注意生命周期
  3. 用户体验

    • 清晰的标题和内容
    • 合理的按钮布局
    • 提供取消选项
  4. 性能优化

    • 避免频繁创建
    • 复用Dialog

第二十章:Menu 菜单系统(8 题)

20.1 Menu是什么?它的作用是什么?

答案:

Menu是菜单组件,用于提供操作选项。

Menu的定义:

  • 操作选项集合
  • 用户交互入口
  • 组织应用功能

主要作用:

  1. 功能入口

    • 提供操作选项
    • 组织应用功能
  2. 用户交互

    • 点击执行操作
    • 简化操作流程
  3. 界面优化

    • 节省屏幕空间
    • 保持界面简洁

20.2 Menu的类型有哪些?

答案:

Menu主要有OptionsMenuContextMenuPopupMenu等类型。

Menu类型:

  1. OptionsMenu

    • 选项菜单
    • 在ActionBar显示
  2. ContextMenu

    • 上下文菜单
    • 长按显示
  3. PopupMenu

    • 弹出菜单
    • 在指定位置显示
  4. SubMenu

    • 子菜单
    • 嵌套菜单

20.3 OptionsMenu的特点是什么?

答案:

OptionsMenu是选项菜单,在ActionBar显示。

特点:

  • 在ActionBar显示
  • 支持图标和文字
  • 可以折叠到溢出菜单
  • 常用操作入口

创建方式:

<!-- menu/options_menu.xml -->
<menu>
    <item
        android:id="@+id/menu_item"
        android:title="菜单项"
        android:icon="@drawable/icon" />
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.options_menu, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item:
            // 处理点击
            return true;
    }
    return super.onOptionsItemSelected(item);
}

20.4 ContextMenu的特点是什么?

答案:

ContextMenu是上下文菜单,长按View显示。

特点:

  • 长按View显示
  • 上下文相关操作
  • 临时菜单

创建方式:

// 注册ContextMenu
registerForContextMenu(view);

@Override
public void onCreateContextMenu(ContextMenu menu, View v, 
                                ContextMenu.ContextMenuInfo menuInfo) {
    getMenuInflater().inflate(R.menu.context_menu, menu);
}

@Override
public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item:
            // 处理点击
            return true;
    }
    return super.onContextItemSelected(item);
}

20.5 如何创建OptionsMenu?

答案:

OptionsMenu通过XML定义代码创建

方式1:XML定义(推荐)

<!-- menu/options_menu.xml -->
<menu>
    <item
        android:id="@+id/menu_search"
        android:title="搜索"
        android:icon="@drawable/search"
        android:showAsAction="ifRoom" />
    <item
        android:id="@+id/menu_settings"
        android:title="设置"
        android:showAsAction="never" />
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.options_menu, menu);
    return true;
}

方式2:代码创建

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(0, 1, 0, "菜单项1");
    menu.add(0, 2, 0, "菜单项2");
    return true;
}

20.6 如何创建ContextMenu?

答案:

ContextMenu需要注册View并实现回调方法。

创建步骤:

// 1. 注册ContextMenu
TextView textView = findViewById(R.id.text);
registerForContextMenu(textView);

// 2. 创建菜单
@Override
public void onCreateContextMenu(ContextMenu menu, View v, 
                                ContextMenu.ContextMenuInfo menuInfo) {
    super.onCreateContextMenu(menu, v, menuInfo);
    getMenuInflater().inflate(R.menu.context_menu, menu);
}

// 3. 处理点击
@Override
public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item:
            // 处理点击
            return true;
    }
    return super.onContextItemSelected(item);
}

20.7 Menu的点击事件如何处理?

答案:

Menu点击事件通过**onOptionsItemSelected()onContextItemSelected()**处理。

处理方式:

// OptionsMenu
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item:
            // 处理点击
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

// ContextMenu
@Override
public boolean onContextItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.menu_item:
            // 处理点击
            return true;
        default:
            return super.onContextItemSelected(item);
    }
}

20.8 Menu的最佳实践有哪些?

答案:

Menu最佳实践包括合理组织用户体验性能优化等。

最佳实践:

  1. 合理组织

    • 按功能分组
    • 使用子菜单
  2. 用户体验

    • 清晰的菜单项
    • 合理的图标
    • 常用项显示在ActionBar
  3. 性能优化

    • 使用XML定义
    • 避免频繁创建

第二十一章:权限系统(18 题)

21.1 Android的权限类型有哪些?

答案:

Android权限分为普通权限危险权限

权限类型:

  1. 普通权限(Normal)

    • 自动授予
    • 不影响用户隐私
  2. 危险权限(Dangerous)

    • 需要用户授权
    • 影响用户隐私
    • Android 6.0+需要运行时申请

权限组:

  • 位置、相机、存储、电话等
  • 同一组权限一次申请

21.2 普通权限和危险权限的区别是什么?

答案:

普通权限和危险权限在申请方式用户影响上有区别。

主要区别:

特性普通权限危险权限
申请方式自动授予需要用户授权
用户影响无影响影响隐私
运行时申请不需要Android 6.0+需要

普通权限示例:

  • INTERNET
  • ACCESS_NETWORK_STATE
  • VIBRATE

危险权限示例:

  • CAMERA
  • READ_EXTERNAL_STORAGE
  • ACCESS_FINE_LOCATION

21.3 权限的申请方式有哪些?

答案:

权限申请方式:静态声明运行时申请

申请方式:

  1. 静态声明(AndroidManifest.xml)
<uses-permission android:name="android.permission.CAMERA" />
  1. 运行时申请(Android 6.0+)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 
    != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(this, 
        new String[]{Manifest.permission.CAMERA}, REQUEST_CODE);
}

21.4 权限的检查方法有哪些?

答案:

权限检查通过**checkSelfPermission()**方法。

检查方式:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 
    == PackageManager.PERMISSION_GRANTED) {
    // 已授予权限
} else {
    // 未授予权限,需要申请
}

批量检查:

String[] permissions = {Manifest.permission.CAMERA, 
                       Manifest.permission.READ_EXTERNAL_STORAGE};
boolean allGranted = true;
for (String permission : permissions) {
    if (ContextCompat.checkSelfPermission(this, permission) 
        != PackageManager.PERMISSION_GRANTED) {
        allGranted = false;
        break;
    }
}

21.5 运行时权限是什么?

答案:

运行时权限是Android 6.0+引入的权限模型,危险权限需要运行时申请。

特点:

  • 危险权限需要运行时申请
  • 用户可以随时撤销
  • 提升用户控制权

申请流程:

  1. 检查权限
  2. 未授予则申请
  3. 处理申请结果

21.6 运行时权限的申请流程是什么?

答案:

运行时权限申请流程包括检查申请处理结果

完整流程:

// 1. 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) 
    != PackageManager.PERMISSION_GRANTED) {
    
    // 2. 是否需要显示说明
    if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
            Manifest.permission.CAMERA)) {
        // 显示说明对话框
        showRationaleDialog();
    } else {
        // 3. 申请权限
        ActivityCompat.requestPermissions(this, 
            new String[]{Manifest.permission.CAMERA}, REQUEST_CODE);
    }
} else {
    // 已授予,执行操作
    doSomething();
}

// 4. 处理申请结果
@Override
public void onRequestPermissionsResult(int requestCode, 
                                      String[] permissions, 
                                      int[] grantResults) {
    if (requestCode == REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == 
            PackageManager.PERMISSION_GRANTED) {
            // 权限已授予
            doSomething();
        } else {
            // 权限被拒绝
            handlePermissionDenied();
        }
    }
}

21.7 运行时权限的拒绝处理如何实现?

答案:

权限拒绝处理包括显示说明引导设置降级处理等。

处理方式:

@Override
public void onRequestPermissionsResult(int requestCode, 
                                      String[] permissions, 
                                      int[] grantResults) {
    if (grantResults.length > 0 && grantResults[0] != 
        PackageManager.PERMISSION_GRANTED) {
        
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, 
                permissions[0])) {
            // 用户拒绝但未选择"不再询问"
            showRationaleDialog();
        } else {
            // 用户选择"不再询问"
            showGoToSettingsDialog();
        }
    }
}

引导设置:

private void showGoToSettingsDialog() {
    new AlertDialog.Builder(this)
        .setTitle("需要权限")
        .setMessage("请在设置中授予权限")
        .setPositiveButton("去设置", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.setData(Uri.parse("package:" + getPackageName()));
                startActivity(intent);
            }
        })
        .setNegativeButton("取消", null)
        .show();
}

21.8 运行时权限的最佳实践有哪些?

答案:

运行时权限最佳实践包括合理申请用户体验错误处理等。

最佳实践:

  1. 合理申请

    • 需要时再申请
    • 说明申请原因
  2. 用户体验

    • 清晰的说明
    • 友好的提示
  3. 错误处理

    • 处理拒绝情况
    • 提供降级方案

21.9 权限的批量申请如何实现?

答案:

权限批量申请通过数组传递多个权限

实现方式:

String[] permissions = {
    Manifest.permission.CAMERA,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.ACCESS_FINE_LOCATION
};

List<String> needRequest = new ArrayList<>();
for (String permission : permissions) {
    if (ContextCompat.checkSelfPermission(this, permission) 
        != PackageManager.PERMISSION_GRANTED) {
        needRequest.add(permission);
    }
}

if (!needRequest.isEmpty()) {
    ActivityCompat.requestPermissions(this, 
        needRequest.toArray(new String[0]), REQUEST_CODE);
}

21.10 权限的永久拒绝如何处理?

答案:

权限永久拒绝(用户选择"不再询问")需要引导用户到设置

处理方式:

if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
    // 用户选择"不再询问"
    showGoToSettingsDialog();
}

引导设置:

  • 显示说明对话框
  • 提供跳转设置按钮
  • 在设置中手动授予

21.11 权限的安全问题如何避免?

答案:

权限安全问题通过最小权限原则权限验证等避免。

安全措施:

  1. 最小权限原则

    • 只申请必要权限
    • 避免过度申请
  2. 权限验证

    • 使用前检查权限
    • 处理权限变化
  3. 数据保护

    • 加密敏感数据
    • 安全存储

21.12 权限的测试如何进行?

答案:

权限测试包括授予测试拒绝测试撤销测试等。

测试场景:

  1. 首次申请
  2. 用户拒绝
  3. 用户永久拒绝
  4. 权限被撤销
  5. 权限重新授予

21.13 悬浮窗权限如何申请?

答案:

悬浮窗权限是特殊权限,需要跳转到设置页面。

申请方式:

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);
    }
}

21.14 通知权限如何申请?

答案:

通知权限在**Android 13+**需要申请。

申请方式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ContextCompat.checkSelfPermission(this, 
            Manifest.permission.POST_NOTIFICATIONS) 
        != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, 
            new String[]{Manifest.permission.POST_NOTIFICATIONS}, 
            REQUEST_CODE);
    }
}

21.15 系统设置权限如何申请?

答案:

系统设置权限需要跳转到设置页面

申请方式:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivity(intent);

21.16 权限的适配如何实现?

答案:

权限适配需要版本检查不同处理

适配方式:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // Android 6.0+运行时申请
    requestPermissions();
} else {
    // Android 6.0以下自动授予
    doSomething();
}

21.17 权限的监控如何实现?

答案:

权限监控可以通过权限变化监听实现。

监控方式:

// 定期检查权限状态
// 使用权限变化回调

21.18 权限的最佳实践总结

答案:

权限最佳实践总结:

  1. 最小权限原则
  2. 合理申请时机
  3. 友好的用户体验
  4. 完善的错误处理
  5. 充分的测试

第二十二章:屏幕适配和资源(15 题)

22.1 Android屏幕适配的挑战是什么?

答案:

Android屏幕适配挑战包括屏幕尺寸多样分辨率不同密度不同等。

挑战:

  1. 屏幕尺寸

    • 手机、平板、电视
    • 横竖屏切换
  2. 分辨率

    • 多种分辨率
    • 宽高比不同
  3. 密度

    • 多种密度
    • 像素密度不同

22.2 屏幕尺寸的单位有哪些?

答案:

屏幕尺寸单位包括dpsppx等。

单位说明:

  • dp(dip):密度无关像素,用于布局
  • sp:缩放无关像素,用于文字
  • px:像素,不推荐使用

22.3 dp、sp、px的区别是什么?

答案:

dp、sp、px在用途缩放上有区别。

区别:

单位用途缩放推荐
dp布局尺寸根据密度缩放推荐
sp文字大小根据密度和字体缩放推荐
px像素不缩放不推荐

使用建议:

  • 布局使用dp
  • 文字使用sp
  • 避免使用px

22.4 屏幕密度如何适配?

答案:

屏幕密度适配通过密度限定符多套资源实现。

适配方式:

res/
  drawable-mdpi/icon.png (160dpi)
  drawable-hdpi/icon.png (240dpi)
  drawable-xhdpi/icon.png (320dpi)
  drawable-xxhdpi/icon.png (480dpi)
  drawable-xxxhdpi/icon.png (640dpi)

系统自动选择:

  • 根据设备密度选择
  • 自动缩放适配

22.5 布局适配的方法有哪些?

答案:

布局适配方法包括约束布局权重布局多布局文件等。

适配方法:

  1. ConstraintLayout

    • 约束布局
    • 自适应不同屏幕
  2. 权重布局

    • LinearLayout权重
    • 按比例分配
  3. 多布局文件

    • layout-small/
    • layout-large/
    • layout-xlarge/

22.6 图片适配的方法有哪些?

答案:

图片适配通过多套资源矢量图实现。

适配方法:

  1. 多套资源

    • 不同密度提供不同图片
    • 系统自动选择
  2. 矢量图

    • 使用Vector Drawable
    • 无损缩放
  3. 9-patch

    • 可拉伸图片
    • 适配不同尺寸

22.7 文字适配的方法有哪些?

答案:

文字适配通过sp单位多语言资源实现。

适配方法:

  1. 使用sp单位

    • 根据系统字体缩放
    • 自适应
  2. 多语言资源

    • values-zh/
    • values-en/

22.8 今日头条适配方案是什么?

答案:

今日头条适配方案通过修改density实现屏幕适配。

原理:

  • 修改系统density值
  • 统一设计稿尺寸
  • 自动适配不同屏幕

实现:

// 在Application中设置
float designWidth = 375f; // 设计稿宽度
float screenWidth = getResources().getDisplayMetrics().widthPixels;
float density = screenWidth / designWidth;
getResources().getDisplayMetrics().density = density;

注意事项:

  • 可能影响第三方库
  • 需要充分测试

22.9 资源的多分辨率如何适配?

答案:

多分辨率适配通过密度限定符实现。

适配方式:

res/
  drawable-mdpi/
  drawable-hdpi/
  drawable-xhdpi/
  drawable-xxhdpi/

系统自动选择:

  • 根据设备密度
  • 自动选择合适资源

22.10 资源的多语言如何实现?

答案:

多语言通过语言限定符实现。

实现方式:

res/
  values/strings.xml (默认)
  values-zh/strings.xml (中文)
  values-en/strings.xml (英文)

使用:

String text = getString(R.string.app_name);
// 系统根据语言自动选择

22.11 资源的动态加载如何实现?

答案:

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

实现方式:

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

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

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

答案:

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

优化策略:

  1. 图片优化

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

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

    • 按需加载资源
    • 使用ViewStub

22.13 屏幕适配的最佳实践有哪些?

答案:

屏幕适配最佳实践包括使用dp/sp多套资源测试验证等。

最佳实践:

  1. 使用dp/sp

    • 布局使用dp
    • 文字使用sp
  2. 多套资源

    • 提供多密度资源
    • 提供多语言资源
  3. 测试验证

    • 在不同设备测试
    • 覆盖主要屏幕尺寸

22.14 屏幕适配的测试如何进行?

答案:

屏幕适配测试通过多设备测试模拟器测试等方式。

测试方式:

  1. 多设备测试

    • 使用不同尺寸设备
    • 覆盖主要屏幕
  2. 模拟器测试

    • 创建不同配置模拟器
    • 自动化测试

22.15 屏幕适配的常见问题有哪些?

答案:

屏幕适配常见问题包括布局错乱图片模糊文字显示异常等。

常见问题:

  1. 布局错乱

    • 使用固定尺寸
    • 未考虑不同屏幕
  2. 图片模糊

    • 未提供高密度资源
    • 使用低分辨率图片
  3. 文字显示异常

    • 使用px单位
    • 未考虑字体缩放

第二十三章:主题和样式(10 题)

23.1 Theme和Style的区别是什么?

答案:

Theme和Style在作用范围应用方式上有区别。

主要区别:

特性ThemeStyle
作用范围整个应用或Activity单个View
应用方式AndroidManifest或代码XML属性
用途全局样式局部样式

Theme:

  • 应用于整个应用或Activity
  • 定义全局样式

Style:

  • 应用于单个View
  • 定义局部样式

23.2 如何定义Style?

答案:

Style在values/styles.xml中定义。

定义方式:

<style name="MyStyle">
    <item name="android:textSize">16sp</item>
    <item name="android:textColor">#000000</item>
</style>

继承:

<style name="MyStyle.Bold">
    <item name="android:textStyle">bold</item>
</style>

23.3 如何定义Theme?

答案:

Theme在values/themes.xml中定义。

定义方式:

<style name="MyTheme" parent="Theme.AppCompat.Light">
    <item name="colorPrimary">#3F51B5</item>
    <item name="colorPrimaryDark">#303F9F</item>
    <item name="colorAccent">#FF4081</item>
</style>

应用:

<application
    android:theme="@style/MyTheme">

23.4 Style的继承如何实现?

答案:

Style继承通过parent属性命名约定实现。

继承方式:

<!-- 方式1:parent属性 -->
<style name="MyStyle" parent="ParentStyle">
    <item name="android:textSize">16sp</item>
</style>

<!-- 方式2:命名约定 -->
<style name="ParentStyle">
    <item name="android:textColor">#000000</item>
</style>

<style name="ParentStyle.Bold">
    <item name="android:textStyle">bold</item>
</style>

23.5 如何应用Theme?

答案:

Theme在AndroidManifest.xml代码中应用。

应用方式:

<!-- 全局应用 -->
<application
    android:theme="@style/MyTheme">

<!-- Activity应用 -->
<activity
    android:theme="@style/MyTheme">
// 代码应用
setTheme(R.style.MyTheme);

23.6 如何应用Style?

答案:

Style在XML布局中应用。

应用方式:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    style="@style/MyStyle" />

23.7 主题和样式的优先级是什么?

答案:

优先级:直接设置的属性 > Style > Theme > 系统默认

优先级顺序:

  1. 直接设置的属性(最高)
  2. Style中定义的属性
  3. Theme中定义的属性
  4. 系统默认值(最低)

23.8 主题和样式的覆盖如何实现?

答案:

主题和样式覆盖通过重新定义属性实现。

覆盖方式:

<!-- 父样式 -->
<style name="ParentStyle">
    <item name="android:textColor">#000000</item>
</style>

<!-- 子样式覆盖 -->
<style name="ChildStyle" parent="ParentStyle">
    <item name="android:textColor">#FF0000</item>
</style>

23.9 主题和样式的最佳实践有哪些?

答案:

主题和样式最佳实践包括合理组织继承使用统一管理等。

最佳实践:

  1. 合理组织

    • 按功能分组
    • 使用命名规范
  2. 继承使用

    • 使用继承减少重复
    • 建立样式体系
  3. 统一管理

    • 统一颜色、尺寸
    • 使用资源引用

23.10 主题和样式的常见问题有哪些?

答案:

主题和样式常见问题包括样式不生效继承错误优先级混乱等。

常见问题:

  1. 样式不生效

    • 检查属性名称
    • 检查应用方式
  2. 继承错误

    • 检查parent属性
    • 检查命名约定
  3. 优先级混乱

    • 理解优先级规则
    • 合理使用覆盖

第二十四章:国际化(8 题)

24.1 国际化(i18n)是什么?

答案:

国际化(i18n)是使应用支持多语言的过程。

国际化的定义:

  • 支持多语言
  • 适配不同地区
  • 提升用户体验

作用:

  • 扩大用户群体
  • 提升用户体验
  • 支持全球市场

24.2 国际化的实现方式有哪些?

答案:

国际化通过多语言资源实现。

实现方式:

res/
  values/strings.xml (默认)
  values-zh/strings.xml (中文)
  values-en/strings.xml (英文)
  values-ja/strings.xml (日文)

系统自动选择:

  • 根据系统语言
  • 自动选择资源

24.3 字符串资源的国际化如何实现?

答案:

字符串资源国际化通过多语言资源文件实现。

实现方式:

res/
  values/strings.xml (默认,英文)
  values-zh/strings.xml (中文)
  values-zh-rCN/strings.xml (简体中文)
  values-zh-rTW/strings.xml (繁体中文)
  values-en/strings.xml (英文)
  values-ja/strings.xml (日文)

资源内容:

<!-- values/strings.xml -->
<string name="app_name">My App</string>
<string name="welcome">Welcome</string>

<!-- values-zh/strings.xml -->
<string name="app_name">我的应用</string>
<string name="welcome">欢迎</string>

使用:

String appName = getString(R.string.app_name);
// 系统根据语言自动选择

24.4 图片资源的国际化如何实现?

答案:

图片资源国际化通过多语言资源目录实现。

实现方式:

res/
  drawable/icon.png (默认)
  drawable-zh/icon.png (中文图标)
  drawable-en/icon.png (英文图标)

注意事项:

  • 图片资源国际化较少使用
  • 通常使用字符串资源
  • 特殊情况下使用多套图片

24.5 国际化的最佳实践有哪些?

答案:

国际化最佳实践包括完整翻译测试验证文化适配等。

最佳实践:

  1. 完整翻译

    • 所有字符串翻译
    • 提供默认资源
  2. 测试验证

    • 在不同语言测试
    • 检查显示效果
  3. 文化适配

    • 考虑文化差异
    • 适配日期、数字格式

24.6 国际化的测试如何进行?

答案:

国际化测试通过切换语言多设备测试自动化测试等方式。

测试方式:

  1. 切换语言

    • 在设置中切换语言
    • 重启应用检查显示
    • 验证所有字符串
  2. 多设备测试

    • 在不同设备测试
    • 覆盖主要语言
    • 检查布局适配
  3. 自动化测试

    • 使用测试框架
    • 自动化切换语言
    • 验证显示内容

测试要点:

  • 所有字符串是否翻译
  • 布局是否适配
  • 特殊字符是否正常显示
  • 日期、数字格式是否正确

24.7 国际化的常见问题有哪些?

答案:

国际化常见问题包括翻译缺失布局错乱字符编码格式问题等。

常见问题:

  1. 翻译缺失

    • 某些语言未翻译
    • 使用默认资源
    • 解决:提供完整翻译
  2. 布局错乱

    • 文字长度不同导致布局错乱
    • 某些语言文字过长
    • 解决:使用弹性布局,考虑文字长度
  3. 字符编码

    • 编码问题
    • 特殊字符显示异常
    • 解决:使用UTF-8编码
  4. 格式问题

    • 日期格式不同
    • 数字格式不同
    • 解决:使用系统格式化API
  5. RTL语言

    • 从右到左语言适配
    • 布局镜像问题
    • 解决:使用RTL支持

24.8 国际化的性能优化有哪些?

答案:

国际化性能优化包括资源优化加载优化缓存优化等。

优化策略:

  1. 资源优化

    • 只加载需要的语言资源
    • 避免加载所有语言
    • 使用资源压缩
  2. 加载优化

    • 延迟加载非关键资源
    • 使用资源缓存
    • 优化资源查找
  3. 缓存优化

    • 缓存翻译结果
    • 避免重复查找
    • 使用内存缓存

注意事项:

  • 平衡性能和用户体验
  • 避免过度优化
  • 测试不同语言性能