第十八章:Notification 通知系统(12 题)
18.1 Notification是什么?它的作用是什么?
答案:
Notification是Android的通知组件,用于在状态栏显示通知信息。
Notification的定义:
- 系统级UI组件
- 在状态栏显示
- 用户可以点击查看
主要作用:
-
信息提醒
- 显示应用信息
- 提醒用户关注
-
后台通知
- 应用在后台时通知
- 保持用户连接
-
用户交互
- 点击通知打开应用
- 执行操作
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重要性设置。
设置方式:
- Android 7.1及以下
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
- 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取消可以通过自动取消或手动取消。
取消方式:
- 自动取消
builder.setAutoCancel(true); // 点击后自动取消
- 手动取消
NotificationManager manager = getSystemService(NotificationManager.class);
manager.cancel(notificationId); // 取消指定通知
manager.cancelAll(); // 取消所有通知
- 程序化取消
// 在通知中设置取消操作
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最佳实践包括合理使用、用户体验、性能优化等。
最佳实践:
-
合理使用
- 不要过度通知
- 提供关闭选项
-
用户体验
- 清晰的标题和内容
- 合适的优先级
- 提供操作按钮
-
性能优化
- 及时取消通知
- 避免通知堆积
-
适配新版本
- 创建通知渠道
- 适配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组件
- 覆盖在当前界面之上
- 用户必须处理才能继续
主要作用:
-
信息提示
- 显示重要信息
- 提示用户操作
-
用户交互
- 确认操作
- 输入信息
- 选择选项
-
临时界面
- 显示临时内容
- 不占用Activity空间
19.2 Dialog的类型有哪些?
答案:
Dialog主要有AlertDialog、ProgressDialog、自定义Dialog等类型。
Dialog类型:
-
AlertDialog
- 标准对话框
- 最常用
-
ProgressDialog
- 进度对话框
- 已废弃,推荐使用ProgressBar
-
DatePickerDialog
- 日期选择对话框
-
TimePickerDialog
- 时间选择对话框
-
自定义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生命周期包括创建、显示、隐藏、销毁等。
生命周期:
- 创建:new Dialog()或onCreateDialog()
- 显示:show()
- 隐藏:dismiss()
- 销毁:系统回收
DialogFragment生命周期:
- onCreate()
- onCreateDialog()
- onStart()
- onResume()
- onPause()
- onStop()
- onDestroy()
注意事项:
- DialogFragment生命周期更好
- 推荐使用DialogFragment
19.9 Dialog的内存泄漏如何避免?
答案:
Dialog内存泄漏通过及时dismiss()和使用DialogFragment避免。
避免方法:
- 及时dismiss()
@Override
protected void onDestroy() {
super.onDestroy();
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
-
使用DialogFragment
- DialogFragment自动管理生命周期
- 配置变更时自动处理
-
避免持有Context
- 使用Application Context
- 避免静态引用
19.10 Dialog的最佳实践有哪些?
答案:
Dialog最佳实践包括使用DialogFragment、及时dismiss()、用户体验等。
最佳实践:
-
使用DialogFragment
- 更好的生命周期管理
- 处理配置变更
-
及时dismiss()
- 避免内存泄漏
- 注意生命周期
-
用户体验
- 清晰的标题和内容
- 合理的按钮布局
- 提供取消选项
-
性能优化
- 避免频繁创建
- 复用Dialog
第二十章:Menu 菜单系统(8 题)
20.1 Menu是什么?它的作用是什么?
答案:
Menu是菜单组件,用于提供操作选项。
Menu的定义:
- 操作选项集合
- 用户交互入口
- 组织应用功能
主要作用:
-
功能入口
- 提供操作选项
- 组织应用功能
-
用户交互
- 点击执行操作
- 简化操作流程
-
界面优化
- 节省屏幕空间
- 保持界面简洁
20.2 Menu的类型有哪些?
答案:
Menu主要有OptionsMenu、ContextMenu、PopupMenu等类型。
Menu类型:
-
OptionsMenu
- 选项菜单
- 在ActionBar显示
-
ContextMenu
- 上下文菜单
- 长按显示
-
PopupMenu
- 弹出菜单
- 在指定位置显示
-
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最佳实践包括合理组织、用户体验、性能优化等。
最佳实践:
-
合理组织
- 按功能分组
- 使用子菜单
-
用户体验
- 清晰的菜单项
- 合理的图标
- 常用项显示在ActionBar
-
性能优化
- 使用XML定义
- 避免频繁创建
第二十一章:权限系统(18 题)
21.1 Android的权限类型有哪些?
答案:
Android权限分为普通权限和危险权限。
权限类型:
-
普通权限(Normal)
- 自动授予
- 不影响用户隐私
-
危险权限(Dangerous)
- 需要用户授权
- 影响用户隐私
- Android 6.0+需要运行时申请
权限组:
- 位置、相机、存储、电话等
- 同一组权限一次申请
21.2 普通权限和危险权限的区别是什么?
答案:
普通权限和危险权限在申请方式和用户影响上有区别。
主要区别:
| 特性 | 普通权限 | 危险权限 |
|---|---|---|
| 申请方式 | 自动授予 | 需要用户授权 |
| 用户影响 | 无影响 | 影响隐私 |
| 运行时申请 | 不需要 | Android 6.0+需要 |
普通权限示例:
- INTERNET
- ACCESS_NETWORK_STATE
- VIBRATE
危险权限示例:
- CAMERA
- READ_EXTERNAL_STORAGE
- ACCESS_FINE_LOCATION
21.3 权限的申请方式有哪些?
答案:
权限申请方式:静态声明和运行时申请。
申请方式:
- 静态声明(AndroidManifest.xml)
<uses-permission android:name="android.permission.CAMERA" />
- 运行时申请(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+引入的权限模型,危险权限需要运行时申请。
特点:
- 危险权限需要运行时申请
- 用户可以随时撤销
- 提升用户控制权
申请流程:
- 检查权限
- 未授予则申请
- 处理申请结果
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 运行时权限的最佳实践有哪些?
答案:
运行时权限最佳实践包括合理申请、用户体验、错误处理等。
最佳实践:
-
合理申请
- 需要时再申请
- 说明申请原因
-
用户体验
- 清晰的说明
- 友好的提示
-
错误处理
- 处理拒绝情况
- 提供降级方案
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 权限的安全问题如何避免?
答案:
权限安全问题通过最小权限原则、权限验证等避免。
安全措施:
-
最小权限原则
- 只申请必要权限
- 避免过度申请
-
权限验证
- 使用前检查权限
- 处理权限变化
-
数据保护
- 加密敏感数据
- 安全存储
21.12 权限的测试如何进行?
答案:
权限测试包括授予测试、拒绝测试、撤销测试等。
测试场景:
- 首次申请
- 用户拒绝
- 用户永久拒绝
- 权限被撤销
- 权限重新授予
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 权限的最佳实践总结
答案:
权限最佳实践总结:
- 最小权限原则
- 合理申请时机
- 友好的用户体验
- 完善的错误处理
- 充分的测试
第二十二章:屏幕适配和资源(15 题)
22.1 Android屏幕适配的挑战是什么?
答案:
Android屏幕适配挑战包括屏幕尺寸多样、分辨率不同、密度不同等。
挑战:
-
屏幕尺寸
- 手机、平板、电视
- 横竖屏切换
-
分辨率
- 多种分辨率
- 宽高比不同
-
密度
- 多种密度
- 像素密度不同
22.2 屏幕尺寸的单位有哪些?
答案:
屏幕尺寸单位包括dp、sp、px等。
单位说明:
- 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 布局适配的方法有哪些?
答案:
布局适配方法包括约束布局、权重布局、多布局文件等。
适配方法:
-
ConstraintLayout
- 约束布局
- 自适应不同屏幕
-
权重布局
- LinearLayout权重
- 按比例分配
-
多布局文件
- layout-small/
- layout-large/
- layout-xlarge/
22.6 图片适配的方法有哪些?
答案:
图片适配通过多套资源和矢量图实现。
适配方法:
-
多套资源
- 不同密度提供不同图片
- 系统自动选择
-
矢量图
- 使用Vector Drawable
- 无损缩放
-
9-patch
- 可拉伸图片
- 适配不同尺寸
22.7 文字适配的方法有哪些?
答案:
文字适配通过sp单位和多语言资源实现。
适配方法:
-
使用sp单位
- 根据系统字体缩放
- 自适应
-
多语言资源
- 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 资源的动态加载如何实现?
答案:
资源动态加载通过AssetManager或Resources实现。
实现方式:
AssetManager assetManager = getAssets();
InputStream is = assetManager.open("file.txt");
Resources resources = getResources();
Drawable drawable = resources.getDrawable(R.drawable.icon);
22.12 资源的性能优化有哪些?
答案:
资源性能优化包括图片优化、资源压缩、延迟加载等。
优化策略:
-
图片优化
- 使用WebP格式
- 压缩图片大小
-
资源压缩
- 使用ProGuard
- 移除未使用资源
-
延迟加载
- 按需加载资源
- 使用ViewStub
22.13 屏幕适配的最佳实践有哪些?
答案:
屏幕适配最佳实践包括使用dp/sp、多套资源、测试验证等。
最佳实践:
-
使用dp/sp
- 布局使用dp
- 文字使用sp
-
多套资源
- 提供多密度资源
- 提供多语言资源
-
测试验证
- 在不同设备测试
- 覆盖主要屏幕尺寸
22.14 屏幕适配的测试如何进行?
答案:
屏幕适配测试通过多设备测试、模拟器测试等方式。
测试方式:
-
多设备测试
- 使用不同尺寸设备
- 覆盖主要屏幕
-
模拟器测试
- 创建不同配置模拟器
- 自动化测试
22.15 屏幕适配的常见问题有哪些?
答案:
屏幕适配常见问题包括布局错乱、图片模糊、文字显示异常等。
常见问题:
-
布局错乱
- 使用固定尺寸
- 未考虑不同屏幕
-
图片模糊
- 未提供高密度资源
- 使用低分辨率图片
-
文字显示异常
- 使用px单位
- 未考虑字体缩放
第二十三章:主题和样式(10 题)
23.1 Theme和Style的区别是什么?
答案:
Theme和Style在作用范围和应用方式上有区别。
主要区别:
| 特性 | Theme | Style |
|---|---|---|
| 作用范围 | 整个应用或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 > 系统默认。
优先级顺序:
- 直接设置的属性(最高)
- Style中定义的属性
- Theme中定义的属性
- 系统默认值(最低)
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 主题和样式的最佳实践有哪些?
答案:
主题和样式最佳实践包括合理组织、继承使用、统一管理等。
最佳实践:
-
合理组织
- 按功能分组
- 使用命名规范
-
继承使用
- 使用继承减少重复
- 建立样式体系
-
统一管理
- 统一颜色、尺寸
- 使用资源引用
23.10 主题和样式的常见问题有哪些?
答案:
主题和样式常见问题包括样式不生效、继承错误、优先级混乱等。
常见问题:
-
样式不生效
- 检查属性名称
- 检查应用方式
-
继承错误
- 检查parent属性
- 检查命名约定
-
优先级混乱
- 理解优先级规则
- 合理使用覆盖
第二十四章:国际化(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 国际化的最佳实践有哪些?
答案:
国际化最佳实践包括完整翻译、测试验证、文化适配等。
最佳实践:
-
完整翻译
- 所有字符串翻译
- 提供默认资源
-
测试验证
- 在不同语言测试
- 检查显示效果
-
文化适配
- 考虑文化差异
- 适配日期、数字格式
24.6 国际化的测试如何进行?
答案:
国际化测试通过切换语言、多设备测试、自动化测试等方式。
测试方式:
-
切换语言
- 在设置中切换语言
- 重启应用检查显示
- 验证所有字符串
-
多设备测试
- 在不同设备测试
- 覆盖主要语言
- 检查布局适配
-
自动化测试
- 使用测试框架
- 自动化切换语言
- 验证显示内容
测试要点:
- 所有字符串是否翻译
- 布局是否适配
- 特殊字符是否正常显示
- 日期、数字格式是否正确
24.7 国际化的常见问题有哪些?
答案:
国际化常见问题包括翻译缺失、布局错乱、字符编码、格式问题等。
常见问题:
-
翻译缺失
- 某些语言未翻译
- 使用默认资源
- 解决:提供完整翻译
-
布局错乱
- 文字长度不同导致布局错乱
- 某些语言文字过长
- 解决:使用弹性布局,考虑文字长度
-
字符编码
- 编码问题
- 特殊字符显示异常
- 解决:使用UTF-8编码
-
格式问题
- 日期格式不同
- 数字格式不同
- 解决:使用系统格式化API
-
RTL语言
- 从右到左语言适配
- 布局镜像问题
- 解决:使用RTL支持
24.8 国际化的性能优化有哪些?
答案:
国际化性能优化包括资源优化、加载优化、缓存优化等。
优化策略:
-
资源优化
- 只加载需要的语言资源
- 避免加载所有语言
- 使用资源压缩
-
加载优化
- 延迟加载非关键资源
- 使用资源缓存
- 优化资源查找
-
缓存优化
- 缓存翻译结果
- 避免重复查找
- 使用内存缓存
注意事项:
- 平衡性能和用户体验
- 避免过度优化
- 测试不同语言性能