Skip to content

模块 07:网络请求与数据持久化

目标:掌握 Android 中的网络请求(OkHttp、Retrofit)和数据存储方案(SharedPreferences、SQLite、Room),学会处理异步数据流。

前端对照

前端Android说明
fetch() / axiosOkHttp / RetrofitHTTP 客户端
localStorageSharedPreferences简单键值存储
IndexedDBSQLite / Room结构化本地数据库
JSON.parse()Gson / MoshiJSON 解析
请求拦截器OkHttp Interceptor请求/响应中间件
CORS / Token 刷新Interceptor 链认证处理

1. OkHttp:底层 HTTP 客户端

1.1 基础请求

java
// 创建 OkHttpClient(通常是单例)
OkHttpClient client = new OkHttpClient.Builder()
    .connectTimeout(30, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .build();

// GET 请求
Request request = new Request.Builder()
    .url("https://api.example.com/users")
    .addHeader("Authorization", "Bearer " + token)
    .build();

// ⚠️ 网络请求必须在后台线程
client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // 请求失败
        Log.e("API", "请求失败", e);
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        if (response.isSuccessful()) {
            String body = response.body().string();
            // ⚠️ 回调在后台线程,更新 UI 需要切回主线程
            runOnUiThread(() -> {
                textView.setText(body);
            });
        }
    }
});

1.2 POST 请求

java
// JSON 请求体
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
String jsonBody = "{\"name\":\"张三\",\"email\":\"zhangsan@example.com\"}";

RequestBody body = RequestBody.create(jsonBody, JSON);
Request request = new Request.Builder()
    .url("https://api.example.com/users")
    .post(body)
    .build();

// 表单请求
RequestBody formBody = new FormBody.Builder()
    .add("username", "zhangsan")
    .add("password", "123456")
    .build();

Request formRequest = new Request.Builder()
    .url("https://api.example.com/login")
    .post(formBody)
    .build();

1.3 Interceptor(拦截器)

类似前端的请求/响应中间件(axios 拦截器):

java
// 添加公共 Header(类似 axios 的请求拦截器)
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(chain -> {
        Request original = chain.request();
        Request request = original.newBuilder()
            .header("Authorization", "Bearer " + getToken())
            .header("Content-Type", "application/json")
            .build();
        return chain.proceed(request);
    })
    // 日志拦截器(调试用)
    .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
    .build();

2. Retrofit:声明式 API 客户端

Retrofit 是建立在 OkHttp 之上的高层封装,使用接口注解定义 API,类似前端的 tRPC 或 OpenAPI 客户端生成器。

2.1 定义 API 接口

java
// 定义 API 接口
public interface ApiService {

    @GET("users")
    Call<List<User>> getUsers(@Query("page") int page, @Query("limit") int limit);

    @GET("users/{id}")
    Call<User> getUserById(@Path("id") int userId);

    @POST("users")
    Call<User> createUser(@Body CreateUserRequest request);

    @PUT("users/{id}")
    Call<User> updateUser(@Path("id") int id, @Body UpdateUserRequest request);

    @DELETE("users/{id}")
    Call<Void> deleteUser(@Path("id") int id);

    @POST("auth/login")
    Call<LoginResponse> login(@Body LoginRequest request);

    // 表单提交
    @FormUrlEncoded
    @POST("auth/login")
    Call<LoginResponse> loginForm(
        @Field("username") String username,
        @Field("password") String password
    );

    // 文件上传
    @Multipart
    @POST("upload")
    Call<UploadResponse> uploadFile(
        @Part MultipartBody.Part file,
        @Part("description") RequestBody description
    );
}

2.2 创建 Retrofit 实例

java
// 创建 Retrofit 实例(通常是单例)
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)   // 复用 OkHttp 客户端配置
    .addConverterFactory(GsonConverterFactory.create())  // JSON → Java 对象
    .build();

ApiService api = retrofit.create(ApiService.class);

2.3 发起请求

java
// 发起请求(回调方式)
Call<List<User>> call = api.getUsers(1, 20);

call.enqueue(new Callback<List<User>>() {
    @Override
    public void onResponse(Call<List<User>> call, Response<List<User>> response) {
        if (response.isSuccessful()) {
            List<User> users = response.body();
            // 更新 UI(注意线程切换)
            runOnUiThread(() -> {
                adapter.submitList(users);
            });
        } else {
            // 服务器返回错误状态码
            int code = response.code();
            Log.e("API", "Error: " + code);
        }
    }

    @Override
    public void onFailure(Call<List<User>> call, Throwable t) {
        // 网络错误或解析错误
        Log.e("API", "网络请求失败", t);
    }
});

// 取消请求(如页面销毁时)
call.cancel();

2.4 Token 刷新拦截器

