文章目录
-
- [一、Java 基础与进阶](#一、Java 基础与进阶)
-
- [1. 面向对象 (OOP)](#1. 面向对象 (OOP))
-
- [1.1 面向对象的三大特性](#1.1 面向对象的三大特性)
- [1.2 接口和抽象类](#1.2 接口和抽象类)
- [2. 集合框架 (Collections Framework)](#2. 集合框架 (Collections Framework))
- [3. 泛型 (Generics)](#3. 泛型 (Generics))
- [4. 异常处理 (Exception Handling)](#4. 异常处理 (Exception Handling))
- [二、Android 核心机制](#二、Android 核心机制)
-
- [1. 事件分发机制](#1. 事件分发机制)
- [2. 自定义 View](#2. 自定义 View)
- [3. 四大组件深度解析](#3. 四大组件深度解析)
- 三、性能优化
-
- [1. 内存优化](#1. 内存优化)
- [2. 布局优化](#2. 布局优化)
- [3. 卡顿优化](#3. 卡顿优化)
- 四、存储系统
-
- [1. SharedPreferences](#1. SharedPreferences)
- [2. 文件存储](#2. 文件存储)
- [3. SQLite 数据库](#3. SQLite 数据库)
- [4. Room 持久性库 (Room Persistence Library)](#4. Room 持久性库 (Room Persistence Library))
- 五、网络编程
-
- [1. HTTP/HTTPS 协议](#1. HTTP/HTTPS 协议)
- [2. 网络请求库](#2. 网络请求库)
- [3. 网络权限](#3. 网络权限)
- [4. 网络状态判断](#4. 网络状态判断)
- [六、Jetpack 组件详解](#六、Jetpack 组件详解)
-
- [1. ViewModel](#1. ViewModel)
- [2. LiveData](#2. LiveData)
- [3. ViewBinding 和 DataBinding](#3. ViewBinding 和 DataBinding)
- [4. Navigation](#4. Navigation)
- [5. WorkManager](#5. WorkManager)
- [七、Kotlin 语言特性在 Android 中的应用](#七、Kotlin 语言特性在 Android 中的应用)
-
- [1. 空安全 (Null Safety)](#1. 空安全 (Null Safety))
- [2. 扩展函数 (Extension Functions)](#2. 扩展函数 (Extension Functions))
- [3. 数据类 (Data Classes)](#3. 数据类 (Data Classes))
- [4. 伴生对象 (Companion Objects)](#4. 伴生对象 (Companion Objects))
- [5. 协程 (Coroutines)](#5. 协程 (Coroutines))
- [八、Jetpack Compose:现代 UI 开发范式](#八、Jetpack Compose:现代 UI 开发范式)
-
- [1. 核心理念:声明式 UI](#1. 核心理念:声明式 UI)
- [2. 核心概念](#2. 核心概念)
- [3. 布局与组件](#3. 布局与组件)
- [4. 与现有 View 系统互操作](#4. 与现有 View 系统互操作)
- [5. 优势与挑战](#5. 优势与挑战)
- 九、应用安全
-
- [1. 数据安全](#1. 数据安全)
- [2. 组件安全](#2. 组件安全)
- [3. 代码安全](#3. 代码安全)
- [4. 权限最佳实践](#4. 权限最佳实践)
- [5. WebView 安全](#5. WebView 安全)
- 十、发布上线与持续集成 (CI/CD)
-
- [1. 构建与签名](#1. 构建与签名)
- [2. Google Play 发布流程](#2. Google Play 发布流程)
- [3. 持续集成与持续部署 (CI/CD)](#3. 持续集成与持续部署 (CI/CD))
- [4. 应用内更新 (In-App Updates)](#4. 应用内更新 (In-App Updates))
- 十一、前沿技术展望
-
- [1. Kotlin Multiplatform (KMP)](#1. Kotlin Multiplatform (KMP))
- [2. 跨设备体验 (Cross-Device Experience)](#2. 跨设备体验 (Cross-Device Experience))
- [3. 人工智能与机器学习集成](#3. 人工智能与机器学习集成)
- [4. 64 位与 ABI 兼容性](#4. 64 位与 ABI 兼容性)
- [5. 隐私沙盒 (Privacy Sandbox)](#5. 隐私沙盒 (Privacy Sandbox))
- 结语
一、Java 基础与进阶
1. 面向对象 (OOP)
面向对象编程(OOP)是 Java 语言的基石,也是 Android 开发的构建范式。它通过封装、继承和多态三大特性,帮助我们构建出高内聚、低耦合、易于维护和扩展的应用。
1.1 面向对象的三大特性
**1. 封装 (Encapsulation)
概念
封装是将对象的属性(数据)和行为(方法)捆绑成一个独立的单元(即类),并对外部隐藏其内部实现的细节。通过访问控制修饰符(private, protected, public)来限制对内部成员的直接访问。通常,我们提供公共的 getter 和 setter 方法来安全地读取和修改私有属性。
作用
- 数据安全与完整性 :防止外部代码随意篡改对象的内部状态。例如,一个
BankAccount对象的余额不能被随意设为负数。 - 降低耦合度:使用者只依赖于类的公共接口,而无需关心其内部是如何工作的。这使得类的内部实现可以自由修改,只要接口不变,调用者代码就无需改动。
- 提高内聚性:将相关的数据和操作这些数据的方法组织在同一个类中,使类的职责更加单一和明确。
示例代码
java
// 一个封装良好的数据模型类
public class User {
// 私有属性,外部无法直接访问
private String name;
private int age;
private String email;
// 构造函数
public User(String name, int age, String email) {
setName(name); // 使用setter进行赋值,可以加入校验逻辑
setAge(age);
setEmail(email);
}
// Getter方法,提供对私有属性的只读访问
public String getName() {
return name;
}
// Setter方法,在赋值前可以进行数据校验
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Name cannot be null or empty");
}
this.name = name.trim();
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Age must be between 0 and 150");
}
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("Invalid email format");
}
this.email = email;
}
// 行为方法
public void introduce() {
System.out.println("Hello, I'm " + name + ", " + age + " years old.");
}
}
2. 继承 (Inheritance)
概念
继承是一种"is-a"关系,它允许一个类(子类/派生类)基于另一个类(父类/基类)来创建。子类会自动继承父类的所有非私有成员(属性和方法),并可以添加自己特有的成员,或者重写(Override)父类的方法以提供特定的实现。
作用
- 代码复用:避免在多个类中重复编写相同的代码。子类可以直接使用父类的功能。
- 建立层次结构 :更准确地对现实世界进行建模。例如,
Dog和Cat都是Animal的一种。 - 功能扩展与多态基础:子类可以在继承的基础上增加新功能,或改变父类的行为,为多态提供了前提。
示例代码
java
// 父类 (基类)
public class Animal {
protected String name; // 使用protected,子类可以访问
protected String species;
public Animal(String name, String species) {
this.name = name;
this.species = species;
}
// 具体方法,子类可以直接继承
public void sleep() {
System.out.println(name + " is sleeping.");
}
// 抽象方法,强制子类必须实现
public abstract void makeSound();
// 所有动物共有的行为
public void breathe() {
System.out.println(name + " is breathing.");
}
}
// 子类
public class Dog extends Animal {
private String breed; // 狗特有的属性
public Dog(String name, String breed) {
super(name, "Canine"); // 调用父类构造函数
this.breed = breed;
}
@Override // 重写父类的抽象方法
public void makeSound() {
System.out.println(name + " says: Woof! Woof!");
}
// 狗特有的行为
public void wagTail() {
System.out.println(name + " is wagging its tail.");
}
}
// 另一个子类
public class Cat extends Animal {
public Cat(String name) {
super(name, "Feline");
}
@Override
public void makeSound() {
System.out.println(name + " says: Meow!");
}
}
3. 多态 (Polymorphism)
概念
多态是指同一个操作作用于不同的对象,可以有不同的解释,并产生不同的执行结果。在 Java 中,主要体现为父类引用可以指向子类对象,并在运行时动态调用实际对象所属类的方法(动态绑定/后期绑定)。
作用
- 提高代码的灵活性和可扩展性 :可以编写出更通用的代码。例如,一个方法可以接收
Animal类型的参数,而实际传入Dog或Cat对象,方法内部会自动调用对应对象的makeSound()方法。 - 实现解耦:上层代码依赖于抽象(父类或接口),而不是具体的实现,使得模块间的依赖关系更加松散。
示例代码
java
public class Zoo {
// 这个方法体现了多态!它接受任何 Animal 的子类对象
public void makeAnimalsSound(Animal animal) {
animal.makeSound(); // 运行时决定调用哪个子类的makeSound方法
}
public static void main(String[] args) {
Zoo zoo = new Zoo();
Animal dog = new Dog("Buddy", "Golden Retriever");
Animal cat = new Cat("Whiskers");
zoo.makeAnimalsSound(dog); // 输出: Buddy says: Woof! Woof!
zoo.makeAnimalsSound(cat); // 输出: Whiskers says: Meow!
// sleep() 是继承的具体方法,也可以直接调用
dog.sleep(); // 输出: Buddy is sleeping.
cat.breathe(); // 输出: Whiskers is breathing.
}
}
4. 三大特性总结
| 特性 | 核心思想 | Android 引用举例 | 作用 |
|---|---|---|---|
| 封装 | 隐藏实现,暴露接口 | Activity 生命周期、自定义 View、数据模型类(如 User) |
保证数据安全、降低耦合、提高代码内聚性、易于维护 |
| 继承 | 代码复用,建立层次 | 创建 BaseActivity/BaseFragment、自定义 CustomView 继承 ViewGroup |
复用代码、扩展功能、建立清晰的类层次结构 |
| 多态 | 一个接口,多种实现 | View 事件分发、Adapter 模式(RecyclerView.Adapter)、回调接口、各种设计模式 |
提高代码灵活性、可扩展性,实现高内聚低耦合 |
这三大特性相辅相成,共同构成了 Android 应用健壮、灵活、可维护的代码基础。熟练掌握并灵活运用它们,是成为一名优秀 Android 开发者的必经之路。
1.2 接口和抽象类
接口(Interface)和抽象类(Abstract Class)都是用来定义抽象的"契约"或"模板",但它们的使用场景和设计意图有显著区别。
1. 抽象类 (Abstract Class)
概念
一个被 abstract 关键字修饰的类。它可以包含抽象方法(只有声明,没有实现)和具体方法(有实现的完整方法),也可以包含成员变量。
设计意图:"是什么" (IS-A)。它代表一个不完整的、通用的基类,用于捕捉子类的共同特征(属性和行为),并为子类提供部分默认实现。抽象类强调的是类的继承关系和代码复用。
示例代码
java
// 一个典型的Android BaseActivity抽象类
public abstract class BaseActivity extends AppCompatActivity {
private ProgressBar progressBar;
// 抽象方法,强制子类必须实现
protected abstract int getLayoutId();
protected abstract void initViews();
protected abstract void initData();
// 具体方法,提供通用功能
@Override
protected final void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutId()); // 子类提供布局ID
initViews(); // 子类初始化视图
initData(); // 子类加载数据
setupToolbar(); // 父类提供的默认功能
}
private void setupToolbar() {
// 通用的Toolbar设置逻辑
Toolbar toolbar = findViewById(R.id.toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
// 提供默认的进度条显示/隐藏方法
protected void showLoading() {
if (progressBar == null) {
progressBar = findViewById(R.id.progress_bar);
}
if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
}
protected void hideLoading() {
if (progressBar != null) progressBar.setVisibility(View.GONE);
}
}
// 具体的子类Activity
public class MainActivity extends BaseActivity {
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
@Override
protected void initViews() {
// 初始化MainActivity特有的视图
}
@Override
protected void initData() {
// 加载MainActivity的数据
}
}
抽象类的核心作用
- 代码复用 :为子类提供公共的属性和方法实现(如
setupToolbar())。 - 强制规范 :通过抽象方法强制要求子类必须实现某些核心功能(如
getLayoutId())。 - 定义模板:结合模板方法模式(Template Method Pattern),定义算法的骨架,将某些步骤延迟到子类中实现。
2. 接口 (Interface)
概念
一个被 interface 关键字修饰的完全抽象的"契约"。在 Java 8 之前,只能包含常量和抽象方法;Java 8 及以后,可以包含默认方法(default)和静态方法(static)。
设计意图:"能做什么" (CAN-DO)。它定义了一组行为规范,规定了实现该接口的类必须具备哪些能力,而不关心这些类的内部状态或具体实现。接口强调的是功能的定义和解耦。
示例代码
java
// 定义一个"可点击"的行为规范
public interface Clickable {
void onClick();
// Java 8 默认方法,提供可选的默认实现
default void onLongClick() {
System.out.println("Long click not handled.");
}
}
// 定义一个"可拖拽"的行为规范
public interface Draggable {
void onStartDrag();
void onDrag(float dx, float dy);
void onStopDrag();
}
// 一个按钮,它"能被点击"
public class Button implements Clickable {
private String text;
public Button(String text) {
this.text = text;
}
@Override
public void onClick() {
System.out.println("Button [" + text + "] was clicked!");
}
}
// 一个图块,它"能被点击"也"能被拖拽"
public class Tile implements Clickable, Draggable {
private float x, y;
@Override
public void onClick() {
System.out.println("Tile clicked at (" + x + ", " + y + ")");
}
@Override
public void onStartDrag() {
System.out.println("Tile drag started.");
}
@Override
public void onDrag(float dx, float dy) {
x += dx;
y += dy;
}
@Override
public void onStopDrag() {
System.out.println("Tile drag stopped at (" + x + ", " + y + ")");
}
}
// 使用多态
public class UIController {
private List<Clickable> clickableElements = new ArrayList<>();
public void addElement(Clickable element) {
clickableElements.add(element);
}
public void simulateClick() {
for (Clickable element : clickableElements) {
element.onClick(); // 无论是Button还是Tile,都能响应点击
}
}
}
接口的核心作用
- 实现"多继承" :Java 类不支持多继承,但一个类可以实现多个接口,从而获得多种能力(如
Tile同时实现了Clickable和Draggable)。 - 定义契约/规范:明确地规定了实现类必须提供的方法,是团队协作和模块化开发的基础。
- 解耦与依赖倒置 :客户端代码(如
UIController)依赖于接口Clickable,而不是具体的Button或Tile类,大大降低了耦合度。 - 支持多态性:可以通过接口类型的引用来调用不同实现类的对象。
- 便于测试与扩展:易于编写 Mock 对象进行单元测试;添加新功能时,可以定义新接口,而不影响现有代码。
3. 抽象类与接口对比总结
| 特性 | 接口 (Interface) | 抽象类 (Abstract Class) |
|---|---|---|
| 实现/继承 | 子类使用 implements 关键字实现接口,必须提供所有抽象方法的实现(除非子类也是抽象的)。 |
子类使用 extends 关键字继承抽象类。如果子类不是抽象的,则必须实现所有抽象方法。 |
| 构造器 | 不能有构造器。 | 可以有构造器,用于初始化抽象类的成员变量。 |
| 成员变量 | 只能是 public static final 的常量。 |
可以是各种类型的成员变量(private, protected, public, static 等)。 |
| 方法 | 在 Java 8 之前,只能有 public abstract 方法。之后可以有 default 和 static 方法。 |
可以有抽象方法和具体方法(任何访问修饰符)。 |
| 访问修饰符 | 接口方法默认是 public,不能使用其他修饰符。 |
抽象方法可以有 public, protected, default (包私有) 修饰符。 |
| main 方法 | 不能 有 main 方法,因此不能直接运行。 |
可以 有 main 方法,并且可以独立运行。 |
| 继承 | 一个类可以 implements 多个接口。 |
一个类只能 extends 一个抽象类。 |
| 速度 | 理论上,接口方法调用可能比抽象类稍慢,因为需要通过接口查找实现。 | 速度相对更快。 |
| 设计理念 | "能做什么" (CAN-DO) - 定义能力。 | "是什么" (IS-A) - 定义一种类型。 |
选择建议:
- 当需要为不相关的类提供公共功能时,使用接口。
- 当需要在几个紧密相关的类之间共享代码时,使用抽象类。
- 当你希望类能够继承多个行为规范时,必须使用接口。
- 在现代 Android 开发中,由于 Lambda 表达式和函数式接口的普及,接口的使用场景更加广泛。
2. 集合框架 (Collections Framework)
Java 集合框架提供了一套高性能、可复用的数据结构和算法,是 Android 开发中处理数据的核心工具。
2.1 主要接口与实现类
| 接口 | 常用实现类 | 特点与适用场景 | 时间复杂度 (平均) |
|---|---|---|---|
List (有序、可重复) |
ArrayList |
基于动态数组,随机访问快,尾部插入/删除快,中间插入/删除慢。适用于查询多、增删少的场景。 | get: O(1) add/remove: O(n) |
LinkedList |
基于双向链表,任意位置插入/删除快,随机访问慢。适用于频繁在任意位置增删的场景。 | get: O(n) add/remove: O(1) | |
Set (无序、唯一) |
HashSet |
基于 HashMap,通过 hashCode() 和 equals() 保证元素唯一性。性能高,但不保证顺序。 |
add/remove/contains: O(1) |
LinkedHashSet |
继承 HashSet,内部使用链表维护插入顺序。保证迭代顺序。 |
add/remove/contains: O(1) | |
TreeSet |
基于 TreeMap (红黑树),元素自动排序。适用于需要排序的场景。 |
add/remove/contains: O(log n) | |
Map (键值对) |
HashMap |
基于哈希表,允许 null 键和 null 值。性能最高,但不保证顺序。 |
get/put: O(1) |
LinkedHashMap |
继承 HashMap,维护插入顺序或访问顺序。可用于实现 LRU 缓存。 |
get/put: O(1) | |
TreeMap |
基于红黑树,按键的自然顺序或自定义比较器排序。 | get/put: O(log n) |
示例代码:集合的使用
java
import java.util.*;
public class CollectionExample {
public static void main(String[] args) {
// 1. List 的使用
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
System.out.println("Names: " + names); // [Alice, Bob, Charlie]
System.out.println("First name: " + names.get(0)); // Alice
// 2. Set 的使用 (去重)
Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice"); // 重复元素,会被忽略
System.out.println("Unique names: " + uniqueNames); // [Bob, Alice] (顺序不定)
// 3. Map 的使用 (键值对)
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Charlie", 92);
System.out.println("Alice's score: " + scores.get("Alice")); // 95
// 4. 遍历 Map
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
2.2 Android 中的集合最佳实践
-
选择合适的集合:根据数据的访问模式(查询、插入、删除)和是否需要排序、去重来选择最合适的集合。
-
初始化容量 :如果能预估集合大小,应在创建时指定初始容量,避免频繁的数组扩容(对
ArrayList,HashMap尤其重要)。javaList<User> users = new ArrayList<>(100); // 预估有100个用户 Map<String, Object> cache = new HashMap<>(32); // 预估缓存大小 -
使用不可变集合 :对于不需要修改的集合,使用
Collections.unmodifiableList()等方法创建不可变视图,提高安全性。 -
注意线程安全 :
ArrayList,HashMap等不是线程安全的。在多线程环境下,应使用CopyOnWriteArrayList、ConcurrentHashMap,或使用同步机制。
3. 泛型 (Generics)
泛型允许在定义类、接口和方法时使用类型参数,从而编写出更安全、更可重用的代码。
概念
泛型提供了编译时类型检查,并且无需进行显式的类型转换。
示例代码
java
// 一个泛型类
public class Box<T> { // T 是类型参数
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用泛型类
public class GenericExample {
public static void main(String[] args) {
// 创建一个只能装 String 的 Box
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent(); // 无需强制转换,类型安全
// 创建一个只能装 Integer 的 Box
Box<Integer> integerBox = new Box<>();
integerBox.setContent(123);
Integer number = integerBox.getContent(); // 无需强制转换
}
}
在 Android 中的应用
- 集合框架 :
List<String>,Map<String, User>等,避免了ClassCastException。 - 自定义 View:可以创建泛型的 Adapter 或 ViewHolder。
- 网络请求:Retrofit 等库广泛使用泛型来解析 JSON 响应。
通配符 (Wildcards)
?:无界通配符,表示任何类型。<? extends T>:上界通配符,表示T或T的子类型(只出不进,Producer)。<? super T>:下界通配符,表示T或T的父类型(只进不出,Consumer)。
java
// PECS 原则: Producer-Extends, Consumer-Super
public static void copy(List<? extends Number> src, List<? super Number> dest) {
for (Number number : src) {
dest.add(number); // dest 是消费者,使用 super
}
}
4. 异常处理 (Exception Handling)
Java 通过 try-catch-finally 机制来处理程序运行时的异常情况。
分类
- 检查型异常 (Checked Exception) :编译器强制要求处理,如
IOException。 - 非检查型异常 (Unchecked Exception) :包括
RuntimeException及其子类(如NullPointerException,IndexOutOfBoundsException),编译器不强制处理。 - 错误 (Error) :严重的系统级问题,如
OutOfMemoryError,通常无法恢复。
最佳实践
- 不要忽略异常:至少要打印日志。
- 具体化 catch 块 :优先捕获具体的异常类型,而不是笼统的
Exception。 - 资源管理 :使用
try-with-resources语句自动关闭实现了AutoCloseable接口的资源。
java
try (FileInputStream fis = new FileInputStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
} catch (IOException e) {
System.err.println("IO error: " + e.getMessage());
} // FileInputStream 和 BufferedReader 会自动关闭
二、Android 核心机制
1. 事件分发机制
Android 的事件分发机制是理解触摸交互的核心,它决定了触摸事件(如点击、滑动)如何从 Activity 传递到具体的 View。
1.1 核心方法
dispatchTouchEvent(MotionEvent ev): 事件分发的起点。返回true表示事件被消费,false表示未被消费,事件会继续传递。onInterceptTouchEvent(MotionEvent ev): 仅在ViewGroup中存在。用于判断是否拦截事件。返回true表示拦截,事件将不再分发给子 View,而是交由自身的onTouchEvent处理。onTouchEvent(MotionEvent ev): 处理触摸事件。返回true表示事件被消费。
1.2 分发流程 (伪代码)
java
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
// 被拦截,事件交给自己处理
consume = onTouchEvent(ev);
} else {
// 没有被拦截,遍历子View
for (View child : mChildren) {
if (child.isUnderPoint(ev.getX(), ev.getY())) {
// 事件坐标在子View范围内
consume = child.dispatchTouchEvent(ev);
if (consume) break; // 一旦被消费,跳出循环
}
}
// 如果所有子View都没消费,自己再尝试处理
if (!consume) {
consume = onTouchEvent(ev);
}
}
return consume;
}
1.3 滑动冲突解决方案
当父容器和子 View 都需要处理滑动事件时(如 ViewPager 中嵌套 RecyclerView),就会产生滑动冲突。解决方案主要有两种:
1. 外部拦截法
在父容器的 onInterceptTouchEvent 方法中,根据滑动方向来决定是否拦截。
java
public class MyParentLayout extends ViewGroup {
private int mLastXIntercept, mLastYIntercept;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false; // ACTION_DOWN 一定不能拦截,否则子 View 无法接收后续事件
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
// 判断是竖直滑动还是水平滑动
if (Math.abs(deltaY) > Math.abs(deltaX)) {
// 竖直滑动,由父容器处理 (例如:ScrollView)
intercepted = true;
} else {
// 水平滑动,由子 View 处理 (例如:ViewPager)
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false; // UP 事件一般不拦截
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
// ... 其他必要方法 (onLayout, onMeasure) 的实现
}
2. 内部拦截法
在子 View 中,通过调用 getParent().requestDisallowInterceptTouchEvent(true) 来请求父容器不要拦截事件。
java
public class MyChildView extends View {
private int mLastX, mLastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 请求父容器不要拦截我的事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (Math.abs(deltaY) > Math.abs(deltaX)) {
// 如果是竖直滑动,说明应该是父容器处理
getParent().requestDisallowInterceptTouchEvent(false);
}
// 如果是水平滑动,继续保持不拦截状态
break;
case MotionEvent.ACTION_UP:
// 手指抬起,重置状态
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
}
2. 自定义 View
当 Android 提供的原生控件无法满足复杂的 UI 需求时,自定义 View 就成为了必不可少的技能。它允许开发者完全掌控 View 的绘制、测量和交互逻辑。
2.1 核心步骤
- 继承 View 或其子类 :根据需求选择合适的基类,如
View,ViewGroup,TextView等。 - 重写
onMeasure()方法 :确定 View 的尺寸(onMeasure是测量过程的核心)。 - 重写
onLayout()方法 (仅ViewGroup):确定子 View 的位置。 - 重写
onDraw()方法 :使用Canvas和Paint进行实际的绘制。 - 处理触摸事件 :重写
onTouchEvent()等方法,实现交互逻辑。 - 提供自定义属性 (可选):通过
attrs.xml定义属性,方便在 XML 中配置。
2.2 测量 (onMeasure)
onMeasure 方法接收两个 MeasureSpec 参数(widthMeasureSpec 和 heightMeasureSpec),它们包含了父容器对当前 View 的尺寸约束信息。
MeasureSpec 的三种模式:
EXACTLY:父容器已经确定了 View 的确切尺寸(如match_parent或指定具体值)。AT_MOST:父容器指定了一个最大尺寸,View 的尺寸不能超过这个值(如wrap_content)。UNSPECIFIED:父容器对 View 的尺寸没有任何限制(较少见,如ScrollView内部)。
示例代码:一个简单的圆形 View
java
public class CircleView extends View {
private Paint mPaint;
private float mRadius;
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); // 抗锯齿
mPaint.setColor(Color.BLUE);
mRadius = 100f; // 默认半径
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width, height;
// 处理宽度
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
// 如果是 wrap_content,我们给一个默认宽度
width = (int) (mRadius * 2 + getPaddingLeft() + getPaddingRight());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize);
}
}
// 处理高度,逻辑同上
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = (int) (mRadius * 2 + getPaddingTop() + getPaddingBottom());
if (heightMode == MeasureSpec.AT_MOST) {
height = Math.min(height, heightSize);
}
}
// 设置测量后的尺寸
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 计算圆心坐标
float centerX = getWidth() / 2f;
float centerY = getHeight() / 2f;
// 绘制圆形
canvas.drawCircle(centerX, centerY, mRadius, mPaint);
}
}
2.3 绘制 (onDraw)
onDraw 方法是 View 的"画布"。它接收一个 Canvas 对象作为参数,开发者可以调用 Canvas 的各种 drawXxx() 方法(如 drawCircle, drawRect, drawText)来绘制图形。绘制时通常会配合 Paint 对象来设置颜色、样式、笔触等。
2.4 自定义属性
为了增强自定义 View 的灵活性,我们可以定义 XML 属性。
-
在
res/values/attrs.xml中定义:xml<resources> <declare-styleable name="CircleView"> <attr name="circleColor" format="color" /> <attr name="circleRadius" format="dimension" /> </declare-styleable> </resources> -
在构造函数中解析属性:
javapublic CircleView(Context context, AttributeSet attrs) { super(context, attrs); init(); // 解析自定义属性 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView); int color = a.getColor(R.styleable.CircleView_circleColor, Color.BLUE); float radius = a.getDimension(R.styleable.CircleView_circleRadius, 100f); a.recycle(); // 必须回收 mPaint.setColor(color); mRadius = radius; } -
在布局文件中使用:
xml<com.example.myapp.CircleView android:layout_width="wrap_content" android:layout_height="wrap_content" app:circleColor="@android:color/holo_red_dark" app:circleRadius="80dp" />
3. 四大组件深度解析
Android 四大组件(Activity, Service, BroadcastReceiver, ContentProvider)是构建应用的基石。
3.1 Activity
核心作用:用户交互的入口,每个界面对应一个 Activity。它管理界面的生命周期、用户输入和导航。
生命周期深度解析 :
Activity 的生命周期由 Android 系统管理,开发者必须正确处理生命周期回调,以保证应用的稳定性和用户体验。
| 方法 | 触发场景 | 典型操作 |
|---|---|---|
onCreate() |
Activity 首次创建时调用。只调用一次。 | 初始化组件(setContentView())、绑定数据、注册广播接收器。 |
onStart() |
Activity 即将对用户可见时调用。 | 开始与用户可见性相关的操作,如启动动画。 |
onResume() |
Activity 获得焦点,可与用户交互时调用。 | 恢复被暂停的 UI 更新、动画、传感器监听等。 |
onPause() |
Activity 失去焦点,但可能仍部分可见(如弹出对话框)。必须快速执行,不能做耗时操作。 | 暂停动画、音乐播放、释放摄像头等独占性资源。保存临时数据。 |
onStop() |
Activity 完全不可见时调用。 | 释放不再需要的资源,如网络连接、数据库连接。 |
onDestroy() |
Activity 被销毁前调用。只调用一次。 | 执行最终的清理工作,如解注册广播接收器、取消网络请求。 |
onRestart() |
Activity 从 onStop 状态重新启动时调用。 |
通常用于刷新 UI 数据。 |
生命周期场景示例:
- A -> B (B 为普通 Activity) : A:
onPause()→ B:onCreate()→ B:onStart()→ B:onResume()→ A:onStop() - 返回 A : B:
onPause()→ A:onRestart()→ A:onStart()→ A:onResume()→ B:onStop()→ B:onDestroy() - A -> B (B 为透明 Activity,如 DialogTheme) : A:
onPause()→ B:onCreate()→ B:onStart()→ B:onResume()(A 的
onStop()未被调用,因为它仍部分可见) - 返回 A : B:
onPause()→ A:onResume()→ B:onStop()→ B:onDestroy()
最佳实践:
- 避免在
onPause()中进行耗时操作:这会阻塞 Activity 的切换,导致界面卡顿。 - 在
onStop()中释放资源:这是释放后台资源的最后机会。 - 使用
ViewModel保存 UI 相关数据:避免因配置变更(如屏幕旋转)导致数据丢失。
3.2 Service
Service 是一种在后台执行长时间运行操作的组件,它没有用户界面。
类型与对比:
| 类型 | 特点 | 适用场景 | 示例 |
|---|---|---|---|
| Started Service | 通过 startService() 启动。一旦启动,就会在后台无限期运行,即使启动它的组件已被销毁。必须调用 stopSelf() 或 stopService() 才能停止。 |
执行独立的、不需要与组件交互的后台任务。 | 下载文件、播放音乐(不与前台交互时)。 |
| Bound Service | 通过 bindService() 启动。客户端(如 Activity)可以与 Service 建立连接,并通过 IBinder 接口进行方法调用和数据交互。当所有客户端都解除绑定后,Service 会被销毁。 |
需要与 Activity 或其他组件进行通信的后台服务。 | 音乐播放器(Activity 控制播放/暂停)、获取传感器数据。 |
| Foreground Service | 一种特殊的 Started Service。它必须提供一个持续显示的通知,告知用户有后台服务在运行。优先级很高,不易被系统杀死。 | 执行用户明确知晓的、重要的后台任务。 | 音乐播放、实时位置追踪、文件下载(用户可见进度)。 |
(1) Started Service 示例:
java
public class MyBackgroundService extends Service {
private static final String TAG = "MyBackgroundService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: Service created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: Task started");
// 在子线程中执行耗时任务
new Thread(() -> {
for (int i = 0; i < 10; i++) {
Log.d(TAG, "Working... " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
// 任务完成后停止服务
stopSelf();
}).start();
// 返回 START_STICKY,如果服务被杀死,系统会尝试重新创建它
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
// Started Service 通常不支持绑定
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: Service destroyed");
}
}
// 启动服务
Intent serviceIntent = new Intent(this, MyBackgroundService.class);
startService(serviceIntent);
// 停止服务
// stopService(serviceIntent);
(2) Foreground Service 示例:
java
public class ForegroundService extends Service {
private static final String CHANNEL_ID = "ForegroundServiceChannel";
private static final int NOTIFICATION_ID = 1;
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String input = intent.getStringExtra("inputExtra");
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
// 创建停止服务的 PendingIntent
Intent stopIntent = new Intent(this, ForegroundService.class);
stopIntent.setAction("STOP_SERVICE");
PendingIntent stopPendingIntent = PendingIntent.getService(
this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE
);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("My Foreground Service")
.setContentText(input)
.setSmallIcon(R.drawable.ic_service)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_stop, "Stop", stopPendingIntent)
.build();
// 将服务置于前台
startForeground(NOTIFICATION_ID, notification);
// 执行后台任务...
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
// 清理工作
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
注意事项:
-
权限声明 :在
AndroidManifest.xml中声明 Service。xml<service android:name=".MyBackgroundService" /> <service android:name=".ForegroundService" /> -
Android 8.0+ 限制 :后台服务的启动受到严格限制。对于非前台服务,应考虑使用
JobScheduler或WorkManager。 -
省电模式 :在 Android 9+ 上,前台服务也可能受到省电策略的影响,建议结合
WorkManager使用。
3.3 BroadcastReceiver
BroadcastReceiver 用于接收并响应系统或应用内发送的广播消息。
类型:
- 标准广播 (Normal Broadcast):异步、无序发送,所有接收者几乎同时收到。
- 有序广播 (Ordered Broadcast):按优先级顺序发送,前面的接收者可以截断广播,阻止后面的接收者接收。
- 粘性广播 (Sticky Broadcast):已废弃,不推荐使用。
- 本地广播 (LocalBroadcastManager):仅在应用内部发送和接收,更安全、高效。
动态注册与静态注册:
| 注册方式 | 代码位置 | 生命周期 | 适用场景 |
|---|---|---|---|
| 动态注册 | 在 Activity/Service 的 onCreate() 中注册,在 onDestroy() 中注销。 |
与注册组件的生命周期一致。 | 接收与特定 UI 状态相关的广播(如网络状态变化)。 |
| 静态注册 | 在 AndroidManifest.xml 中声明。 |
应用安装后一直有效,即使应用未运行。 | 接收系统级广播(如开机启动、充电状态变化)。 |
示例代码:动态注册网络状态变化广播:
java
public class MainActivity extends AppCompatActivity {
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerNetworkReceiver();
}
private void registerNetworkReceiver() {
networkChangeReceiver = new NetworkChangeReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(networkChangeReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (networkChangeReceiver != null) {
unregisterReceiver(networkChangeReceiver);
}
}
// 广播接收器
class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnected();
Toast.makeText(context, "Network: " + (isConnected ? "Connected" : "Disconnected"), Toast.LENGTH_SHORT).show();
}
}
}
3.4 ContentProvider
ContentProvider 用于在不同应用之间安全地共享数据。
核心概念:
- URI (Uniform Resource Identifier) :唯一标识一个数据集,格式为
content://authority/path/id。 - MIME Type:描述返回数据的类型。
- CRUD 操作 :通过
insert(),delete(),update(),query()方法提供数据的增删改查。
示例代码:一个简单的 ContentProvider:
java
public class MyContentProvider extends ContentProvider {
public static final String AUTHORITY = "com.example.myapp.provider";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/items");
private SQLiteDatabase database;
@Override
public boolean onCreate() {
DatabaseHelper helper = new DatabaseHelper(getContext());
database = helper.getWritableDatabase();
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return database.query("items", projection, selection, selectionArgs, null, null, sortOrder);
}
@Override
public Uri insert(Uri uri, ContentValues values) {
long id = database.insert("items", null, values);
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);
}
// ... 实现 delete, update, getType 方法
// DatabaseHelper 省略
}
在其他应用中访问:
java
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(MyContentProvider.CONTENT_URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex("name"));
// 处理数据
} while (cursor.moveToNext());
cursor.close();
}
三、性能优化
性能是衡量一个 Android 应用质量的核心指标。糟糕的性能会导致应用卡顿、耗电、内存溢出甚至崩溃。开发者必须从多个维度进行系统性的优化。
1. 内存优化
Android 设备的内存资源有限,不当的内存使用是导致应用崩溃(如 OutOfMemoryError)和卡顿的主要原因。
1.1 内存泄漏 (Memory Leak)
内存泄漏是指本该被回收的对象,由于被错误地持有引用而无法被 GC (Garbage Collector) 回收,导致内存占用持续增长。
常见场景与解决方案:
| 场景 | 原因 | 解决方案 |
|---|---|---|
| 静态变量持有 Activity/View 引用 | 静态变量的生命周期与应用进程相同,若持有 Activity 引用,Activity 销毁后也无法被回收。 | 避免在静态变量中直接持有 Context 或 View。使用 ApplicationContext,或在合适时机将引用置为 null。 |
| 非静态内部类持有外部类引用 | 非静态内部类(如匿名内部类)会隐式持有外部类的引用。如果该内部类实例的生命周期长于外部类(如静态 Handler),就会导致内存泄漏。 |
将内部类声明为 static(静态内部类),并使用 WeakReference 持有外部类引用。 |
| 未注销的监听器/广播接收器 | 在 Activity/Fragment 中注册了 BroadcastReceiver、SensorManager 监听器等,但未在 onDestroy() 中注销。 |
在组件的生命周期结束时(如 onDestroy() 或 onStop()),务必调用 unregisterReceiver() 或 unregisterListener()。 |
| 无限增长的集合 | 集合(如 List、Map)不断添加对象,但没有相应的清理机制。 |
定期清理不再需要的对象,或使用弱引用集合(如 WeakHashMap)。 |
| Bitmap 使用不当 | 加载过大的 Bitmap,或未及时调用 recycle()。 |
使用图片加载库(如 Glide、Picasso)自动管理;根据 ImageView 大小进行缩放(inSampleSize);及时回收不再使用的 Bitmap。 |
工具检测:
- Android Studio Profiler:实时监控内存分配。
- LeakCanary:一个强大的开源库,能自动检测并报告内存泄漏。
1.2 内存抖动 (Memory Churn)
内存抖动指在短时间内频繁地创建和销毁大量对象,导致频繁触发 GC。GC 会暂停所有线程(Stop-the-World),造成界面卡顿。
避免方法:
- 避免在
onDraw()等高频调用的方法中创建对象 :如Paint、Rect等对象应在类初始化时创建,作为成员变量复用。 - 使用对象池 (Object Pooling) :对于频繁创建销毁的对象(如
Message、Bitmap),可以使用Handler的Message Pool或自定义对象池。 - 使用基本数据类型 :在性能敏感的代码中,优先使用
int而不是Integer。
2. 布局优化
复杂的布局层级会显著增加 measure、layout 和 draw 的耗时,导致界面渲染缓慢。
2.1 减少嵌套层级
深层嵌套的 ViewGroup 会增加测量和布局的计算量。
解决方案:
- 使用更轻量的
ViewGroup:- 用
ConstraintLayout替代深层嵌套的LinearLayout和RelativeLayout。ConstraintLayout能实现复杂布局且性能优异。 - 用
FrameLayout替代简单的单层嵌套。
- 用
- 使用
<include>标签复用布局:将公共布局(如标题栏)提取出来,避免重复编写。 - 使用
<merge>标签减少层级 :当被<include>的布局根节点与父布局类型相同时,使用<merge>可以避免产生额外的ViewGroup。
2.2 使用 <ViewStub> 懒加载
<ViewStub> 是一个轻量级的占位 View,它不绘制任何内容,也不参与布局。只有在需要时调用 inflate(),才会加载真正的布局。
适用场景:
- 错误提示页面。
- 复杂的筛选条件面板。
- 用户不常访问的功能模块。
xml
<!-- 布局文件 -->
<ViewStub
android:id="@+id/stub_error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/layout_error" />
<!-- 在代码中按需加载 -->
ViewStub stub = findViewById(R.id.stub_error);
if (needToShowError) {
stub.inflate(); // 第一次调用 inflate() 会加载 layout_error 布局
}
3. 卡顿优化
卡顿(Jank)是指应用界面在 16ms 内未能完成一帧的渲染,导致掉帧。60 FPS 是流畅的标准。
3.1 主线程 (UI Thread) 优化
Android 的 UI 操作必须在主线程进行。任何耗时操作(如网络请求、数据库读写、复杂计算)阻塞主线程,都会导致界面卡顿。
解决方案:
- 使用多线程 :
AsyncTask(已废弃):简单任务,但易导致内存泄漏。HandlerThread/Looper:处理串行任务。ExecutorService:线程池,管理并发。Kotlin Coroutines:现代、简洁的异步编程方案,推荐使用。
- 使用
IntentService/WorkManager:处理后台任务。 - 使用
RxJava:响应式编程,处理复杂的异步数据流。
3.2 渲染性能优化
即使主线程没有耗时操作,复杂的 onDraw() 也可能导致卡顿。
工具:
Profile GPU Rendering:在开发者选项中开启,以柱状图形式显示每帧的渲染耗时。Systrace:更强大的性能分析工具,可分析系统级和应用级的性能瓶颈。
优化点:
- 避免过度绘制 (Overdraw):同一个像素被绘制多次。使用开发者选项中的"调试GPU过度绘制"功能,将过度绘制的区域变为红色,应尽量减少红色区域。
- 简化
onDraw()逻辑 :避免在onDraw()中进行对象创建、字符串拼接等操作。 - 使用硬件加速 :确保
View的硬件加速是开启的(通常默认开启)。
四、存储系统
Android 提供了多种数据存储方案,开发者需根据数据类型和需求选择合适的方案。
1. SharedPreferences
- 类型:轻量级的键值对存储。
- 适用场景:保存应用的配置信息、用户偏好设置(如主题、音量)。
- 特点 :
- 基于 XML 文件存储。
- 仅支持基本数据类型(
boolean,int,float,long,String)。 apply()异步提交,无返回值;commit()同步提交,有返回值。
- 注意:不适合存储大量数据,否则读写会很慢。
java
// 获取 SharedPreferences
SharedPreferences sp = getSharedPreferences("config", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
// 写入数据
editor.putBoolean("is_first_launch", false);
editor.putString("user_name", "Alice");
editor.apply(); // 异步提交
// 读取数据
boolean isFirstLaunch = sp.getBoolean("is_first_launch", true);
String userName = sp.getString("user_name", "Guest");
2. 文件存储
- 内部存储 (Internal Storage) :
- 路径:
/data/data/<package_name>/files/。 - 特点:私有,应用卸载时数据自动删除。适合存储敏感或重要的应用数据。
- API:
openFileInput(),openFileOutput()。
- 路径:
- 外部存储 (External Storage) :
- 路径:
/storage/emulated/0/Android/data/<package_name>/files/(应用专属目录) 或公共目录(如Pictures,Download)。 - 特点:空间大,但可能不可用(如被用户卸载 SD 卡)。应用专属目录在卸载时也会被删除。
- 权限 :Android 10+ 对外部存储访问有严格限制(Scoped Storage),应优先使用应用专属目录或
MediaStoreAPI。
- 路径:
3. SQLite 数据库
- 类型:轻量级的关系型数据库。
- 适用场景:存储结构化数据,如用户信息、消息记录、商品列表。
- 核心类 :
SQLiteOpenHelper:管理数据库的创建和版本升级。SQLiteDatabase:执行 CRUD 操作。
- 最佳实践 :
- 使用事务(
beginTransaction()/setTransactionSuccessful()/endTransaction())批量操作,提高效率。 - 使用
ContentValues和?占位符,防止 SQL 注入。 - 使用
Cursor后及时关闭。
- 使用事务(
示例:
java
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "app.db";
private static final int DB_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
}
// 使用
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();
// 插入
ContentValues values = new ContentValues();
values.put("name", "Bob");
values.put("age", 25);
db.insert("users", null, values);
// 查询
Cursor cursor = db.query("users", null, null, null, null, null, null);
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
}
cursor.close();
db.close();
4. Room 持久性库 (Room Persistence Library)
Room 是 Google 推出的官方 ORM (对象关系映射) 库,它在 SQLite 的基础上提供了更简洁、更安全的 API。
优势:
- 编译时检查 SQL 语句。
- 通过注解将 Java/Kotlin 对象映射为数据库表。
- 与
LiveData和Flow集成,实现数据变更自动通知 UI。 - 减少模板代码。
核心组件:
@Entity:标记一个类为数据库表。@Dao(Data Access Object):定义访问数据库的方法。@Database:继承RoomDatabase,是数据库的持有者。
示例:
java
// 1. Entity
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
public String name;
public int age;
}
// 2. DAO
@Dao
public interface UserDao {
@Insert
void insert(User user);
@Query("SELECT * FROM users")
List<User> getAllUsers();
}
// 3. Database
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
// 4. 使用
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "app-db").build();
UserDao userDao = db.userDao();
User user = new User();
user.name = "Charlie";
user.age = 30;
userDao.insert(user);
List<User> users = userDao.getAllUsers();
五、网络编程
现代应用几乎都离不开网络通信。
1. HTTP/HTTPS 协议
- HTTP:超文本传输协议,明文传输,不安全。
- HTTPS :HTTP over SSL/TLS,加密传输,保证数据安全。生产环境必须使用 HTTPS。
2. 网络请求库
HttpURLConnection:Android 原生 API,功能完整但使用繁琐。OkHttp:- 高性能、功能强大的 HTTP 客户端。
- 支持 HTTP/2、连接池、GZIP 压缩、缓存等。
- 是 Retrofit 的底层依赖。
Retrofit:- 基于 OkHttp 的类型安全的 RESTful 客户端。
- 通过注解定义 API 接口,将 HTTP 请求转换为 Java/Kotlin 方法调用。
- 极大简化了网络请求代码。
Retrofit 示例:
java
// 1. 定义 API 接口
public interface ApiService {
@GET("users/{id}")
Call<User> getUser(@Path("id") int userId);
@POST("users")
Call<User> createUser(@Body User user);
}
// 2. 创建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create()) // JSON 转换
.build();
// 3. 创建服务
ApiService service = retrofit.create(ApiService.class);
// 4. 发起请求
Call<User> call = service.getUser(123);
call.enqueue(new Callback<User>() {
@Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
User user = response.body();
// 处理成功结果
}
}
@Override
public void onFailure(Call<User> call, Throwable t) {
// 处理失败
}
});
3. 网络权限
必须在 AndroidManifest.xml 中声明网络权限:
xml
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果只使用应用专属网络(如 OkHttp 的内部缓存),可能不需要 -->
<!-- 对于 Android 9+ 的 cleartext traffic,如果使用 HTTP,还需在 network_security_config 中配置 -->
4. 网络状态判断
在发起网络请求前,应先检查网络连接状态。
java
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnected();
六、Jetpack 组件详解
Jetpack 是 Google 官方推出的一套库、工具和指南的集合,旨在帮助开发者遵循最佳实践,减少样板代码,并编写出向后兼容、稳定可靠的应用。它是现代 Android 开发的基石。
1. ViewModel
ViewModel 的核心作用是以生命周期感知的方式存储和管理与 UI 相关的数据。
解决的问题 :
在传统的 Activity/Fragment 中,数据通常作为成员变量存储。当设备配置发生变化(如屏幕旋转)时,Activity 会被销毁并重新创建,导致所有成员变量丢失,需要重新请求网络或查询数据库,造成资源浪费和用户体验下降。
工作原理:
ViewModel的生命周期比 Activity/Fragment 更长。- 当 Activity 因配置变更被销毁时,系统会保留其关联的
ViewModel实例。 - 新创建的 Activity 会获取到同一个
ViewModel实例,从而恢复之前的数据。
使用示例:
kotlin
// 1. 定义 ViewModel
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user // 暴露不可变的 LiveData
private val repository = UserRepository()
fun loadUser(userId: Int) {
// 在后台线程中加载数据
viewModelScope.launch {
try {
val userData = repository.fetchUser(userId)
_user.value = userData // 更新 LiveData
} catch (e: Exception) {
// 处理错误
}
}
}
}
// 2. 在 Activity/Fragment 中使用
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// 获取 ViewModel,而不是 new 一个
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
// 观察数据变化
viewModel.user.observe(this) { user ->
// 更新 UI
textViewName.text = user.name
textViewAge.text = user.age.toString()
}
// 发起数据加载
viewModel.loadUser(123)
}
}
关键点:
- 使用
ViewModelProvider获取实例。 - 配合
LiveData或StateFlow实现数据驱动 UI。 viewModelScope:内置的 CoroutineScope,会在 ViewModel 被清除时自动取消协程,防止内存泄漏。
2. LiveData
LiveData 是一个可观察的数据持有者类,它是生命周期感知的。
核心优势:
- 生命周期安全 :只有当观察者的生命周期处于
STARTED或RESUMED状态时,才会通知更新。避免了在非活跃状态下更新 UI 导致的崩溃。 - 不会引起内存泄漏 :当观察者(如 Activity)被销毁时,
LiveData会自动移除该观察者。
使用场景:
- 与
ViewModel结合,将数据从 ViewModel 传递到 UI 层。 - 替代传统的事件总线(如 EventBus),用于组件间通信。
基本用法:
kotlin
// 1. 在 ViewModel 中定义
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun updateData(newData: String) {
_data.value = newData // 触发通知
}
}
// 2. 在 UI 层观察
viewModel.data.observe(this, Observer { newData ->
// 此处更新 UI,例如:
textView.text = newData
})
Transformations :
LiveData 提供了 Transformations 工具类,可以在不改变原始 LiveData 的情况下对其进行转换。
kotlin
val userName: LiveData<String> = Transformations.map(userLiveData) { user ->
"${user.firstName} ${user.lastName}"
}
3. ViewBinding 和 DataBinding
两者都旨在简化视图操作,但侧重点不同。
ViewBinding:
- 作用:为每个 XML 布局文件生成一个绑定类,提供对布局中所有 View 的类型安全引用。
- 优点 :
- 完全替代
findViewById(),避免NullPointerException。 - 编译时生成,无运行时开销。
- 减少样板代码。
- 完全替代
- 缺点:不支持布局中的逻辑表达式。
使用示例:
kotlin
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 通过绑定类设置内容视图
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 直接访问 View,无需 findViewById
binding.textViewHello.text = "Hello World!"
binding.buttonClick.setOnClickListener {
// 处理点击
}
}
}
DataBinding:
- 作用:允许在布局文件中直接绑定数据源(如变量、事件处理方法),实现 MVVM 模式。
- 优点 :
- 可以在 XML 中使用表达式(如
android:text="@{viewmodel.userName}")。 - 支持双向数据绑定(
@={})。 - 减少 Activity/Fragment 中的胶水代码。
- 可以在 XML 中使用表达式(如
- 缺点:编译时间稍长,布局文件可能变得复杂。
使用示例:
xml
<!-- layout/activity_main.xml -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewmodel"
type="com.example.MyViewModel" />
</data>
<LinearLayout ...>
<TextView
android:text="@{viewmodel.userName}"
... />
<Button
android:onClick="@{() -> viewmodel.onButtonClick()}"
... />
</LinearLayout>
</layout>
kotlin
// Activity 中
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewmodel = myViewModel
binding.lifecycleOwner = this // 对于 LiveData,需要设置 lifecycle owner
选择建议:
- 如果只需要避免
findViewById,推荐使用 ViewBinding,简单高效。 - 如果追求极致的 MVVM 和数据驱动,可以考虑 DataBinding。
4. Navigation
Navigation 组件提供了一个统一的框架来实现应用内导航。
核心概念:
- Navigation Graph (导航图):一个 XML 文件,定义了应用的所有目的地(Destinations)以及它们之间的跳转关系。
- Destination:导航的目标,通常是 Fragment,也可以是 Activity。
- NavHost :一个容器,用于在其中显示导航图中的目的地。常用的是
NavHostFragment。 - NavController:负责管理导航后退栈的核心类。
优势:
- 可视化导航流程:在 Android Studio 中可以图形化查看导航图。
- 处理常见导航模式:自动处理 Fragment 的替换、入栈出栈。
- 类型安全的参数传递:通过 Safe Args 插件,可以类型安全地在目的地之间传递参数。
- 集成底部导航栏、抽屉菜单等。
基本用法:
- 创建
res/navigation/nav_graph.xml。 - 在主 Activity 布局中添加
NavHostFragment。 - 使用
NavController进行跳转。
kotlin
// 跳转到 destination_detail
findNavController().navigate(R.id.action_to_detail)
// 或使用 Safe Args 生成的 Directions 类
findNavController().navigate(MyFragmentDirections.actionToDetail(arg1, arg2))
5. WorkManager
WorkManager 是一个用于处理可延迟的、保证执行的后台任务的库。
适用场景:
- 数据同步。
- 备份用户数据。
- 压缩图片并上传。
- 执行不需要立即完成的任务。
核心特性:
- 保证执行:即使应用退出或设备重启,任务也会在合适的时机被执行。
- 约束条件 (Constraints) :可以设置任务运行的条件,如:
- 设备处于空闲状态 (
DeviceIdle) - 有网络连接 (
NetworkType) - 设备正在充电 (
Charging) - 存储空间充足 (
StorageNotLow)
- 设备处于空闲状态 (
- 灵活性:支持一次性任务和周期性任务。
- 向后兼容 :在 API 14+ 上都能正常工作,底层根据系统版本选择最优的调度机制(如
JobScheduler,Firebase JobDispatcher,AlarmManager)。
使用示例:
kotlin
// 1. 定义 Worker
class UploadWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
// 执行耗时的上传操作
uploadData()
Result.success()
} catch (e: Exception) {
// 失败,可根据策略重试
Result.retry()
}
}
}
// 2. 调度任务
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.build()
val uploadRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueue(uploadRequest)
七、Kotlin 语言特性在 Android 中的应用
Kotlin 已成为 Android 开发的首选语言。它简洁、安全、功能强大,极大地提升了开发效率。
1. 空安全 (Null Safety)
Kotlin 的类型系统明确区分可空类型 (String?) 和非空类型 (String),从编译期就杜绝了 NullPointerException。
kotlin
var text: String = "Hello" // 非空,不能赋值 null
text = null // 编译错误!
var nullableText: String? = "World" // 可空
nullableText = null // 合法
// 安全调用操作符 ?.
val length = nullableText?.length // 如果 nullableText 为 null,结果为 null
// Elvis 操作符 ?:
val len = nullableText?.length ?: 0 // 如果左边为 null,则取右边的值
// 断言操作符 !!
val mustHaveLength = nullableText!!.length // 断言不为 null,否则抛出异常
2. 扩展函数 (Extension Functions)
允许在不修改原类的情况下,为其添加新的函数。
kotlin
// 为 String 类添加一个扩展函数
fun String.isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
// 使用
val email = "user@example.com"
if (email.isValidEmail()) {
// ...
}
3. 数据类 (Data Classes)
使用 data class 关键字,自动生成 equals(), hashCode(), toString(), copy() 等方法。
kotlin
data class User(val name: String, val age: Int)
val user1 = User("Alice", 30)
val user2 = User("Alice", 30)
println(user1 == user2) // true (基于属性值比较)
println(user1) // User(name=Alice, age=30)
val user3 = user1.copy(age = 31) // 创建副本
4. 伴生对象 (Companion Objects)
用于存放与类相关但不依赖于类实例的函数和属性,类似于 Java 的静态成员。
kotlin
class NetworkClient {
companion object {
const val BASE_URL = "https://api.example.com/"
fun createInstance(): NetworkClient {
return NetworkClient()
}
}
}
// 使用
val client = NetworkClient.createInstance()
println(NetworkClient.BASE_URL)
5. 协程 (Coroutines)
协程是 Kotlin 处理异步编程的现代化方案,它基于线程,但更轻量、更易读。
核心概念:
suspend函数:可以暂停执行而不阻塞线程的函数。CoroutineScope:协程的作用域,用于启动和管理协程。launch/async:启动协程的构建器。
在 Android 中的应用:
kotlin
class MyViewModel : ViewModel() {
fun fetchData() {
// viewModelScope 是由 ViewModel 提供的 CoroutineScope
viewModelScope.launch {
try {
// 调用 suspend 函数,代码看起来像同步的
val result = repository.getDataFromNetwork()
_uiState.value = UiState.Success(result)
} catch (e: Exception) {
_uiState.value = UiState.Error(e.message)
}
}
}
}
// Repository 中的 suspend 函数
class MyRepository {
suspend fun getDataFromNetwork(): Data {
// Retrofit 接口方法可以是 suspend 的
return apiService.getData()
}
}
优势:
- 避免回调地狱:代码线性化,易于理解和维护。
- 轻量:单个线程可以支持成千上万个协程。
- 结构化并发:通过作用域管理协程的生命周期。
八、Jetpack Compose:现代 UI 开发范式
Jetpack Compose 是 Google 推出的现代化、声明式的 UI 工具包,旨在彻底改变 Android 应用的界面开发方式。它完全基于 Kotlin 构建,使用函数来描述 UI,取代了传统的基于 XML 布局和 View 系统的命令式编程模型。
1. 核心理念:声明式 UI
- 命令式 (Imperative) :传统的
View系统是命令式的。开发者需要明确地告诉系统"如何做"(How),例如findViewById(),然后调用setText()、setVisibility()等方法来一步步修改 UI 的状态。 - 声明式 (Declarative):Compose 是声明式的。开发者只需描述"UI 应该是什么样"(What),基于当前的应用状态。当状态发生变化时,Compose 会自动、高效地更新 UI。
类比:
- 命令式就像导演给演员下达一系列动作指令。
- 声明式就像给演员一个剧本,演员(Compose)会根据剧本(状态)自动演绎出正确的场景(UI)。
2. 核心概念
2.1 Composable 函数
- 以
@Composable注解标记的 Kotlin 函数。 - 函数名通常以大写字母开头(如
Greeting)。 - 它的职责是定义 UI 的一部分。
- 可以像普通函数一样被调用和复用。
kotlin
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
2.2 状态 (State)
状态是 UI 的单一事实来源。在 Compose 中,当状态改变时,相关的 Composable 函数会自动重新执行(Recompose),以反映新的状态。
mutableStateOf():创建一个可观察的状态。remember:在重组(Recomposition)之间记住状态的值。
kotlin
@Composable
fun Counter() {
// 使用 remember 记住 count 的值
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
// 当 count 改变时,整个 Counter 函数会重新执行
}
2.3 重组 (Recomposition)
- 当状态发生变化时,Compose 会智能地重新执行(重组)那些读取了该状态的 Composable 函数。
- 智能重组:Compose 会尽可能地缩小重组的范围,只更新真正需要改变的部分,而不是整个 UI 树。
- 并发性:重组可以在后台线程进行,不影响主线程的流畅性。
3. 布局与组件
Compose 提供了一套全新的布局和 UI 组件。
3.1 主要布局 (Layouts)
Column:垂直排列子项。Row:水平排列子项。Box:层叠排列子项(类似FrameLayout)。LazyColumn/LazyRow:延迟加载的滚动列表,性能优异,是RecyclerView的 Compose 版本。
kotlin
@Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageItem(message)
}
}
}
3.2 Material Design 组件
Compose 内置了对 Material Design 3 的完整支持。
kotlin
@Composable
fun MyApp() {
MaterialTheme {
Surface(color = MaterialTheme.colorScheme.background) {
Column {
TopAppBar(title = { Text("My App") })
Greeting("Android")
Button(onClick = { /* handle click */ }) {
Text("Click me")
}
}
}
}
}
4. 与现有 View 系统互操作
在迁移到 Compose 的过程中,可以逐步进行。
- 在 Compose 中使用 View :使用
AndroidView可组合项。 - 在 View 系统中使用 Compose :使用
ComposeView。
xml
<!-- 在 XML 布局中使用 ComposeView -->
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
kotlin
// 在 Activity/Fragment 中设置 Compose 内容
composeView.setContent {
MaterialTheme {
Greeting("Hello from Compose!")
}
}
5. 优势与挑战
| 优势 | 挑战 |
|---|---|
| 代码更简洁:Kotlin 代码替代 XML,逻辑和 UI 紧密结合。 | 学习曲线:需要理解声明式 UI、重组、状态管理等新概念。 |
| 强大的 Kotlin 集成:充分利用 Kotlin 的语言特性。 | 工具链成熟度:虽然已稳定,但某些高级功能和第三方库支持仍在发展中。 |
实时预览 :Android Studio 提供强大的 @Preview 注解,可即时预览 Composable。 |
性能调优:需要理解重组机制以避免不必要的性能开销。 |
| 热重载 (Live Edit):修改代码后,UI 可以快速更新,无需重新编译整个应用。 | 迁移成本:大型传统项目完全迁移到 Compose 需要时间和规划。 |
结论:Compose 是 Android UI 开发的未来。对于新项目,强烈推荐直接使用 Compose。对于现有项目,可以采用渐进式迁移策略。
九、应用安全
安全是应用开发中不可忽视的一环。一个存在安全漏洞的应用,不仅会损害用户体验,还可能导致用户数据泄露和品牌声誉受损。
1. 数据安全
1.1 数据存储安全
- 敏感数据 :避免将密码、Token 等敏感信息明文存储在
SharedPreferences或文件中。 - 解决方案 :
- 使用 Android 的
Security库或EncryptedSharedPreferences。 - 将敏感信息存储在 Android Keystore 系统中,利用硬件级安全模块(如果可用)进行保护。
- 使用 Android 的
1.2 数据传输安全
- 强制使用 HTTPS :在
AndroidManifest.xml中设置android:usesCleartextTraffic="false",并配置network_security_config。 - 证书锁定 (Certificate Pinning):防止中间人攻击(MITM),确保应用只与预期的服务器通信。
2. 组件安全
2.1 Activity/Service/Broadcast Receiver 安全
- 权限控制 :为需要保护的组件设置
android:exported="false",或通过android:permission属性要求特定权限。 - 避免隐式 Intent 滥用:谨慎处理来自其他应用的 Intent,验证其来源和数据。
2.2 Content Provider 安全
- 权限控制 :通过
android:readPermission和android:writePermission限制访问。 - 参数化查询:防止 SQL 注入攻击。
3. 代码安全
3.1 代码混淆 (ProGuard / R8)
- 作用:压缩、优化和混淆代码,使反编译后的代码难以阅读,保护核心逻辑。
- 配置 :在
build.gradle中启用minifyEnabled true。
3.2 Root 检测与反调试
- 目的:检测设备是否已 Root 或应用是否正在被调试,以采取相应的安全措施(如限制功能、增加日志等)。
- 注意:这些技术可以被绕过,应作为纵深防御的一部分,而非唯一防线。
4. 权限最佳实践
- 最小权限原则:只申请应用功能所必需的权限。
- 运行时权限:对于危险权限(如位置、相机、存储),必须在运行时向用户请求。
- 优雅降级:当用户拒绝权限时,应用应能优雅地降级功能,而非直接崩溃。
kotlin
// 检查并请求权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CODE_CAMERA
)
} else {
// 权限已授予,执行操作
}
5. WebView 安全
- 禁用 JavaScript :如果不需要,应禁用
setJavaScriptEnabled(false)。 - 避免
addJavascriptInterface:此方法在 API 17 以下存在严重的远程代码执行漏洞。 - 校验 URL:确保加载的网页 URL 是可信的。
十、发布上线与持续集成 (CI/CD)
将应用从开发环境成功部署到用户手中,并建立高效的迭代流程,是开发周期的最后也是至关重要的一环。
1. 构建与签名
1.1 构建变体 (Build Variants)
Android Gradle 插件允许通过组合 Build Type 和 Product Flavor 来创建不同的构建变体。
- Build Type :定义构建的配置,通常用于区分开发和生产环境。
debug:默认包含,用于开发调试,自动签名(调试密钥),开启调试功能。release:用于发布,需要手动配置,通常启用代码混淆和优化。
- Product Flavor :定义应用的不同"版本",例如:
free/paid:免费版和付费版。dev/staging/prod:不同后端环境。us/eu:针对不同地区的版本。
示例配置 (build.gradle):
groovy
android {
...
flavorDimensions "version", "environment"
productFlavors {
free {
dimension "version"
applicationIdSuffix ".free"
versionNameSuffix "-free"
}
paid {
dimension "version"
applicationIdSuffix ".paid"
versionNameSuffix "-paid"
}
dev {
dimension "environment"
applicationIdSuffix ".dev"
versionNameSuffix "-dev"
buildConfigField "String", "BASE_URL", "\"https://api.dev.example.com/\""
}
prod {
dimension "environment"
buildConfigField "String", "BASE_URL", "\"https://api.example.com/\""
}
}
buildTypes {
debug {
debuggable true
}
release {
debuggable false
minifyEnabled true // 启用 R8 代码压缩
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release // 关联签名配置
}
}
}
这将生成如 freeDevDebug, paidProdRelease 等多种构建变体。
1.2 应用签名 (App Signing)
为了在 Google Play 上发布,应用必须使用数字证书进行签名。签名有两个主要目的:验证应用来源和确保应用更新的完整性。
-
密钥库 (Keystore) :一个包含私钥和证书的文件(
.jks或.keystore)。 -
生成密钥 :使用
keytool命令或 Android Studio 的向导创建。bashkeytool -genkey -v -keystore my-upload-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias -
配置签名 :在
build.gradle中配置signingConfigs。groovyandroid { ... signingConfigs { release { storeFile file('my-upload-key.jks') storePassword 'your_store_password' keyAlias 'my-key-alias' keyPassword 'your_key_password' } } buildTypes { release { ... signingConfig signingConfigs.release } } }重要提示 :务必安全备份您的密钥库文件和密码!一旦丢失,您将无法更新该应用。
-
Google Play 应用签名:Google Play 提供了一项服务,由 Google 为您管理应用的签名密钥。您上传一个"上传密钥"给 Google,而 Google 会使用其管理的"应用签名密钥"来为您的应用签名并分发。这更安全,因为即使您的上传密钥泄露,Google 也可以为您重置它,而不会影响应用的签名。
2. Google Play 发布流程
- 创建开发者账号:支付一次性注册费(25美元)。
- 准备发布内容 :
- APK 或 AAB :生成发布版本的包。强烈推荐使用 Android App Bundle (
.aab)。AAB 不是直接安装的文件,而是 Google Play 用来为不同设备生成优化过的 APK(如按 ABI、屏幕密度分包)的格式,可以显著减小用户下载的体积。 - 应用图标:512x512 PNG。
- 屏幕截图:不同设备尺寸的截图。
- 应用描述:标题、短描述、长描述。
- 分类、内容分级等。
- APK 或 AAB :生成发布版本的包。强烈推荐使用 Android App Bundle (
- 创建应用:在 Google Play Console 中创建新应用。
- 内部测试/封闭式测试:先发布到测试轨道,邀请特定用户进行测试。
- 开放式测试:发布到更大的测试群体。
- 生产环境发布:当测试完成,将版本从测试轨道提升到生产轨道,向所有用户发布。
- 监控与迭代:通过 Play Console 监控崩溃报告、用户反馈、下载量等数据,持续迭代更新。
3. 持续集成与持续部署 (CI/CD)
CI/CD 是自动化构建、测试和部署流程的实践,旨在提高开发效率和软件质量。
核心流程:
- 提交代码:开发者将代码推送到 Git 仓库(如 GitHub, GitLab)。
- 触发流水线 (Pipeline):代码推送事件自动触发 CI/CD 流水线。
- 构建 :在 CI 服务器上执行
./gradlew assembleRelease生成 AAB/APK。 - 测试 :运行单元测试 (
test) 和 Android 测试 (connectedAndroidTest)。 - 代码质量检查:运行 Lint、Ktlint 等静态分析工具。
- 部署 :
- 测试环境:将构建产物自动上传到测试分发平台(如 Firebase App Distribution, TestFlight for Android)。
- 生产环境:经过审批后,自动发布到 Google Play 的指定轨道(如内部测试、生产)。
常用工具:
- GitHub Actions:集成在 GitHub 中,使用 YAML 文件定义工作流。
- GitLab CI/CD :集成在 GitLab 中,使用
.gitlab-ci.yml文件。 - Jenkins:开源的自动化服务器,功能强大,配置灵活。
- CircleCI , Travis CI 等。
GitHub Actions 示例 (/.github/workflows/android.yml):
yaml
name: Android CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
- name: Run Tests
run: ./gradlew testDebugUnitTest connectedDebugAndroidTest
- name: Run Lint
run: ./gradlew lintDebug
4. 应用内更新 (In-App Updates)
Google Play 提供了 API,允许应用在运行时检查更新并引导用户完成更新,无需离开应用。
- 灵活更新 (Flexible Update):下载在后台进行,用户可以在下载完成后选择重启应用来应用更新。适合非强制性更新。
- 立即更新 (Immediate Update):弹出全屏对话框,要求用户立即更新。适合包含重要安全修复的强制性更新。
优势:显著提高应用的更新率,确保用户能及时获得新功能和修复。
十一、前沿技术展望
Android 生态系统不断发展,开发者需要关注前沿技术以保持竞争力。
1. Kotlin Multiplatform (KMP)
KMP 允许开发者使用 Kotlin 语言编写可以在多个平台(Android, iOS, Web, Desktop, Backend)上共享的业务逻辑代码。
- 核心思想:将平台无关的代码(如数据模型、网络请求、业务规则)提取到一个共享模块中。
- 平台特定代码 :UI 和平台特定的 API(如 Android 的
Context, iOS 的UIKit)仍然需要在各自平台上实现。 - 优势 :
- 代码复用:减少重复开发,提高开发效率。
- 一致性:确保不同平台的业务逻辑完全一致。
- 单一语言:团队可以更专注于 Kotlin。
- 挑战:生态系统仍在成熟中,某些库的跨平台支持有限。
2. 跨设备体验 (Cross-Device Experience)
Android 不再局限于手机。Google 正在推动无缝的跨设备体验。
- Wear OS:为智能手表设计的操作系统,与手机深度集成。
- Android TV / Google TV:为电视和流媒体设备提供大屏体验。
- Android for Cars:车载信息娱乐系统。
- Foldables:可折叠设备,需要应用适配不同的屏幕尺寸和形态。
- 开发重点:响应式布局、Material You 设计、利用设备特定传感器(如手表的心率监测)。
3. 人工智能与机器学习集成
将 AI/ML 能力集成到应用中,可以创造更智能的用户体验。
- ML Kit :Google 提供的移动 SDK,包含预训练的 API,如:
- 文本识别 (Text Recognition)
- 条形码扫描 (Barcode Scanning)
- 图像标记 (Image Labeling)
- 人脸检测 (Face Detection)
- 语言翻译 (Translate)
- 自定义模型 (Custom Model):可以将 TensorFlow Lite 模型集成到应用中。
- 优势:在设备端运行,保护用户隐私,响应速度快,无需网络连接。
4. 64 位与 ABI 兼容性
Google Play 已强制要求新应用和更新必须提供 64 位版本(arm64-v8a, x86_64)。
- 原因:64 位架构性能更好,内存寻址能力更强。
- Native Code :如果应用包含 C/C++ 代码(通过 NDK),必须为 64 位 ABI 编译相应的
.so文件。 - 第三方库:确保所有使用的第三方库都支持 64 位。
5. 隐私沙盒 (Privacy Sandbox)
为应对日益严格的隐私法规,Google 正在 Android 上推行隐私沙盒,旨在在保护用户隐私的同时,为开发者提供替代的广告和分析方案。
- 限制:减少对设备标识符(如 Advertising ID)的依赖。
- 新 API:提供基于群体兴趣的广告定位(Topics API)、私有计算来源(Protected Audience API)等。
- 开发者影响:需要重新评估应用的广告和用户追踪策略。
结语
Android 开发是一个充满活力且不断演进的领域。从最初基于 XML 和 Activity 的命令式 UI,到如今以 Kotlin 和 Jetpack Compose 为核心的声明式、现代化开发范式,技术栈已经发生了翻天覆地的变化。
要成为一名优秀的 Android 开发者,不仅需要精通本文所涵盖的核心技术------如四大组件、性能优化、Jetpack 架构组件、Kotlin 语言特性、Compose、安全和发布流程------更需要保持对新技术的敏锐嗅觉,如 Kotlin Multiplatform、跨设备体验和 AI 集成。
核心建议:
- 掌握基础:深刻理解 Android 系统的运行机制。
- 拥抱现代架构:熟练使用 MVVM、Jetpack 组件和 Kotlin 协程。
- 关注用户体验:性能、流畅度和安全性是应用的生命线。
- 持续学习:定期关注 Android Developers 官方博客、Google I/O 大会,学习最佳实践。