跨设备电影排行榜:基于豆瓣API的热门电影展示与多设备同步系统 原创
跨设备电影排行榜:基于豆瓣API的热门电影展示与多设备同步系统
一、项目概述
本文实现一个基于豆瓣API的电影排行榜应用,该应用能够获取并展示热门电影信息,并借鉴《鸿蒙跨端U同步》中的状态同步机制,实现用户收藏状态在多设备间的实时同步。系统包含电影列表展示、电影详情查看、收藏功能以及跨设备同步等核心功能。
二、架构设计
±--------------------+       ±--------------------+
电影列表界面       <-----> 收藏同步服务
(MovieListSlice) (FavoriteSyncService)
±---------±---------+       ±---------±---------+
±---------v----------+       ±---------v----------+
豆瓣API客户端      分布式数据管理
(DoubanClient) (DistributedData)
±---------±---------+       ±---------±---------+
±---------v-----------------------------v----------+
HarmonyOS基础服务                  |
±--------------------------------------------------+
三、核心代码实现
电影数据模型
public class Movie {
private String id;          // 电影ID
private String title;      // 电影标题
private String originalTitle; // 原始标题
private double rating;      // 评分
private String imageUrl;    // 海报图片URL
private String year;        // 上映年份
private List<String> genres;// 类型
private String summary;     // 简介
private boolean isFavorite; // 是否收藏
// JSON解析构造方法
public Movie(JSONObject json) throws JSONException {
    this.id = json.getString("id");
    this.title = json.getString("title");
    this.originalTitle = json.optString("original_title", title);
    this.rating = json.optDouble("rating", 0);
    
    JSONObject images = json.getJSONObject("images");
    this.imageUrl = images.getString("medium");
    
    this.year = json.optString("year", "");
    
    this.genres = new ArrayList<>();
    JSONArray genresArray = json.optJSONArray("genres");
    if (genresArray != null) {
        for (int i = 0; i < genresArray.length(); i++) {
            genres.add(genresArray.getString(i));
}
    this.summary = json.optString("summary", "");
    this.isFavorite = false;
// getter方法…
// toJson方法...
豆瓣API客户端实现
public class DoubanClient {
private static final String TAG = “DoubanClient”;
private static final String API_BASE = “https://api.douban.com/v2/movie/”;
private static final String API_KEY = “0df993c66c0c636e29ecbb5344252a4a”; // 示例API Key
// 获取正在热映的电影
public static List<Movie> getInTheaters() {
    String url = API_BASE + "in_theaters?apikey=" + API_KEY;
    return fetchMovies(url);
// 获取Top250电影
public static List<Movie> getTop250(int start, int count) {
    String url = API_BASE + "top250?apikey=" + API_KEY + 
        "&start=" + start + "&count=" + count;
    return fetchMovies(url);
// 获取电影详情
public static Movie getMovieDetail(String movieId) {
    String url = API_BASE + "subject/" + movieId + "?apikey=" + API_KEY;
    try {
        String jsonStr = HttpUtils.get(url);
        return new Movie(new JSONObject(jsonStr));
catch (Exception e) {
        HiLog.error(TAG, "Failed to fetch movie detail: " + e.getMessage());
        return null;
}
private static List<Movie> fetchMovies(String url) {
    List<Movie> movies = new ArrayList<>();
    try {
        String jsonStr = HttpUtils.get(url);
        JSONObject json = new JSONObject(jsonStr);
        JSONArray subjects = json.getJSONArray("subjects");
        
        for (int i = 0; i < subjects.length(); i++) {
            movies.add(new Movie(subjects.getJSONObject(i)));
} catch (Exception e) {
        HiLog.error(TAG, "Failed to fetch movies: " + e.getMessage());
return movies;
}
收藏同步服务实现
public class FavoriteSyncService {
private static final String TAG = “FavoriteSyncService”;
private static final String SYNC_CHANNEL = “movie_favorite_sync”;
private static FavoriteSyncService instance;
private DistributedDataManager dataManager;
private Map<String, FavoriteListener> listeners = new HashMap<>();
private Set<String> localFavorites = new HashSet<>();
private FavoriteSyncService(Context context) {
    this.dataManager = DistributedDataManagerFactory.getInstance()
        .createDistributedDataManager(context);
    initDataListener();
    loadLocalFavorites();
public static synchronized FavoriteSyncService getInstance(Context context) {
    if (instance == null) {
        instance = new FavoriteSyncService(context);
return instance;
private void initDataListener() {
    dataManager.registerDataChangeListener(SYNC_CHANNEL, new DataChangeListener() {
        @Override
        public void onDataChanged(String deviceId, String key, String value) {
            try {
                JSONObject favoriteJson = new JSONObject(value);
                String movieId = favoriteJson.getString("movieId");
                boolean isFavorite = favoriteJson.getBoolean("isFavorite");
                long timestamp = favoriteJson.getLong("timestamp");
                String deviceName = favoriteJson.getString("deviceName");
                
                // 忽略本地设备发送的更新
                if (deviceId.equals(DistributedDeviceInfo.getLocalDeviceId())) {
                    return;
// 更新本地收藏状态
                if (isFavorite) {
                    localFavorites.add(movieId);
else {
                    localFavorites.remove(movieId);
FavoriteListener listener = listeners.get(movieId);
                if (listener != null) {
                    listener.onFavoriteChanged(movieId, isFavorite, deviceName);
} catch (JSONException e) {
                HiLog.error(TAG, "Failed to parse favorite data");
}
    });
// 加载本地收藏
private void loadLocalFavorites() {
    // 从本地存储加载收藏列表
    // 实现略...
// 同步收藏状态
public void syncFavorite(String movieId, boolean isFavorite, String movieTitle) {
    // 更新本地状态
    if (isFavorite) {
        localFavorites.add(movieId);
else {
        localFavorites.remove(movieId);
// 同步到其他设备
    JSONObject favoriteJson = new JSONObject();
    try {
        favoriteJson.put("movieId", movieId);
        favoriteJson.put("movieTitle", movieTitle);
        favoriteJson.put("isFavorite", isFavorite);
        favoriteJson.put("timestamp", System.currentTimeMillis());
        favoriteJson.put("deviceName", DistributedDeviceInfo.getLocalDeviceName());
        favoriteJson.put("version", getNextVersion());
        
        DistributedOptions options = new DistributedOptions();
        options.setPriority(DistributedOptions.Priority.HIGH);
        
        dataManager.putString(SYNC_CHANNEL, 
            favoriteJson.toString(), 
            DistributedDataManager.PUT_MODE_RELIABLE, 
            options);
catch (JSONException e) {
        HiLog.error(TAG, "Failed to serialize favorite data");
}
// 检查是否收藏
public boolean isFavorite(String movieId) {
    return localFavorites.contains(movieId);
// 注册收藏监听器
public void registerListener(String movieId, FavoriteListener listener) {
    listeners.put(movieId, listener);
// 取消注册监听器
public void unregisterListener(String movieId) {
    listeners.remove(movieId);
public interface FavoriteListener {
    void onFavoriteChanged(String movieId, boolean isFavorite, String fromDevice);
private int versionCounter = 0;
private synchronized int getNextVersion() {
    return ++versionCounter;
}
电影列表界面实现
public class MovieListSlice extends AbilitySlice {
private static final String TAG = “MovieListSlice”;
private ListContainer movieList;
private List<Movie> movies = new ArrayList<>();
private FavoriteSyncService syncService;
@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    setUIContent(ResourceTable.Layout_movie_list_layout);
    
    // 初始化服务
    syncService = FavoriteSyncService.getInstance(this);
    
    // 获取UI组件
    movieList = (ListContainer) findComponentById(ResourceTable.Id_movie_list);
    Button refreshBtn = (Button) findComponentById(ResourceTable.Id_refresh_btn);
    
    // 设置刷新按钮事件
    refreshBtn.setClickedListener(component -> refreshMovies());
    
    // 初始加载电影数据
    refreshMovies();
private void refreshMovies() {
    new Thread(() -> {
        List<Movie> newMovies = DoubanClient.getInTheaters();
        getUITaskDispatcher().asyncDispatch(() -> {
            movies.clear();
            movies.addAll(newMovies);
            updateMovieList();
        });
    }).start();
private void updateMovieList() {
    movieList.setItemProvider(new MovieItemProvider(movies, this));
private class MovieItemProvider extends BaseItemProvider {
    private List<Movie> movies;
    private AbilitySlice slice;
    
    public MovieItemProvider(List<Movie> movies, AbilitySlice slice) {
        this.movies = movies;
        this.slice = slice;
@Override
    public int getCount() {
        return movies.size();
@Override
    public Object getItem(int position) {
        return movies.get(position);
@Override
    public long getItemId(int position) {
        return position;
@Override
    public Component getComponent(int position, Component convertComponent, ComponentContainer parent) {
        final DirectionalLayout itemLayout = (DirectionalLayout) LayoutScatter.getInstance(slice)
            .parse(ResourceTable.Layout_movie_item_layout, null, false);
            
        Movie movie = movies.get(position);
        
        // 设置电影信息
        Text titleText = (Text) itemLayout.findComponentById(ResourceTable.Id_movie_title);
        Text ratingText = (Text) itemLayout.findComponentById(ResourceTable.Id_movie_rating);
        Text yearText = (Text) itemLayout.findComponentById(ResourceTable.Id_movie_year);
        Image image = (Image) itemLayout.findComponentById(ResourceTable.Id_movie_image);
        Button favoriteBtn = (Button) itemLayout.findComponentById(ResourceTable.Id_favorite_btn);
        
        titleText.setText(movie.getTitle());
        ratingText.setText(String.format(Locale.getDefault(), "%.1f", movie.getRating()));
        yearText.setText(movie.getYear());
        
        // 异步加载图片
        new Thread(() -> {
            try {
                PixelMap pixelMap = ImageUtils.getPixelMapFromUrl(movie.getImageUrl());
                getUITaskDispatcher().asyncDispatch(() -> {
                    image.setPixelMap(pixelMap);
                });
catch (IOException e) {
                HiLog.error(TAG, "Failed to load image: " + e.getMessage());
}).start();
        // 更新收藏按钮状态
        updateFavoriteButton(favoriteBtn, movie.getId());
        
        // 设置收藏按钮点击事件
        favoriteBtn.setClickedListener(component -> {
            boolean isFavorite = syncService.isFavorite(movie.getId());
            syncService.syncFavorite(movie.getId(), !isFavorite, movie.getTitle());
            updateFavoriteButton(favoriteBtn, movie.getId());
        });
        
        // 设置点击跳转到详情页
        itemLayout.setClickedListener(component -> {
            Intent intent = new Intent();
            Operation operation = new Intent.OperationBuilder()
                .withDeviceId("")
                .withBundleName(slice.getBundleName())
                .withAbilityName("MovieDetailAbility")
                .build();
            intent.setOperation(operation);
            intent.setParam("movieId", movie.getId());
            slice.startAbility(intent);
        });
        
        return itemLayout;
private void updateFavoriteButton(Button button, String movieId) {
        boolean isFavorite = syncService.isFavorite(movieId);
        button.setText(isFavorite ? "已收藏" : "收藏");
        button.setTextColor(isFavorite ? Color.RED : Color.BLACK);
}
电影详情界面实现
public class MovieDetailSlice extends AbilitySlice {
private static final String TAG = “MovieDetailSlice”;
private String movieId;
private FavoriteSyncService syncService;
@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    setUIContent(ResourceTable.Layout_movie_detail_layout);
    
    movieId = intent.getStringParam("movieId");
    syncService = FavoriteSyncService.getInstance(this);
    
    // 注册收藏监听器
    syncService.registerListener(movieId, new FavoriteSyncService.FavoriteListener() {
        @Override
        public void onFavoriteChanged(String changedMovieId, boolean isFavorite, String fromDevice) {
            if (changedMovieId.equals(movieId)) {
                updateFavoriteButton();
}
    });
    
    // 加载电影详情
    loadMovieDetail();
private void loadMovieDetail() {
    new Thread(() -> {
        Movie movie = DoubanClient.getMovieDetail(movieId);
        if (movie != null) {
            getUITaskDispatcher().asyncDispatch(() -> {
                displayMovieDetail(movie);
            });
}).start();
private void displayMovieDetail(Movie movie) {
    Text titleText = (Text) findComponentById(ResourceTable.Id_detail_title);
    Text originalTitleText = (Text) findComponentById(ResourceTable.Id_detail_original_title);
    Text ratingText = (Text) findComponentById(ResourceTable.Id_detail_rating);
    Text yearText = (Text) findComponentById(ResourceTable.Id_detail_year);
    Text genresText = (Text) findComponentById(ResourceTable.Id_detail_genres);
    Text summaryText = (Text) findComponentById(ResourceTable.Id_detail_summary);
    Image image = (Image) findComponentById(ResourceTable.Id_detail_image);
    Button favoriteBtn = (Button) findComponentById(ResourceTable.Id_detail_favorite_btn);
    
    titleText.setText(movie.getTitle());
    originalTitleText.setText(movie.getOriginalTitle());
    ratingText.setText(String.format(Locale.getDefault(), "评分: %.1f", movie.getRating()));
    yearText.setText("年份: " + movie.getYear());
    genresText.setText("类型: " + String.join(", ", movie.getGenres()));
    summaryText.setText(movie.getSummary());
    
    // 异步加载图片
    new Thread(() -> {
        try {
            PixelMap pixelMap = ImageUtils.getPixelMapFromUrl(movie.getImageUrl());
            getUITaskDispatcher().asyncDispatch(() -> {
                image.setPixelMap(pixelMap);
            });
catch (IOException e) {
            HiLog.error(TAG, "Failed to load image: " + e.getMessage());
}).start();
    // 设置收藏按钮
    updateFavoriteButton(favoriteBtn);
    favoriteBtn.setClickedListener(component -> {
        boolean isFavorite = syncService.isFavorite(movieId);
        syncService.syncFavorite(movieId, !isFavorite, movie.getTitle());
        updateFavoriteButton(favoriteBtn);
    });
private void updateFavoriteButton(Button button) {
    boolean isFavorite = syncService.isFavorite(movieId);
    button.setText(isFavorite ? "已收藏" : "收藏");
    button.setTextColor(isFavorite ? Color.RED : Color.BLACK);
@Override
protected void onStop() {
    super.onStop();
    // 取消注册监听器
    syncService.unregisterListener(movieId);
}
四、与《鸿蒙跨端U同步》的技术关联
本项目借鉴了游戏多设备同步的以下关键技术:
状态同步模型:类似游戏中玩家状态的实时同步,收藏状态通过JSON格式广播
设备标识:使用设备名称区分不同来源的收藏状态更新
版本控制:引入版本号解决潜在的冲突问题
可靠传输:使用高优先级的数据传输确保同步成功率
本地缓存:类似游戏中的本地存档,实现收藏状态的本地存储
增强的同步逻辑(借鉴游戏同步机制):
// 增强的收藏同步方法
public void syncFavorite(String movieId, boolean isFavorite, String movieTitle) {
JSONObject favoriteJson = new JSONObject();
try {
favoriteJson.put(“movieId”, movieId);
favoriteJson.put(“movieTitle”, movieTitle);
favoriteJson.put(“isFavorite”, isFavorite);
favoriteJson.put(“timestamp”, System.currentTimeMillis());
favoriteJson.put(“deviceName”, DistributedDeviceInfo.getLocalDeviceName());
favoriteJson.put(“version”, getNextVersion());
    // 增加校验码
    favoriteJson.put("checksum", calculateChecksum(movieId, isFavorite));
    
    // 设置传输选项
    DistributedOptions options = new DistributedOptions();
    options.setPriority(DistributedOptions.Priority.HIGH);
    options.setTimeToLive(60000); // 60秒有效期
    options.setRetryCount(5); // 重试5次
    
    // 使用可靠传输
    int result = dataManager.putString(SYNC_CHANNEL, 
        favoriteJson.toString(), 
        DistributedDataManager.PUT_MODE_RELIABLE, 
        options);
        
    if (result != 0) {
        HiLog.warn(TAG, "Favorite sync failed with code: " + result);
        // 可以在这里实现重试逻辑
} catch (JSONException e) {
    HiLog.error(TAG, "Failed to serialize favorite data");
}
// 校验收藏数据完整性
private boolean validateFavoriteData(JSONObject favoriteJson) {
try {
String movieId = favoriteJson.getString(“movieId”);
boolean isFavorite = favoriteJson.getBoolean(“isFavorite”);
int checksum = favoriteJson.getInt(“checksum”);
return checksum == calculateChecksum(movieId, isFavorite);
catch (JSONException e) {
    return false;
}
// 简单的校验码计算
private int calculateChecksum(String movieId, boolean isFavorite) {
return (movieId.hashCode() ^ (isFavorite ? 1 : 0)) & 0x7FFFFFFF;
五、项目特色与创新点
实时数据获取:从豆瓣API获取最新电影信息
跨设备同步:收藏状态在多设备间实时同步
高性能列表:优化电影列表的滚动性能
图片缓存:实现电影海报图片的本地缓存
响应式UI:实时更新收藏状态和同步状态
六、应用场景
电影推荐应用:展示热门电影并同步用户偏好
家庭观影记录:家庭成员间的观影记录共享
电影社交平台:好友间的电影收藏分享
影院选座系统:多设备同步选择的电影
七、总结
本电影排行榜应用实现了以下功能:
从豆瓣API获取热门电影信息
电影列表和详情展示
用户收藏功能
收藏状态的多设备同步
电影海报图片的加载和缓存
通过借鉴游戏中的状态同步技术,我们构建了一个可靠的跨设备电影收藏同步系统。未来可扩展功能包括:
电影搜索功能:支持按名称搜索电影
观影记录:记录用户观看过的电影
评分系统:用户可以为电影评分并同步
好友分享:与好友分享电影收藏
这个项目展示了如何将游戏中的同步技术应用于电影类应用开发,为用户提供无缝的跨设备电影收藏体验。




















