Android基础教程 - 学习完成记录
视频学习教程
视频链接:2022 最新 Android 基础教程,从开发入门到项目实战,看它就够了,更新中_哔哩哔哩_bilibili
学习下来,有遇到很多问题,在 chatgpt、claude 和 Android Studio 插件通义千问的帮助下,一一解决。
目前还是有很多不懂的,虽然学了一遍,敲了一遍,但是 Android 体系还是太多,需要不断地学习。
Gitee 地址
代码均可以跑通,视频中项目实战均实现。
Magnolia/AndroidLearning
如果可以,帮点个小星星,谢谢~
下面是一些记录。
资源IDs 变成 non-final
Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements.
在 Android Gradle Plugin 8.0 中,资源 ID 默认变为 non-final,意思是它们不再是编译时常量,而是动态生成的。这是因为 Android 的资源 ID 现在默认使用了新的构建优化方式,使得每次构建时资源 ID 可能发生变化,从而减少了重编译时间,但也带来了以下变化:
解释 non-final
- 原本的 final 行为:在 Android Gradle Plugin 8.0 之前,资源 ID(如
R.id.example_button
)被编译成final
常量(不可变的静态值),因此可以直接用于switch
语句,因为编译器在编译时可以确定它们的值。 - non-final 意味着动态生成:从 8.0 开始,资源 ID 不再是
final
的。它们的值在每次构建时可能会动态生成,无法在编译时确定。因此,在switch
语句中使用这些非固定值会导致编译错误,因为switch
语句要求常量。
影响和替代方案
由于资源 ID 变为 non-final,在 Android Gradle Plugin 8.0+ 中,不能在 switch
语句中使用它们。可以使用 if-else
结构来替代 switch
。
变量声明为 final
将变量声明为 final 有以下好处:
- 不可变性:确保该变量在初始化后不能被修改,提高代码的安全性和可预测性。
- 线程安全:在多线程环境中,final 变量可以避免多个线程同时修改同一个变量带来的问题。
- 优化:编译器可以对 final 变量进行一些优化,提高性能。
private String mRequest = "你好,在吗?";
// 可以写成:
private final String mRequest = "你好,在吗?";
override 注解
@Override
注解在 Java 中用于表示一个方法是在重写父类或实现接口中的方法。使用 @Override 有以下几个好处:
- 提高可读性:明确告知其他开发者这个方法是一个重写的方法。
- 编译器检查:如果方法签名与父类或接口中的方法不匹配,编译器会报错,帮助开发者发现潜在的错误。
如果不加 @Override
注解,代码仍然可以正常编译和运行,但会有以下问题:
- 缺乏明确性:其他开发者阅读代码时可能不清楚这个方法是否是重写的。
- 潜在错误:如果方法签名写错了(例如拼写错误或参数类型不匹配),编译器不会报错,但方法不会被正确调用,导致难以调试的问题。
.9.png
在 Android 开发中,.9.png
是一种特殊的图片格式,称为 Nine-Patch (九宫格) 图片。这种格式的图片在 .png
图片基础上做了特殊处理,主要用于实现拉伸和缩放效果,适应不同尺寸的屏幕和控件。
特点与用途
- 灵活拉伸:Nine-Patch 图片可以指定哪些区域可被拉伸,哪些区域保持不变。这对于按钮、对话框背景等需要根据内容大小动态调整的控件非常有用。
- 边界标记:Nine-Patch 图片的边界上有一像素宽的黑色线条,Android 会根据这些标记确定哪些部分可以拉伸,哪些部分保持原样。具体来说:
- 左侧和上侧边界:定义图片可以被拉伸的区域。
- 右侧和下侧边界:定义内容显示区域。
- 高效适配:Nine-Patch 图片在适应不同分辨率和屏幕尺寸时,不会失真或模糊,提供较好的视觉效果。适合用于各种弹窗、输入框背景、气泡等需要拉伸的图形背景。
示例应用
- 按钮背景、输入框背景、对话框背景等需要根据内容大小动态变化的 UI 元素。
创建方法
Nine-Patch 图片可以通过 Android Studio 的 Draw 9-Patch 工具制作,或直接在图片上手动添加一像素的黑色边界。Android 会根据这些边界像素的标记进行渲染处理。
Nine-Patch 图片通过简单的标记,能以更小的资源量适配不同屏幕,是一种高效的图片处理方式。
复合按钮
由复合按钮 CompoundButton
派生而来的控件包括:CheckBox
,RadioButton
,Switch
。
导入 Nullable
import androidx.annotation.Nullable;
作用是导入 Nullable
注解。这个注解来自 AndroidX 库,用于标记方法参数、返回值或字段,表示它们可以为 null
。
- 代码可读性:提高代码的可读性和维护性,明确指出某个变量或参数可以为
null
。 - 静态分析:帮助 IDE 和静态代码分析工具进行更准确的检查,减少潜在的空指针异常。
示例
@Nullable
public String getName() {
return name;
}
在这个示例中,@Nullable
注解表明 getName
方法的返回值可以为 null
。
Boolean 与 boolean
Boolean married = ck_married.isChecked();
boolean married = ck_married.isChecked(); 这两种写法有什么区别?
-
Boolean married = ck_married.isChecked();
- 类型:
Boolean
是boolean
的包装类。 - 值:可以为
true
、false
或null
。 - 使用场景:适用于需要表示
null
值的情况,例如在集合中存储布尔值或处理可能为null
的返回值。
- 类型:
-
boolean married = ck_married.isChecked();
- 类型:
boolean
是基本数据类型。 - 值:只能为
true
或false
。 - 使用场景:适用于不需要表示
null
值的情况,性能更好,占用内存更少。
- 类型:
在代码中:
Boolean married = ck_married.isChecked();
:这里married
变量可以为true
、false
或null
。但由于isChecked()
方法总是返回true
或false
,所以married
不会为null
。boolean married = ck_married.isChecked();
:这里married
变量只能为true
或false
,更符合实际需求,因为isChecked()
方法不会返回null
。
推荐做法
在大多数情况下,特别是当你确定变量不会为 null
时,推荐使用基本数据类型 boolean
,因为它更高效且占用内存更少。
boolean married = ck_married.isChecked();
总结
Boolean
:包装类,可以为null
。boolean
:基本数据类型,不能为null
。- 推荐:在不需要表示
null
值的情况下,使用boolean
。
参数化查询和静态查询
参数化查询
String sql = "select * from " + TABLE_NAME + " where phone=? and remember=1";
Cursor cursor = mRDB.rawQuery(sql, new String[]{phone});
-
SQL 查询:
select * from <TABLE_NAME> where phone=? and remember=1
:这条 SQL 语句从表中选择所有列,条件是phone
等于某个值且remember
等于 1。?
是一个占位符,用于防止 SQL 注入攻击。
-
参数化查询:
new String[]{phone}
:这是一个字符串数组,包含一个元素phone
。这个数组中的值会替换 SQL 语句中的?
占位符。- 例如,如果
phone
的值是"1234567890"
,那么最终的 SQL 语句会变成select * from <TABLE_NAME> where phone='1234567890' and remember=1
。
静态查询
String sql = "select * from " + TABLE_NAME + " where remember=1 order by _id desc limit 1";
Cursor cursor = mRDB.rawQuery(sql, null);
-
SQL 查询:
select * from <TABLE_NAME> where remember=1 order by _id desc limit 1
:这条 SQL 语句从表中选择所有列,条件是remember
等于 1,并按_id
列降序排列,只取第一行。- 没有使用占位符
?
,因为查询条件中没有动态参数。
-
参数化查询:
null
:表示没有参数需要传递给 SQL 语句。
为什么前者多了 new String[]{phone}
- 参数化查询:
new String[]{phone}
用于传递动态参数phone
给 SQL 语句中的占位符?
。这样做可以防止 SQL 注入攻击,提高安全性。 - 静态查询:第二段代码没有动态参数,因此不需要传递参数数组,直接使用
null
即可。
总结
- 第一段代码:使用参数化查询,动态传递
phone
参数。 - 第二段代码:使用静态查询,没有动态参数。
android.intent.action.VIEW
"android.intent.action.VIEW"
或者 "android.intent.action.ACTION_VIEW"
都可以
Intent intent = new Intent(Intent.ACTION_VIEW);
<activity
android:name=".ThirdActivity"
android:exported="false"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.ACTION_VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
简要解释原因
在 Android 中,Intent
的 action
属性用于指定操作类型。"android.intent.action.VIEW"
和 "android.intent.action.ACTION_VIEW"
实际上是同一个常量的不同表示形式。
"android.intent.action.VIEW"
:这是直接使用字符串形式的 action 名称。"android.intent.action.ACTION_VIEW"
:这是通过Intent
类中的静态常量Intent.ACTION_VIEW
来引用的。
这两种方式在功能上是等价的,因为它们都指向同一个标准的 Android 操作名称 "android.intent.action.VIEW"
。
分点描述
-
字符串形式 vs 常量形式:
- 使用字符串形式(如
"android.intent.action.VIEW"
)时,代码更直观,但容易出现拼写错误。 - 使用常量形式(如
Intent.ACTION_VIEW
)时,代码更安全,编译器可以进行类型检查和自动补全,减少错误。
- 使用字符串形式(如
-
推荐使用常量形式:
- 使用常量形式可以提高代码的可读性和维护性。
- 编译器可以在编译时检查常量的有效性,避免运行时错误。
示例代码
在 AndroidManifest.xml
中使用常量形式
<activity
android:name=".ThirdActivity"
android:exported="false"
tools:ignore="AppLinkUrlError">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<!-- 或者使用 -->
<action android:name="android.intent.action.ACTION_VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
</intent-filter>
</activity>
在 Java 代码中使用常量形式
else if (v.getId() == R.id.btn_jump2web) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
虽然 "android.intent.action.VIEW"
和 "android.intent.action.ACTION_VIEW"
都可以正常工作,但建议使用常量形式 Intent.ACTION_VIEW
,以提高代码的安全性和可维护性。
Bug记录
购物车数目只更新一次
private void addToCart(int goodsId, String goodsName) {
int goodsCount = MyApplication.getInstance().goodsCount;
++goodsCount;
tv_count.setText(String.valueOf(goodsCount));
mDBHelper.insertCardInfo(goodsId);
ToastUtil.show(this, "已添加一件" + goodsName + "到购物车");
}
原因:仅对局部变量 goodsCount
进行自增操作,此时MyApplication.getInstance().goodsCount
的值并没有被修改,首次操作时, goodsCount 加一了,所以购物车数目会加一。但是,后面再添加就不会更新,因为每次获取的 goodsCount 都是一样的值,tv_count 更新也都是第一次更新的数字。
先对MyApplication.getInstance().goodsCount
进行自增操作,然后将自增后的结果赋值给goodsCount
,确保MyApplication.getInstance().goodsCount
和UI显示同步更新。
private void addToCart(int goodsId, String goodsName) {
int goodsCount = ++MyApplication.getInstance().goodsCount;
tv_count.setText(String.valueOf(goodsCount));
mDBHelper.insertCardInfo(goodsId);
ToastUtil.show(this, "已添加一件" + goodsName + "到购物车");
}
问题处理
Run后模拟器未启动app
可能的原因或者解决方法
-
模拟器是否多开,在另一个中启动
-
重启模拟器,清除模拟器数据(wipe data)后重启
-
adb devices 查看状态
如果是 unauthorized,尝试
adb kill-server, adb start-server
,设置cold boot
启动 -
查看 AndroidManifest.xml 是否配置正确
模拟器进程被占用
AVD Pixel_2_API_31 is already running. If that is not the case, delete the files at D:\software\Android.android\avd/Pixel_2_API_31.avd/*.lock and try again.
taskkill /F /IM qemu-system-* /IM emulator-* /IM adb.exe
del /F /Q D:\software\Android\.android\avd\Pixel_2_API_31.avd\*.lock
使用 procexp.exe 找到并结束进程。
记录
AndroidStudio 中, debug 运行代码,生成包 build/intermediates/apk/debug/chapter06-debug.apk