深入浅出安卓K线缓存实践

118 阅读4分钟

深入浅出安卓K线缓存实践

什么是K线数据?

K线(蜡烛图)是金融交易中最常用的图表类型之一,它显示了一段时间内的开盘价、收盘价、最高价和最低价。在股票、外汇、加密货币等交易应用中,K线数据是核心展示内容。

为什么需要缓存K线数据?

  1. 减少网络请求:K线数据通常从服务器获取,频繁请求会增加服务器负担和用户流量消耗
  2. 提升用户体验:本地缓存可以实现秒开,避免每次等待网络加载
  3. 离线访问:在网络不稳定或离线时仍能查看历史数据
  4. 节省成本:减少API调用次数,特别是付费API

安卓K线缓存实现方案

1. 内存缓存(一级缓存)

// 使用LruCache实现内存缓存
private LruCache<String, List<KLineItem>> memoryCache;

// 初始化
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8; // 使用最大内存的1/8
memoryCache = new LruCache<String, List<KLineItem>>(cacheSize) {
    @Override
    protected int sizeOf(String key, List<KLineItem> value) {
        // 计算每条K线数据占用的内存大小
        return value.size() * 20; // 估算值,根据实际数据结构调整
    }
});

// 存入缓存
memoryCache.put(cacheKey, kLineData);

// 读取缓存
List<KLineItem> cachedData = memoryCache.get(cacheKey);

2. 磁盘缓存(二级缓存)

使用Room数据库实现持久化缓存:

// 定义K线数据实体
@Entity(tableName = "kline_data")
public class KLineEntity {
    @PrimaryKey
    public String symbol;      // 交易对
    public String interval;    // 时间间隔(1m,15m,1h等)
    public long timestamp;     // 时间戳
    public double open;        // 开盘价
    public double close;       // 收盘价
    public double high;        // 最高价
    public double low;         // 最低价
    public double volume;      // 成交量
}

// 定义DAO接口
@Dao
public interface KLineDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(List<KLineEntity> entities);
    
    @Query("SELECT * FROM kline_data WHERE symbol = :symbol AND interval = :interval ORDER BY timestamp ASC")
    List<KLineEntity> getKLineData(String symbol, String interval);
    
    @Query("DELETE FROM kline_data WHERE symbol = :symbol AND interval = :interval")
    void deleteBySymbolAndInterval(String symbol, String interval);
}

// 使用示例
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "kline-db").build();
KLineDao kLineDao = db.kLineDao();

// 保存数据
kLineDao.insertAll(entities);

// 读取数据
List<KLineEntity> cachedData = kLineDao.getKLineData("BTCUSDT", "1h");

3. 缓存策略设计

public class KLineCacheManager {
    // 获取K线数据的完整流程
    public Observable<List<KLineItem>> getKLineData(String symbol, String interval, long startTime, long endTime) {
        // 1. 先从内存缓存获取
        String memoryKey = generateMemoryKey(symbol, interval, startTime, endTime);
        List<KLineItem> memoryData = memoryCache.get(memoryKey);
        if (memoryData != null) {
            return Observable.just(memoryData);
        }
        
        // 2. 从磁盘缓存获取
        return Observable.fromCallable(() -> {
            List<KLineEntity> diskData = kLineDao.getKLineData(symbol, interval);
            if (diskData != null && !diskData.isEmpty()) {
                List<KLineItem> convertedData = convertToKLineItems(diskData);
                // 存入内存缓存
                memoryCache.put(memoryKey, convertedData);
                return convertedData;
            }
            return null;
        }).flatMap(diskData -> {
            if (diskData != null) {
                return Observable.just(diskData);
            }
            
            // 3. 从网络请求
            return apiService.getKLineData(symbol, interval, startTime, endTime)
                .doOnNext(networkData -> {
                    // 保存到磁盘
                    List<KLineEntity> entities = convertToEntities(networkData, symbol, interval);
                    kLineDao.insertAll(entities);
                    
                    // 保存到内存
                    memoryCache.put(memoryKey, networkData);
                });
        });
    }
    
    // 生成内存缓存key
    private String generateMemoryKey(String symbol, String interval, long startTime, long endTime) {
        return symbol + "_" + interval + "_" + startTime + "_" + endTime;
    }
}

4. 缓存更新策略

  1. 时间过期策略

    • 短期K线(1m/5m/15m):缓存5-10分钟
    • 中期K线(1h/4h):缓存1-2小时
    • 长期K线(1d/1w):缓存24小时
  2. 增量更新

    • 只请求最新时间段的数据,与本地缓存合并
    • 避免全量刷新
// 增量更新示例
public void updateKLineData(String symbol, String interval) {
    // 获取本地最新数据的时间
    long latestTime = getLatestCachedTime(symbol, interval);
    
    // 只请求最新时间之后的数据
    apiService.getKLineData(symbol, interval, latestTime, System.currentTimeMillis())
        .subscribe(newData -> {
            // 合并新旧数据
            List<KLineItem> mergedData = mergeData(cachedData, newData);
            
            // 更新缓存
            memoryCache.put(generateMemoryKey(symbol, interval), mergedData);
            kLineDao.insertAll(convertToEntities(newData, symbol, interval));
        });
}

优化技巧

  1. 数据压缩

    • 使用Protocol Buffers或FlatBuffers替代JSON
    • 对数值进行精度处理(如价格保留4位小数)
  2. 分片缓存

    • 按时间范围分片存储,避免加载全部历史数据
    • 实现按需加载
  3. 智能预加载

    • 根据用户浏览习惯预加载可能查看的K线数据
    • 在WiFi环境下预加载更多数据
  4. 内存优化

    • 使用基本类型数组替代对象列表
    • 对不再显示的数据及时释放内存

常见问题解决

  1. 数据不一致

    • 实现版本控制或时间戳校验
    • 提供手动刷新按钮
  2. 缓存膨胀

    • 设置最大缓存大小
    • 定期清理过期数据
  3. 多周期同步

    • 当1小时K线更新时,同步更新4小时和日K数据
    • 建立K线数据间的关联关系

总结

安卓K线缓存实现需要结合内存缓存和磁盘缓存,设计合理的缓存策略和更新机制。通过多级缓存、智能预加载和数据压缩等技术,可以在保证数据及时性的同时,大幅提升应用性能和用户体验。实际开发中,还需要根据具体业务需求和数据特点进行调整优化。