Android 开发核心技术深度解析

文章目录

    • [一、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)来限制对内部成员的直接访问。通常,我们提供公共的 gettersetter 方法来安全地读取和修改私有属性。

作用

  • 数据安全与完整性 :防止外部代码随意篡改对象的内部状态。例如,一个 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)父类的方法以提供特定的实现。

作用

  • 代码复用:避免在多个类中重复编写相同的代码。子类可以直接使用父类的功能。
  • 建立层次结构 :更准确地对现实世界进行建模。例如,DogCat 都是 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 类型的参数,而实际传入 DogCat 对象,方法内部会自动调用对应对象的 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的数据
    }
}

抽象类的核心作用

  1. 代码复用 :为子类提供公共的属性和方法实现(如 setupToolbar())。
  2. 强制规范 :通过抽象方法强制要求子类必须实现某些核心功能(如 getLayoutId())。
  3. 定义模板:结合模板方法模式(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,都能响应点击
        }
    }
}

接口的核心作用

  1. 实现"多继承" :Java 类不支持多继承,但一个类可以实现多个接口,从而获得多种能力(如 Tile 同时实现了 ClickableDraggable)。
  2. 定义契约/规范:明确地规定了实现类必须提供的方法,是团队协作和模块化开发的基础。
  3. 解耦与依赖倒置 :客户端代码(如 UIController)依赖于接口 Clickable,而不是具体的 ButtonTile 类,大大降低了耦合度。
  4. 支持多态性:可以通过接口类型的引用来调用不同实现类的对象。
  5. 便于测试与扩展:易于编写 Mock 对象进行单元测试;添加新功能时,可以定义新接口,而不影响现有代码。

3. 抽象类与接口对比总结

特性 接口 (Interface) 抽象类 (Abstract Class)
实现/继承 子类使用 implements 关键字实现接口,必须提供所有抽象方法的实现(除非子类也是抽象的)。 子类使用 extends 关键字继承抽象类。如果子类不是抽象的,则必须实现所有抽象方法。
构造器 不能有构造器。 可以有构造器,用于初始化抽象类的成员变量。
成员变量 只能是 public static final 的常量。 可以是各种类型的成员变量(private, protected, public, static 等)。
方法 在 Java 8 之前,只能有 public abstract 方法。之后可以有 defaultstatic 方法。 可以有抽象方法和具体方法(任何访问修饰符)。
访问修饰符 接口方法默认是 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 尤其重要)。

    java 复制代码
    List<User> users = new ArrayList<>(100); // 预估有100个用户
    Map<String, Object> cache = new HashMap<>(32); // 预估缓存大小
  • 使用不可变集合 :对于不需要修改的集合,使用 Collections.unmodifiableList() 等方法创建不可变视图,提高安全性。

  • 注意线程安全ArrayList, HashMap 等不是线程安全的。在多线程环境下,应使用 CopyOnWriteArrayListConcurrentHashMap,或使用同步机制。

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>:上界通配符,表示 TT 的子类型(只出不进,Producer)。
  • <? super T>:下界通配符,表示 TT 的父类型(只进不出,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 核心步骤

  1. 继承 View 或其子类 :根据需求选择合适的基类,如 View, ViewGroup, TextView 等。
  2. 重写 onMeasure() 方法 :确定 View 的尺寸(onMeasure 是测量过程的核心)。
  3. 重写 onLayout() 方法 (仅 ViewGroup):确定子 View 的位置。
  4. 重写 onDraw() 方法 :使用 CanvasPaint 进行实际的绘制。
  5. 处理触摸事件 :重写 onTouchEvent() 等方法,实现交互逻辑。
  6. 提供自定义属性 (可选):通过 attrs.xml 定义属性,方便在 XML 中配置。

2.2 测量 (onMeasure)

onMeasure 方法接收两个 MeasureSpec 参数(widthMeasureSpecheightMeasureSpec),它们包含了父容器对当前 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 属性。

  1. res/values/attrs.xml 中定义

    xml 复制代码
    <resources>
        <declare-styleable name="CircleView">
            <attr name="circleColor" format="color" />
            <attr name="circleRadius" format="dimension" />
        </declare-styleable>
    </resources>
  2. 在构造函数中解析属性

    java 复制代码
    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;
    }
  3. 在布局文件中使用

    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+ 限制 :后台服务的启动受到严格限制。对于非前台服务,应考虑使用 JobSchedulerWorkManager

  • 省电模式 :在 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 中注册了 BroadcastReceiverSensorManager 监听器等,但未在 onDestroy() 中注销。 在组件的生命周期结束时(如 onDestroy()onStop()),务必调用 unregisterReceiver()unregisterListener()
