一、Java 基础与进阶1. 面向对象 (OOP)面向对象编程(OOP)是 Java 语言的基石,也是 Android 开发的构建范式。它通过封装、继承和多态三大特性,帮助我们构建出高内聚、低耦合、易于维护和扩展的应用。
1.1 面向对象的三大特性**1. 封装 (Encapsulation)
概念
封装是将对象的属性(数据)和行为(方法)捆绑成一个独立的单元(即类),并对外部隐藏其内部实现的细节。通过访问控制修饰符(private, protected, public)来限制对内部成员的直接访问。通常,我们提供公共的 getter 和 setter 方法来安全地读取和修改私有属性。
作用
数据安全与完整性:防止外部代码随意篡改对象的内部状态。例如,一个 BankAccount 对象的余额不能被随意设为负数。降低耦合度:使用者只依赖于类的公共接口,而无需关心其内部是如何工作的。这使得类的内部实现可以自由修改,只要接口不变,调用者代码就无需改动。提高内聚性:将相关的数据和操作这些数据的方法组织在同一个类中,使类的职责更加单一和明确。示例代码
代码语言:javascript复制// 一个封装良好的数据模型类
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 的一种。功能扩展与多态基础:子类可以在继承的基础上增加新功能,或改变父类的行为,为多态提供了前提。示例代码
代码语言:javascript复制// 父类 (基类)
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() 方法。实现解耦:上层代码依赖于抽象(父类或接口),而不是具体的实现,使得模块间的依赖关系更加松散。示例代码
代码语言:javascript复制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)。它代表一个不完整的、通用的基类,用于捕捉子类的共同特征(属性和行为),并为子类提供部分默认实现。抽象类强调的是类的继承关系和代码复用。
示例代码
代码语言:javascript复制// 一个典型的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)。它定义了一组行为规范,规定了实现该接口的类必须具备哪些能力,而不关心这些类的内部状态或具体实现。接口强调的是功能的定义和解耦。
示例代码
代码语言:javascript复制// 定义一个“可点击”的行为规范
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
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)
示例代码:集合的使用
代码语言:javascript复制import java.util.*;
public class CollectionExample {
public static void main(String[] args) {
// 1. List 的使用
List
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
uniqueNames.add("Alice");
uniqueNames.add("Bob");
uniqueNames.add("Alice"); // 重复元素,会被忽略
System.out.println("Unique names: " + uniqueNames); // [Bob, Alice] (顺序不定)
// 3. Map 的使用 (键值对)
Map
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
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}2.2 Android 中的集合最佳实践
选择合适的集合:根据数据的访问模式(查询、插入、删除)和是否需要排序、去重来选择最合适的集合。
初始化容量:如果能预估集合大小,应在创建时指定初始容量,避免频繁的数组扩容(对 ArrayList, HashMap 尤其重要)。
代码语言:javascript复制List
Map
注意线程安全:ArrayList, HashMap 等不是线程安全的。在多线程环境下,应使用 CopyOnWriteArrayList、ConcurrentHashMap,或使用同步机制。
3. 泛型 (Generics)泛型允许在定义类、接口和方法时使用类型参数,从而编写出更安全、更可重用的代码。
概念
泛型提供了编译时类型检查,并且无需进行显式的类型转换。
示例代码
代码语言:javascript复制// 一个泛型类
public class Box
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
stringBox.setContent("Hello");
String content = stringBox.getContent(); // 无需强制转换,类型安全
// 创建一个只能装 Integer 的 Box
Box
integerBox.setContent(123);
Integer number = integerBox.getContent(); // 无需强制转换
}
}在 Android 中的应用
集合框架:List
?:无界通配符,表示任何类型。 extends T>:上界通配符,表示 T 或 T 的子类型(只出不进,Producer)。 super T>:下界通配符,表示 T 或 T 的父类型(只进不出,Consumer)。代码语言:javascript复制// 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 接口的资源。代码语言:javascript复制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 分发流程 (伪代码)
代码语言:javascript复制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 方法中,根据滑动方向来决定是否拦截。
代码语言:javascript复制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) 来请求父容器不要拦截事件。
代码语言:javascript复制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
代码语言:javascript复制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 中定义:
代码语言:javascript复制
在构造函数中解析属性:
代码语言:javascript复制public 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;
}在布局文件中使用:
代码语言:javascript复制 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 示例: 代码语言:javascript复制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 示例: 代码语言:javascript复制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。 代码语言:javascript复制 省电模式:在 Android 9+ 上,前台服务也可能受到省电策略的影响,建议结合 WorkManager 使用。 3.3 BroadcastReceiver BroadcastReceiver 用于接收并响应系统或应用内发送的广播消息。 类型: 标准广播 (Normal Broadcast):异步、无序发送,所有接收者几乎同时收到。有序广播 (Ordered Broadcast):按优先级顺序发送,前面的接收者可以截断广播,阻止后面的接收者接收。粘性广播 (Sticky Broadcast):已废弃,不推荐使用。本地广播 (LocalBroadcastManager):仅在应用内部发送和接收,更安全、高效。动态注册与静态注册: 注册方式 代码位置 生命周期 适用场景 动态注册 在 Activity/Service 的 onCreate() 中注册,在 onDestroy() 中注销。 与注册组件的生命周期一致。 接收与特定 UI 状态相关的广播(如网络状态变化)。 静态注册 在 AndroidManifest.xml 中声明。 应用安装后一直有效,即使应用未运行。 接收系统级广播(如开机启动、充电状态变化)。 示例代码:动态注册网络状态变化广播: 代码语言:javascript复制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: 代码语言:javascript复制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 省略 }在其他应用中访问: 代码语言:javascript复制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 替代简单的单层嵌套。使用 适用场景: 错误提示页面。复杂的筛选条件面板。用户不常访问的功能模块。代码语言:javascript复制 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() 同步提交,有返回值。注意:不适合存储大量数据,否则读写会很慢。代码语言:javascript复制// 获取 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/ 代码语言:javascript复制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,是数据库的持有者。示例: 代码语言:javascript复制// 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 } // 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 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 示例: 代码语言:javascript复制// 1. 定义 API 接口 public interface ApiService { @GET("users/{id}") Call @POST("users") Call } // 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 call.enqueue(new Callback @Override public void onResponse(Call if (response.isSuccessful()) { User user = response.body(); // 处理成功结果 } } @Override public void onFailure(Call // 处理失败 } });3. 网络权限必须在 AndroidManifest.xml 中声明网络权限: 代码语言:javascript复制 4. 网络状态判断在发起网络请求前,应先检查网络连接状态。 代码语言:javascript复制ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork != null && activeNetwork.isConnected();六、Jetpack 组件详解Jetpack 是 Google 官方推出的一套库、工具和指南的集合,旨在帮助开发者遵循最佳实践,减少样板代码,并编写出向后兼容、稳定可靠的应用。它是现代 Android 开发的基石。 1. ViewModelViewModel 的核心作用是以生命周期感知的方式存储和管理与 UI 相关的数据。 解决的问题: 在传统的 Activity/Fragment 中,数据通常作为成员变量存储。当设备配置发生变化(如屏幕旋转)时,Activity 会被销毁并重新创建,导致所有成员变量丢失,需要重新请求网络或查询数据库,造成资源浪费和用户体验下降。 工作原理: ViewModel 的生命周期比 Activity/Fragment 更长。当 Activity 因配置变更被销毁时,系统会保留其关联的 ViewModel 实例。新创建的 Activity 会获取到同一个 ViewModel 实例,从而恢复之前的数据。使用示例: 代码语言:javascript复制// 1. 定义 ViewModel class UserViewModel : ViewModel() { private val _user = MutableLiveData val 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. LiveDataLiveData 是一个可观察的数据持有者类,它是生命周期感知的。 核心优势: 生命周期安全:只有当观察者的生命周期处于 STARTED 或 RESUMED 状态时,才会通知更新。避免了在非活跃状态下更新 UI 导致的崩溃。不会引起内存泄漏:当观察者(如 Activity)被销毁时,LiveData 会自动移除该观察者。使用场景: 与 ViewModel 结合,将数据从 ViewModel 传递到 UI 层。替代传统的事件总线(如 EventBus),用于组件间通信。基本用法: 代码语言:javascript复制// 1. 在 ViewModel 中定义 class MyViewModel : ViewModel() { private val _data = MutableLiveData val data: LiveData fun updateData(newData: String) { _data.value = newData // 触发通知 } } // 2. 在 UI 层观察 viewModel.data.observe(this, Observer { newData -> // 此处更新 UI,例如: textView.text = newData })Transformations: LiveData 提供了 Transformations 工具类,可以在不改变原始 LiveData 的情况下对其进行转换。 代码语言:javascript复制val userName: LiveData "${user.firstName} ${user.lastName}" }3. ViewBinding 和 DataBinding两者都旨在简化视图操作,但侧重点不同。 ViewBinding: 作用:为每个 XML 布局文件生成一个绑定类,提供对布局中所有 View 的类型安全引用。优点: 完全替代 findViewById(),避免 NullPointerException。编译时生成,无运行时开销。减少样板代码。缺点:不支持布局中的逻辑表达式。使用示例: 代码语言:javascript复制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 中的胶水代码。缺点:编译时间稍长,布局文件可能变得复杂。使用示例: 代码语言:javascript复制 name="viewmodel" type="com.example.MyViewModel" /> android:text="@{viewmodel.userName}" ... />