OkHttp中获取数据与操作数据
一、数据获取核心机制
1. 同步请求(阻塞式)
// 1. 创建HTTP客户端(全局应复用实例)
OkHttpClient client = new OkHttpClient();
// 2. 构建请求对象(GET示例)
Request request = new Request.Builder()
.url("https://api.example.com/data") // 必填目标URL
.header("User-Agent", "MyApp/1.0") // 添加自定义请求头
.get() // 明确指定GET方法
.build();
// 3. 执行同步请求(阻塞当前线程)
try (Response response = client.newCall(request).execute()) {
// 4. 验证响应状态码 (200-299范围表示成功)
if (response.isSuccessful()) {
// 5. 提取响应体数据(string()只能调用一次)
String rawData = response.body().string();
// 6. 数据处理逻辑
processData(rawData);
} else {
// 处理服务器错误(如404, 500等)
System.err.println("请求失败:" + response.code());
}
} catch (IOException e) {
// 7. 处理网络错误(超时、DNS解析失败等)
e.printStackTrace();
}
关键点说明:
execute()
:同步调用会阻塞当前线程- 使用场景:后台任务(日志上报、文件下载)
- 注意事项:
- 响应体
.string()
只能调用一次(后续调用返回空) - 必须使用try-with-resources确保资源释放
- Android需在子线程执行
- 响应体
2. 异步请求(非阻塞)
// 构建请求(同同步示例)
Request request = ...;
// 发起异步请求
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 1. 网络层失败处理(如无网络、超时)
Log.e("Network", "请求失败: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
// 2. 注意:此回调在异步线程执行!
try {
if (response.isSuccessful()) {
// 3. 获取原始响应数据
String responseData = response.body().string();
// 4. 切回主线程处理数据(UI操作必须主线程)
runOnUiThread(() -> {
updateUI(responseData); // 更新UI组件
saveToLocal(responseData); // 数据持久化
});
} else {
// 5. 处理业务错误(如400 Bad Request)
Log.w("API", "业务错误:" + response.code());
}
} finally {
// 6. 确保关闭响应资源(防止内存泄漏)
response.close();
}
}
});
// Android线程切换工具方法
private void runOnUiThread(Runnable action) {
new Handler(Looper.getMainLooper()).post(action);
}
核心优势:
- 非阻塞调用:避免主线程卡顿
- 自动线程切换:网络IO在工作线程执行
- 生命周期安全:支持请求取消(
call.cancel()
)
二、数据操作深度解析
1. JSON数据解析
场景:解析用户数据接口响应
{
"user": {
"id": 123,
"name": "张伟",
"email": "zhangwei@example.com",
"created_at": "2023-08-15T10:30:00Z"
}
}
方案1:原生JSONObject解析(适合简单结构)
String json = response.body().string();
try {
JSONObject root = new JSONObject(json);
JSONObject user = root.getJSONObject("user");
int id = user.getInt("id");
String name = user.getString("name");
String email = user.getString("email");
// 日期字符串转换
String dateStr = user.getString("created_at");
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
Date createDate = format.parse(dateStr);
// 构建业务对象
User userObj = new User(id, name, email, createDate);
} catch (JSONException | ParseException e) {
// 处理格式错误或字段缺失
}
方案2:Gson自动映射(推荐复杂结构)
// 实体类定义
public class User {
private int id;
private String name;
private String email;
@SerializedName("created_at") // 自定义字段映射
private Date createDate;
// Getters & Setters
}
// 解析操作
Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateDeserializer()) // 自定义日期解析
.create();
// 直接映射JSON到Java对象
User user = gson.fromJson(json, User.class);
Gson日期转换器示例:
class DateDeserializer implements JsonDeserializer<Date> {
private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
@Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
try {
return dateFormat.parse(json.getAsString());
} catch (ParseException e) {
throw new JsonParseException("日期格式错误", e);
}
}
}
2. XML数据解析
场景:解析RSS订阅源
<rss>
<channel>
<item>
<title>OkHttp 4.9发布</title>
<link>https://example.com/news/123</link>
<pubDate>Wed, 15 Aug 2023 08:00:00 GMT</pubDate>
</item>
</channel>
</rss>
Pull解析实现:
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(xmlData));
List<NewsItem> newsList = new ArrayList<>();
NewsItem currentItem = null;
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
String tagName = parser.getName();
if ("item".equals(tagName)) {
currentItem = new NewsItem();
} else if (currentItem != null) {
// 提取标签内文本
if ("title".equals(tagName)) {
currentItem.setTitle(parser.nextText());
} else if ("link".equals(tagName)) {
currentItem.setLink(parser.nextText());
} else if ("pubDate".equals(tagName)) {
currentItem.setPublishDate(parseRssDate(parser.nextText()));
}
}
break;
case XmlPullParser.END_TAG:
if ("item".equals(parser.getName())) {
newsList.add(currentItem);
currentItem = null;
}
break;
}
eventType = parser.next();
}
3. 数据加工处理
类型安全转换:
// 带错误恢复的类型转换
public int safeParseInt(String value, int defaultValue) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
logError("数字格式错误: " + value);
return defaultValue;
}
}
数据过滤(Java Stream API):
List<User> users = getUsersFromResponse();
// 筛选VIP用户并提取邮箱列表
List<String> vipEmails = users.stream()
.filter(u -> u.getLevel() >= 3) // VIP等级条件
.map(User::getEmail) // 提取邮箱字段
.filter(email -> email.contains("@")) // 邮箱有效性检查
.collect(Collectors.toList());
响应缓存处理:
// 创建带缓存的客户端
File cacheDir = new File(getCacheDir(), "okhttp_cache");
long cacheSize = 50 * 1024 * 1024; // 50MB
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(cacheDir, cacheSize))
.addNetworkInterceptor(new CacheControlInterceptor()) // 缓存控制
.build();
// 自定义缓存策略拦截器
static class CacheControlInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response original = chain.proceed(chain.request());
return original.newBuilder()
.header("Cache-Control", "public, max-age=" + 3600) // 缓存1小时
.build();
}
}
Handler替代方案
一、核心替代场景对比
功能 | Handler 实现 | 协程替代方案 | LiveData 替代方案 |
---|---|---|---|
延时操作 | postDelayed() | delay() | 无需直接替代 |
主线程切换 | post(Runnable) | withContext(Dispatchers.Main) | postValue() / setValue() |
定时任务 | sendMessageDelayed() | whileActive + delay() | MutableLiveData + ViewModel |
资源安全释放 | 手动移除回调 | 结构化并发自动取消 | 自动生命周期感知 |
数据传递 | Message.obj | 协程返回值 / Channel | LiveData 观察模式 |
二、Kotlin 协程替代方案详解
1. 主线程切换 (替代 post())
Handler 实现:
handler.post(() -> {
// 在主线程执行
textView.setText("Updated");
});
协程替代方案:
// 在任何协程上下文中
lifecycleScope.launch(Dispatchers.Default) {
// 后台操作
val result = fetchData()
// 切换到主线程(替代Handler.post())
withContext(Dispatchers.Main) {
textView.text = result
button.isEnabled = true
}
}
优势:
- 顺序编程模型,避免回调嵌套
- 自动处理线程池资源
- 与生命周期自动绑定
2. 延时操作 (替代 postDelayed())
Handler 实现:
handler.postDelayed(() -> {
showNotification();
}, 3000);
协程替代方案:
lifecycleScope.launch {
// 非阻塞延迟 (不会占用线程资源)
delay(3000)
// 自动在主线程执行
showNotification()
}
高级延时场景 - 定时轮询:
private var pollingJob: Job? = null
fun startPolling() {
pollingJob = lifecycleScope.launch {
while (isActive) { // 结构化并发感知取消
fetchUpdates()
delay(60_000) // 每分钟执行一次
}
}
}
fun stopPolling() {
pollingJob?.cancel() // 取消定时任务(替代removeCallbacks())
}
3. 复杂任务管理 (替代多个 Runnable)
传统 Handler 问题:
Handler handler = new Handler();
handler.post(task1);
handler.post(task2);
handler.postDelayed(task3, 1000);
协程结构化并发:
lifecycleScope.launch {
// 同时发起多个任务
val deferred1 = async { loadUserProfile() }
val deferred2 = async { loadUserOrders() }
// 等待所有任务完成
val (profile, orders) = awaitAll(deferred1, deferred2)
// 处理结果(自动在主线程)
updateUI(profile, orders)
// 顺序执行多个任务
withContext(Dispatchers.IO) {
saveToLocal(profile)
uploadAnalytics(orders)
}
}
三、LiveData 替代方案详解
1. 主线程数据更新 (替代 Handler 的 UI 更新)
Handler 实现:
// 后台线程
new Thread(() -> {
String data = getData();
handler.post(() -> textView.setText(data));
}).start();
LiveData 替代方案:
// ViewModel中
class MyViewModel : ViewModel() {
private val _uiData = MutableLiveData<String>()
val uiData: LiveData<String> = _uiData
fun loadData() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.fetchData()
_uiData.postValue(result) // 自动切换到主线程
}
}
}
// Activity中
viewModel.uiData.observe(this) { data ->
textView.text = data // 已在主线程
}
2. 生命周期感知 (替代手动回调移除)
传统 Handler 的问题:
// 可能泄漏Activity
handler.postDelayed(() -> {
if (getActivity() != null) {
updateUI(); // 危险!可能访问已销毁的Activity
}
}, 10000);
LiveData 解决方案:
class SafeViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
init {
viewModelScope.launch {
while (true) {
delay(10000)
val newData = fetchPeriodicData()
_data.postValue(newData)
}
}
}
}
// Activity中 - 自动处理生命周期
viewModel.data.observe(this) { data ->
// 只有Activity处于活跃状态时才会触发
updateUI(data)
}
3. 事件总线替代方案 (单次事件处理)
传统 Handler 广播问题:
// 多个Handler处理同一消息
handler.sendMessage(Message.obtain().apply {
what = MSG_UPDATE
obj = data
});
LiveData 事件总线:
// 单次事件包装器
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner) { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
}
// ViewModel中使用
class EventViewModel : ViewModel() {
private val _networkEvent = SingleLiveEvent<String>()
val networkEvent: LiveData<String> = _networkEvent
fun triggerEvent() {
_networkEvent.value = "Event occurred at ${System.currentTimeMillis()}"
}
}
MVVM 与 MVC 数据传输流向终极总结
MVC 数据流向
单向环形闭环
用户操作 → View → Controller → Model → Controller → View更新
- 核心特征:Controller 作为中枢手动协调一切
- 致命缺陷:View 与 Model 隐性耦合,Controller 臃肿
- Android 现状:已被 Google 官方废弃
MVVM 数据流向
双向自动通道
用户操作 → View → ViewModel ↔ Model
Model变更 → ViewModel → 自动 → View更新
- 革命性突破:
- 数据绑定实现自动同步
- ViewModel 完全解耦视图
- 单向数据流确保可预测性
- Android 未来:
Jetpack 官方架构(ViewModel + LiveData/Flow)
本质区别
MVC | MVVM | |
---|---|---|
驱动力 | 用户操作驱动 | 数据变更驱动 |
更新方式 | 手动命令式更新 | 自动声明式更新 |
测试性 | 需模拟视图 | 独立测试业务逻辑 |
代码量 | 冗余胶水代码多 | 简洁易维护 |