无限增长的集合 集合(如 ListMap)不断添加对象,但没有相应的清理机制。 定期清理不再需要的对象,或使用弱引用集合(如 WeakHashMap)。
Bitmap 使用不当 加载过大的 Bitmap,或未及时调用 recycle() 使用图片加载库(如 Glide、Picasso)自动管理;根据 ImageView 大小进行缩放(inSampleSize);及时回收不再使用的 Bitmap。

工具检测

  • Android Studio Profiler:实时监控内存分配。
  • LeakCanary:一个强大的开源库,能自动检测并报告内存泄漏。

1.2 内存抖动 (Memory Churn)

内存抖动指在短时间内频繁地创建和销毁大量对象,导致频繁触发 GC。GC 会暂停所有线程(Stop-the-World),造成界面卡顿。

避免方法

  • 避免在 onDraw() 等高频调用的方法中创建对象 :如 PaintRect 等对象应在类初始化时创建,作为成员变量复用。
  • 使用对象池 (Object Pooling) :对于频繁创建销毁的对象(如 MessageBitmap),可以使用 HandlerMessage Pool 或自定义对象池。
  • 使用基本数据类型 :在性能敏感的代码中,优先使用 int 而不是 Integer

2. 布局优化

复杂的布局层级会显著增加 measurelayoutdraw 的耗时,导致界面渲染缓慢。

2.1 减少嵌套层级

深层嵌套的 ViewGroup 会增加测量和布局的计算量。

解决方案

  • 使用更轻量的 ViewGroup
    • ConstraintLayout 替代深层嵌套的 LinearLayoutRelativeLayoutConstraintLayout 能实现复杂布局且性能优异。
    • 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),应优先使用应用专属目录或 MediaStore API。

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 对象映射为数据库表。
  • LiveDataFlow 集成,实现数据变更自动通知 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 获取实例。
  • 配合 LiveDataStateFlow 实现数据驱动 UI。
  • viewModelScope:内置的 CoroutineScope,会在 ViewModel 被清除时自动取消协程,防止内存泄漏。

2. LiveData

LiveData 是一个可观察的数据持有者类,它是生命周期感知的。

核心优势

  • 生命周期安全 :只有当观察者的生命周期处于 STARTEDRESUMED 状态时,才会通知更新。避免了在非活跃状态下更新 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 复制代码
<!-- 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

Navigation 组件提供了一个统一的框架来实现应用内导航。

核心概念

  • Navigation Graph (导航图):一个 XML 文件,定义了应用的所有目的地(Destinations)以及它们之间的跳转关系。
  • Destination:导航的目标,通常是 Fragment,也可以是 Activity。
  • NavHost :一个容器,用于在其中显示导航图中的目的地。常用的是 NavHostFragment
  • NavController:负责管理导航后退栈的核心类。

优势

  • 可视化导航流程:在 Android Studio 中可以图形化查看导航图。
  • 处理常见导航模式:自动处理 Fragment 的替换、入栈出栈。
  • 类型安全的参数传递:通过 Safe Args 插件,可以类型安全地在目的地之间传递参数。
  • 集成底部导航栏、抽屉菜单等

