Android第十四次面试总结

OkHttp中获取数据与操作数据

一、数据获取核心机制

1. ​同步请求(阻塞式)​
java 复制代码
// 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. ​异步请求(非阻塞)​
java 复制代码
// 构建请求(同同步示例)
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)​​:

java 复制代码
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());

响应缓存处理​:

java 复制代码
// 创建带缓存的客户端
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()
}

高级延时场景 - 定时轮询:​

Kotlin 复制代码
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);

协程结构化并发:​

Kotlin 复制代码
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 实现:​

Kotlin 复制代码
// 后台线程
new Thread(() -> {
    String data = getData();
    handler.post(() -> textView.setText(data));
}).start();

LiveData 替代方案:​

Kotlin 复制代码
// 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 解决方案:​

Kotlin 复制代码
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 事件总线:​

Kotlin 复制代码
// 单次事件包装器
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
驱动力 用户操作驱动 数据变更驱动
更新方式 手动命令式更新 自动声明式更新
测试性 需模拟视图 独立测试业务逻辑
代码量 冗余胶水代码多 简洁易维护
相关推荐
Yang-Never1 小时前
Kotlin协程 -> Job.join() 完整流程图与核心源码分析
android·开发语言·kotlin·android studio
资深前端之路6 小时前
react 面试题 react 有什么特点?
前端·react.js·面试·前端框架
拉不动的猪6 小时前
回顾vue中的Props与Attrs
前端·javascript·面试
一笑的小酒馆7 小时前
Android性能优化之截屏时黑屏卡顿问题
android
boonya9 小时前
Redis核心原理与面试问题解析
数据库·redis·面试
懒人村杂货铺9 小时前
Android BLE 扫描完整实战
android
在未来等你9 小时前
Kafka面试精讲 Day 8:日志清理与数据保留策略
大数据·分布式·面试·kafka·消息队列
沐怡旸10 小时前
【算法--链表】114.二叉树展开为链表--通俗讲解
算法·面试
白水清风11 小时前
关于Js和Ts中类(class)的知识
前端·javascript·面试