java
// 自动刷新 Token(老项目中常见模式)
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new Authenticator() {
        @Override
        public Request authenticate(Route route, Response response) {
            // 当收到 401 时自动刷新 Token
            String newToken = refreshToken();
            if (newToken != null) {
                return response.request().newBuilder()
                    .header("Authorization", "Bearer " + newToken)
                    .build();
            }
            return null; // 刷新失败,放弃重试
        }
    })
    .build();

3. Gson:JSON 解析

3.1 数据模型定义

java
public class User {
    // @SerializedName:映射 JSON 字段名(当 Java 字段名与 JSON 不一致时)
    @SerializedName("user_id")
    private int id;

    @SerializedName("user_name")
    private String name;

    private String email;

    @SerializedName("is_vip")
    private boolean isVip;

    @SerializedName("created_at")
    private String createdAt;

    // 嵌套对象
    @SerializedName("address")
    private Address address;

    // Getters
    public int getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
    public boolean isVip() { return isVip; }
}

public class Address {
    private String city;
    private String street;
    // ...
}

// 通用响应包装(老项目中几乎都有)
public class ApiResponse<T> {
    @SerializedName("code")
    private int code;

    @SerializedName("message")
    private String message;

    @SerializedName("data")
    private T data;

    public boolean isSuccess() { return code == 0 || code == 200; }
    public T getData() { return data; }
    public String getMessage() { return message; }
}

3.2 手动使用 Gson

java
Gson gson = new Gson();

// JSON → Java 对象
String json = "{\"user_id\":1,\"user_name\":\"张三\",\"email\":\"zhang@example.com\"}";
User user = gson.fromJson(json, User.class);

// Java 对象 → JSON
String jsonOutput = gson.toJson(user);

// JSON 数组 → List
String jsonArray = "[{\"user_id\":1},{\"user_id\":2}]";
Type listType = new TypeToken<List<User>>(){}.getType();
List<User> users = gson.fromJson(jsonArray, listType);

4. SharedPreferences:简单键值存储

java
// 获取 SharedPreferences 实例
SharedPreferences prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE);

// 写入数据
SharedPreferences.Editor editor = prefs.edit();
editor.putString("user_name", "张三");
editor.putInt("user_id", 123);
editor.putBoolean("is_logged_in", true);
editor.putFloat("score", 95.5f);
editor.putStringSet("tags", new HashSet<>(Arrays.asList("java", "android")));
editor.apply();   // 异步写入(推荐)
// editor.commit(); // 同步写入(不推荐,会阻塞线程)

// 读取数据
String name = prefs.getString("user_name", "默认值");
int userId = prefs.getInt("user_id", 0);
boolean isLoggedIn = prefs.getBoolean("is_logged_in", false);

// 删除
editor.remove("user_name").apply();

// 清空
editor.clear().apply();

常见用途

  • 存储登录 Token
  • 保存用户设置(主题、语言等)
  • 记录首次启动标记
  • 缓存简单配置

4.1 实际项目中的 SharedPreferences 封装

老项目中通常会把 SharedPreferences 封装成工具类,避免到处写 key 字符串:

java
// 来自实际教程项目的封装模式
public class SharedUtil {
    private static SharedUtil mUtil;    // 单例实例
    private SharedPreferences mShared;  // SP 对象
    private SharedPreferences.Editor mEditor; // 编辑器

    // 单例模式获取实例
    public static SharedUtil getInstance(Context ctx) {
        if (mUtil == null) {
            mUtil = new SharedUtil(ctx);
        }
        return mUtil;
    }

    private SharedUtil(Context ctx) {
        // "share" 是 SP 文件名,MODE_PRIVATE 表示私有模式
        mShared = ctx.getSharedPreferences("share", Context.MODE_PRIVATE);
    }

    // 写入字符串
    public void writeString(String key, String value) {
        mEditor = mShared.edit();
        mEditor.putString(key, value);
        mEditor.apply();
    }

    // 读取字符串
    public String readString(String key, String defaultValue) {
        return mShared.getString(key, defaultValue);
    }

    // 写入布尔值
    public void writeBoolean(String key, boolean value) {
        mEditor = mShared.edit();
        mEditor.putBoolean(key, value);
        mEditor.apply();
    }

    // 读取布尔值
    public boolean readBoolean(String key, boolean defaultValue) {
        return mShared.getBoolean(key, defaultValue);
    }
}

// 使用方式(前端类比:类似封装 localStorage 工具类)
SharedUtil shared = SharedUtil.getInstance(this);
shared.writeString("user_name", "张三");
shared.writeBoolean("is_logged_in", true);
String name = shared.readString("user_name", "");

4.5 SQLiteOpenHelper:原生 SQLite 数据库(老项目)

