Hot Update Not Reflecting New Assets or Logic After Game Restart on Android

I’ve implemented the Cocos Creator Hot Update system for my Android project. The update process seems to work fine, the files are successfully downloaded from the remote server, and I reset/restart the game after the update.

However, after restarting, the game still runs with the old assets and logic. The newly downloaded files don’t seem to take effect.

import { _decorator, assetManager, Component, instantiate, JsonAsset, Node, Prefab, sys, SpriteFrame, Texture2D, AudioClip, Sprite, AudioSource, native, Asset, game, director, AssetManager, Label, ProgressBar, Button } from ‘cc’;

import { NATIVE } from ‘cc/env’;

const { ccclass, property } = _decorator;

@ccclass(‘RemoteEntryLoader’)

export class RemoteEntryLoader extends Component {
@property(Asset)
manifestUrl: Asset = null!;

@property(Label)
statusLabel: Label = null!;

@property(ProgressBar)
progressBar: ProgressBar = null!;

@property(Button)
checkUpdateBtn: Button = null!;

@property(Button)
retryBtn: Button = null!;

private assetsManager: any = null;
private storagePath: string = ‘’;
// private manifestUrl: string = ‘’;
private updating: boolean = false;

private canRetry: boolean = false;
private updateListener: any = null;

onLoad() {
// Only initialize on native platforms
if (!NATIVE) {
this.statusLabel.string = ‘Hot update is only available on native platforms’;
return;
}

// Setup paths - customize these for your project
this.storagePath = ((native.fileUtils && native.fileUtils.getWritablePath) ? 
    native.fileUtils.getWritablePath() : '/') + 'hot-update/';

// Your remote manifest URL - replace with your server URL
// this.manifestUrl = 'https://thanisthani.github.io/CocosHotUpdate/remote-assets/project.manifest';

this.initHotUpdate();
this.setupUI();

}

private initHotUpdate(): void {
if (!NATIVE) return;

try {

  if (NATIVE) {
    const hotUpdateRoot = native.fileUtils.getWritablePath() + 'hot-update/';
    console.log('Hot update directory exists:', native.fileUtils.isDirectoryExist(hotUpdateRoot));
    console.log('Manifest exists:', native.fileUtils.isFileExist(hotUpdateRoot + 'project.manifest'));
    
    // List files in hot update directory
    const files = native.fileUtils.listFiles(hotUpdateRoot);
    console.log('Files in hot update directory:', files);
}

     // Ensure storage directory exists
     if (!native.fileUtils.isDirectoryExist(this.storagePath)) {
      native.fileUtils.createDirectory(this.storagePath);
  }

    // Create AssetsManager instance
    this.assetsManager = new native.AssetsManager(this.manifestUrl.nativeUrl, this.storagePath);
    
    // Set up search paths for hot updated assets
    this.setupSearchPaths();
    
    // Configure AssetsManager
    this.assetsManager.setMaxConcurrentTask(5); // Limit concurrent downloads
    
    // Set version comparison function (optional)
    this.assetsManager.setVersionCompareHandle((versionA: string, versionB: string) => {
        return this.compareVersion(versionA, versionB);
    });
    
    // Set file verification callback (optional but recommended)
    this.assetsManager.setVerifyCallback((path: string, asset: any) => {
        return this.verifyAsset(path, asset);
    });
    
    // Set update event listener
    this.updateListener = (event: any) => this.updateCallback(event);
    this.assetsManager.setEventCallback(this.updateListener);
    
    this.statusLabel.string = 'Hot update initialized';
    
} catch (error) {
    console.error('Failed to initialize hot update:', error);
    this.statusLabel.string = 'Failed to initialize hot update';
}

}

private setupSearchPaths(): void {
if (!NATIVE || !this.assetsManager) return;

try {
  if (this.assetsManager.getState() === native.AssetsManager.State.UNINITED) {
                    console.log("loadLocalManifest is called")
                    if (this.manifestUrl && this.manifestUrl.nativeUrl) {
                        this.assetsManager.loadLocalManifest(this.manifestUrl.nativeUrl);
                    }
                }
    // Get hot update search paths from local manifest
    const localManifest = this.assetsManager.getLocalManifest();
    if (!localManifest || !localManifest.isLoaded()) {
      console.log("Failed to load bundled manifest");
      throw new Error('Failed to load bundled manifest');
      
  }

    if (localManifest && localManifest.isLoaded()) {
      console.log("localManifest is loaded");
        const hotUpdateSearchPaths = localManifest.getSearchPaths();
        const searchPaths = native.fileUtils.getSearchPaths();
        
        // Insert hot update paths at the beginning for priority
        Array.prototype.unshift.apply(searchPaths, hotUpdateSearchPaths);
        native.fileUtils.setSearchPaths(searchPaths);
        
        console.log('Search paths updated:', searchPaths);
    }
} catch (error) {
    console.error('Failed to setup search paths:', error);
}

}

private setupUI(): void {
// Setup button callbacks
if (this.checkUpdateBtn) {
this.checkUpdateBtn.node.on(‘click’, this.checkForUpdate, this);
}

if (this.retryBtn) {
    this.retryBtn.node.on('click', this.retry, this);
    this.retryBtn.node.active = false;
}

}

private checkForUpdate(): void {
if (!NATIVE || !this.assetsManager || this.updating) return;

console.log("checkForUpdate is called");
this.updating = true;
this.canRetry = false;
this.checkUpdateBtn.node.active = false;
this.retryBtn.node.active = false;
this.statusLabel.string = 'Checking for updates...';
this.progressBar.progress = 0;

// Check for updates
this.assetsManager.checkUpdate();

}

private retry(): void {
if (!this.canRetry) return;

this.canRetry = false;
this.retryBtn.node.active = false;
this.statusLabel.string = 'Retrying failed downloads...';

// Retry downloading failed assets
this.assetsManager.downloadFailedAssets();

}

private updateCallback(event: any): void {
const eventCode = event.getEventCode();
console.log(“update eventCode is”, eventCode);

switch (eventCode) {
    case native.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
        this.statusLabel.string = 'No local manifest file found';
        this.onUpdateFinished(false);
        break;
        
    case native.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
        this.statusLabel.string = 'Failed to download manifest';
        this.onUpdateFinished(false);
        break;
        
    case native.EventAssetsManager.ERROR_PARSE_MANIFEST:
        this.statusLabel.string = 'Failed to parse manifest';
        this.onUpdateFinished(false);
        break;
        
    case native.EventAssetsManager.NEW_VERSION_FOUND:
        this.statusLabel.string = 'New version found, starting download...';
        this.assetsManager.update();
        break;
        
    case native.EventAssetsManager.ALREADY_UP_TO_DATE:
        this.statusLabel.string = 'Already up to date';
        this.onUpdateFinished(true);
        break;
        
    case native.EventAssetsManager.UPDATE_PROGRESSION:
        this.handleUpdateProgress(event);
        break;
        
    case native.EventAssetsManager.ASSET_UPDATED:
        // Individual asset updated
        break;
        
    case native.EventAssetsManager.ERROR_UPDATING:
        this.statusLabel.string = 'Update error: ' + event.getMessage();
        this.onUpdateFinished(false);
        break;
        
    case native.EventAssetsManager.UPDATE_FINISHED:
        this.statusLabel.string = 'Update completed! Restart required.';
        const searchPaths = native.fileUtils.getSearchPaths();
        console.log("searchPaths is", searchPaths);
        const hotUpdatePath = this.storagePath;
        
        // Remove if exists, then add at beginning
        const index = searchPaths.indexOf(hotUpdatePath);
        if (index > -1) searchPaths.splice(index, 1);
        searchPaths.unshift(hotUpdatePath);
        
        native.fileUtils.setSearchPaths(searchPaths);
        console.log("updated searchPaths is", searchPaths);
        
        // Save restart flag
        sys.localStorage.setItem('hotUpdateReady', 'true');
        sys.localStorage.setItem('hotUpdatePath', hotUpdatePath);

        this.onUpdateFinished(true, true);
        break;
        
    case native.EventAssetsManager.UPDATE_FAILED:
        this.statusLabel.string = 'Update failed: ' + event.getMessage();
        this.canRetry = true;
        this.retryBtn.node.active = true;
        this.onUpdateFinished(false);
        break;
        
    case native.EventAssetsManager.ERROR_DECOMPRESS:
        this.statusLabel.string = 'Decompression failed';
        this.onUpdateFinished(false);
        break;
}

}

private handleUpdateProgress(event: any): void {
const percent = event.getPercent();
const filePercent = event.getPercentByFile();
const downloadedBytes = event.getDownloadedBytes();
const totalBytes = event.getTotalBytes();
const downloadedFiles = event.getDownloadedFiles();
const totalFiles = event.getTotalFiles();

// Update progress bar (using byte progress)
this.progressBar.progress = percent / 100;

// Update status label with detailed progress
this.statusLabel.string = `Downloading... ${Math.floor(percent)}%\n` +
    `Files: ${downloadedFiles}/${totalFiles}\n` +
    `Size: ${this.formatBytes(downloadedBytes)}/${this.formatBytes(totalBytes)}`;

    console.log(`Downloading... ${Math.floor(percent)}%\n` +
    `Files: ${downloadedFiles}/${totalFiles}\n` +
    `Size: ${this.formatBytes(downloadedBytes)}/${this.formatBytes(totalBytes)}`)

}

private onUpdateFinished(success: boolean, needRestart: boolean = false): void {
this.updating = false;
this.checkUpdateBtn.node.active = true;

if (needRestart) {
    // Show restart button or automatically restart
    this.showRestartOption();
}

}

private showRestartOption(): void {
// You can show a restart dialog here
// For now, we’ll just add a restart button functionality
this.statusLabel.string = ‘Update completed! Tap to restart.’;
this.checkUpdateBtn.getComponentInChildren(Label)!.string = ‘Restart Game’;
this.checkUpdateBtn.node.off(‘click’, this.checkForUpdate, this);
this.checkUpdateBtn.node.on(‘click’, this.restartGame, this);
}

private restartGame(): void {
console.log(“restartGame is called”);
// Clean up before restart
if (this.assetsManager) {
this.assetsManager.setEventCallback(null);
}

// Restart the game
game.restart();

}

private compareVersion(versionA: string, versionB: string): number {
// Custom version comparison logic
// Return > 0 if versionA > versionB
// Return 0 if versionA == versionB
// Return < 0 if versionA < versionB

const parseVersion = (version: string) => {
    return version.split('.').map(v => parseInt(v) || 0);
};

const vA = parseVersion(versionA);
const vB = parseVersion(versionB);

for (let i = 0; i < Math.max(vA.length, vB.length); i++) {
    const a = vA[i] || 0;
    const b = vB[i] || 0;
    if (a !== b) return a - b;
}
return 0;

}

private verifyAsset(filePath: string, asset: any): boolean {
// Asset verification logic
// You can implement MD5 check here if needed
// For now, just return true to skip verification
return true;
}

private formatBytes(bytes: number): string {
if (bytes === 0) return ‘0 B’;
const k = 1024;
const sizes = [‘B’, ‘KB’, ‘MB’, ‘GB’];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ’ ’ + sizes[i];
}

onDestroy(): void {
// Clean up
if (this.assetsManager && this.updateListener) {
this.assetsManager.setEventCallback(null);
}
}

}