基本用法

  1. 创建 res/navigation/nav_graph.xml
  2. 在主 Activity 布局中添加 NavHostFragment
  3. 使用 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 系统中,利用硬件级安全模块(如果可用)进行保护。

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:readPermissionandroid: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 TypeProduct 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 的向导创建。

    bash 复制代码
    keytool -genkey -v -keystore my-upload-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias
  • 配置签名 :在 build.gradle 中配置 signingConfigs

    groovy 复制代码
    android {
        ...
        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 发布流程

  1. 创建开发者账号:支付一次性注册费(25美元)。
  2. 准备发布内容
    • APK 或 AAB :生成发布版本的包。强烈推荐使用 Android App Bundle (.aab)。AAB 不是直接安装的文件,而是 Google Play 用来为不同设备生成优化过的 APK(如按 ABI、屏幕密度分包)的格式,可以显著减小用户下载的体积。
    • 应用图标:512x512 PNG。
    • 屏幕截图:不同设备尺寸的截图。
    • 应用描述:标题、短描述、长描述。
    • 分类、内容分级等。
  3. 创建应用:在 Google Play Console 中创建新应用。
  4. 内部测试/封闭式测试:先发布到测试轨道,邀请特定用户进行测试。
  5. 开放式测试:发布到更大的测试群体。
  6. 生产环境发布:当测试完成,将版本从测试轨道提升到生产轨道,向所有用户发布。
  7. 监控与迭代:通过 Play Console 监控崩溃报告、用户反馈、下载量等数据,持续迭代更新。

3. 持续集成与持续部署 (CI/CD)

CI/CD 是自动化构建、测试和部署流程的实践,旨在提高开发效率和软件质量。

核心流程

  1. 提交代码:开发者将代码推送到 Git 仓库(如 GitHub, GitLab)。
  2. 触发流水线 (Pipeline):代码推送事件自动触发 CI/CD 流水线。
  3. 构建 :在 CI 服务器上执行 ./gradlew assembleRelease 生成 AAB/APK。
  4. 测试 :运行单元测试 (test) 和 Android 测试 (connectedAndroidTest)。
  5. 代码质量检查:运行 Lint、Ktlint 等静态分析工具。
  6. 部署
    • 测试环境:将构建产物自动上传到测试分发平台(如 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 集成。

核心建议

  1. 掌握基础:深刻理解 Android 系统的运行机制。
  2. 拥抱现代架构:熟练使用 MVVM、Jetpack 组件和 Kotlin 协程。
  3. 关注用户体验:性能、流畅度和安全性是应用的生命线。
  4. 持续学习:定期关注 Android Developers 官方博客、Google I/O 大会,学习最佳实践。
相关推荐
盼哥PyAI实验室4 小时前
Python 正则表达式实战 + 详解:从匹配QQ邮箱到掌握核心语法
python·mysql·正则表达式
nju_spy4 小时前
力扣每日一题(四)线段树 + 树状数组 + 差分
数据结构·python·算法·leetcode·面试·线段树·笔试
程序员烧烤4 小时前
【Java基础14】函数式接口、lamba表达式、方法引用一网打尽(下)
java·开发语言
lzq6034 小时前
Python虚拟环境全指南:venv与conda对比与实践
开发语言·python·conda
QING6184 小时前
Jetpack Compose 条件布局与 Layout 内在测量详解
android·kotlin·android jetpack
一念杂记4 小时前
没网太崩溃!手机电脑网络共享,简单几步搞定网络共享,再也不用为没网担忧~
android·windows
Candice_jy4 小时前
vscode运行ipynb文件:使用docker中的虚拟环境
服务器·ide·vscode·python·docker·容器·编辑器
小年糕是糕手4 小时前
【数据结构】常见的排序算法 -- 插入排序
c语言·开发语言·数据结构·学习·算法·leetcode·排序算法
星释5 小时前
Rust 练习册 4:Deref trait 与智能指针
开发语言·后端·rust