在 Room 出现之前,Android 使用 SQLiteOpenHelper 直接操作 SQLite。老项目中大量存在:

java
// 来自实际教程项目 UserDBHelper.java
// 前端类比:类似手动封装 IndexedDB,Room 之前就是这种方式
public class UserDBHelper extends SQLiteOpenHelper {
    private static final String DB_NAME = "user.db";      // 数据库文件名
    private static final int DB_VERSION = 1;                // 数据库版本号
    private static UserDBHelper mHelper = null;             // 单例实例
    private SQLiteDatabase mDB = null;                      // 数据库连接
    public static final String TABLE_NAME = "user_info";    // 表名

    // 私有构造方法(单例模式)
    private UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    // 单例获取实例
    public static UserDBHelper getInstance(Context context, int version) {
        if (version > 0 && mHelper == null) {
            mHelper = new UserDBHelper(context, version);
        } else if (mHelper == null) {
            mHelper = new UserDBHelper(context);
        }
        return mHelper;
    }

    // 创建表(首次安装时执行)
    @Override
    public void onCreate(SQLiteDatabase db) {
        String createSql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
            + "_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,"
            + "name VARCHAR NOT NULL,"
            + "age INTEGER NOT NULL,"
            + "height INTEGER NOT NULL,"
            + "phone VARCHAR,"
            + "password VARCHAR"
            + ");";
        db.execSQL(createSql);
    }

    // 数据库升级时执行
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 版本升级时添加新列
        if (oldVersion < 2) {
            db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN phone VARCHAR");
        }
    }

    // 插入数据(类似 SQL INSERT)
    public long insert(UserInfo user) {
        ContentValues values = new ContentValues();
        values.put("name", user.getName());
        values.put("age", user.getAge());
        values.put("phone", user.getPhone());
        mDB = mHelper.getWritableDatabase();
        return mDB.insert(TABLE_NAME, null, values);
    }

    // 查询数据(类似 SQL SELECT)
    public List<UserInfo> query(String condition) {
        List<UserInfo> list = new ArrayList<>();
        mDB = mHelper.getReadableDatabase();
        String sql = "SELECT * FROM " + TABLE_NAME
            + (condition != null ? " WHERE " + condition : "");
        Cursor cursor = mDB.rawQuery(sql, null);
        while (cursor.moveToNext()) {
            UserInfo info = new UserInfo();
            info.setName(cursor.getString(1));   // 第 2 列是 name
            info.setAge(cursor.getInt(2));       // 第 3 列是 age
            info.setPhone(cursor.getString(4));  // 第 5 列是 phone
            list.add(info);
        }
        cursor.close();
        return list;
    }

    // 删除数据
    public int delete(String condition) {
        mDB = mHelper.getWritableDatabase();
        return mDB.delete(TABLE_NAME, condition, null);
    }
}

// 使用方式
UserDBHelper dbHelper = UserDBHelper.getInstance(this, 1);
dbHelper.openWriteLink();
dbHelper.insert(new UserInfo("张三", 25, "13800001234"));
List<UserInfo> users = dbHelper.query("age > 20");
dbHelper.closeLink();

Room Entity 注解示例(来自实际项目)

java
// 来自实际教程项目 BookInfo.java
// Room 用注解替代手动建表,类似 Prisma / TypeORM 的装饰器
@Entity  // 标记为 Room 实体,自动生成表
public class BookInfo {
    @PrimaryKey                    // 标记为主键
    @NonNull                       // 主键必须非空
    private String name;           // 书籍名称
    private String author;         // 作者
    private String press;          // 出版社
    private double price;          // 价格

    // Getters and Setters...
}

SQLiteOpenHelper vs Room:老项目用 SQLiteOpenHelper 手动写 SQL,新项目用 Room 注解。如果你看到 extends SQLiteOpenHelperrawQueryContentValues,这就是原生 SQLite 写法。

5. Room:SQLite ORM

Room 是 Google 官方的 SQLite 抽象层,类似前端的 Dexie.js 或 Prisma。

5.1 定义实体(Entity)

java
@Entity(tableName = "users")
public class UserEntity {
    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "user_name")
    private String name;

    @ColumnInfo(name = "email")
    private String email;

    @ColumnInfo(name = "avatar_url")
    private String avatarUrl;

    @ColumnInfo(name = "created_at")
    private long createdAt;

    // Getters and Setters...
}

5.2 定义 DAO(数据访问对象)

java
@Dao
public interface UserDao {

    @Query("SELECT * FROM users ORDER BY created_at DESC")
    List<UserEntity> getAllUsers();

    @Query("SELECT * FROM users WHERE id = :userId")
    UserEntity getUserById(int userId);

