視頻學習教程
視頻鏈接: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);
<activityandroid: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
中使用常量形式
<activityandroid: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