    @Query("SELECT * FROM users WHERE user_name LIKE '%' || :keyword || '%'")
    List<UserEntity> searchUsers(String keyword);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertUser(UserEntity user);

    @Insert
    void insertUsers(List<UserEntity> users);

    @Update
    void updateUser(UserEntity user);

    @Delete
    void deleteUser(UserEntity user);

    @Query("DELETE FROM users")
    void deleteAllUsers();

    // LiveData 响应式查询(数据变化时自动通知)
    @Query("SELECT * FROM users")
    LiveData<List<UserEntity>> observeAllUsers();
}

5.3 定义数据库

java
@Database(entities = {UserEntity.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();

    private static volatile AppDatabase INSTANCE;

    public static AppDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (AppDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        "app_database"
                    ).build();
                }
            }
        }
        return INSTANCE;
    }
}

5.4 使用

java
// ⚠️ Room 操作必须在后台线程
new Thread(() -> {
    UserDao dao = AppDatabase.getInstance(this).userDao();

    // 插入
    UserEntity user = new UserEntity();
    user.setName("张三");
    user.setEmail("zhangsan@example.com");
    dao.insertUser(user);

    // 查询
    List<UserEntity> users = dao.getAllUsers();

    // 响应式查询(使用 LiveData)
    runOnUiThread(() -> {
        dao.observeAllUsers().observe(this, users -> {
            // 数据库变化时自动回调
            adapter.submitList(users);
        });
    });
}).start();

5.5 数据库迁移

java
// 版本升级时添加迁移
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
    }
};

Room.databaseBuilder(context, AppDatabase.class, "app_database")
    .addMigrations(MIGRATION_1_2)
    .build();

6. 老项目中常见的网络库

6.1 技术栈识别

网络库依赖标识时期说明
OkHttp 3.x/4.xcom.squareup.okhttp3:okhttp2015+目前主流
Retrofit 2.xcom.squareup.retrofit2:retrofit2016+目前主流
Volleycom.android.volley:volley2013+Google 出品,老项目可能用
AsyncHttpClientcom.loopj.android:android-async-http2012+已停维护
HttpURLConnectionJava 内置原始最底层,极老项目

6.2 Volley 示例(老项目可能遇到)

java
// Volley 请求队列(单例)
RequestQueue queue = Volley.newRequestQueue(this);

// GET 请求
StringRequest request = new StringRequest(Request.Method.GET, url,
    response -> {
        // 成功(已在主线程)
        textView.setText(response);
    },
    error -> {
        // 失败
        textView.setText("请求失败");
    }
);
queue.add(request);

7. 常见数据流模式

7.1 Repository 模式

java
// 统一数据源(网络 + 本地缓存)
public class UserRepository {
    private ApiService api;
    private UserDao userDao;

    public UserRepository(ApiService api, UserDao userDao) {
        this.api = api;
        this.userDao = userDao;
    }

    // 先返回缓存,再请求网络
    public void getUsers(DataCallback<List<User>> callback) {
        // 先返回本地数据
        List<UserEntity> cached = userDao.getAllUsers();
        if (cached != null && !cached.isEmpty()) {
            callback.onSuccess(convertToUsers(cached));
        }

        // 再请求网络数据
        api.getUsers(1, 50).enqueue(new Callback<ApiResponse<List<User>>>() {
            @Override
            public void onResponse(Call<ApiResponse<List<User>>> call,
                                   Response<ApiResponse<List<User>>> response) {
                if (response.isSuccessful() && response.body().isSuccess()) {
                    List<User> users = response.body().getData();
                    // 保存到本地
                    userDao.insertUsers(convertToEntities(users));
                    callback.onSuccess(users);
                }
            }

            @Override
            public void onFailure(Call<ApiResponse<List<User>>> call, Throwable t) {
                if (cached == null || cached.isEmpty()) {
                    callback.onError(t);
                }
            }
        });
    }
}

前端开发者备忘

前端数据概念Android 对应备注
fetch(url, options)OkHttp client.newCall(request)HTTP 请求
axios.create({ baseURL })Retrofit.Builder().baseUrl()API 客户端
axios.interceptorsOkHttp Interceptor请求/响应中间件
response.dataresponse.body()响应体
JSON.parse()gson.fromJson()JSON 解析
localStorage.setItem()SharedPreferences.Editor.putString()简单存储
IndexedDB / sqlite.jsRoom / SQLite结构化存储
Prisma / TypeORMRoom(@Entity + @Dao)ORM
SWR / React QueryRepository + LiveData数据获取+缓存
AbortController.abort()call.cancel()取消请求

下一步

掌握了数据获取和存储后,接下来学习 模块 08:架构模式与导航,了解如何组织代码结构。

面向前端开发者的 Java Android 开发教程