Flutter iOS:记录鼓捣 On Demand Resources File 的过程

550 阅读2分钟
记下来,省的下次忘了

使用 On Demand Resources File 的目的是使用App Store托管资源文件,但是又不想增加“下载大小”,避免用户看到资源太大不愿意下载App。

1、添加资源文件到项目里

image.png

注意不要直接复制到目录里,要在xcode里面操作,不然不能被项目识别

2、设置Tag标签,设置为按需下载类型

image.png

Tag是获取下载的标签,要注意这个名字

image.png

Debug模式不支持从App Store下载资源,所以要设置Debug为包含资源

3、写原生方法下载资源


import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        
        let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
        
        let methodChannel = FlutterMethodChannel.init(name: "com.example.odr", binaryMessenger: controller.binaryMessenger)
 
        
        methodChannel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
            if (call.method == "requestODR") {
                self.requestODR(call, result)
            }
            if (call.method == "requestODRFile") {
                self.loadFile(call: call, result: result)
            }

        }
        

        
        
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    
    
    
    
    ///
    /// 请求ODR标签数据
    ///
    
    func requestODR(_ call: FlutterMethodCall, _ result:  @escaping  FlutterResult) {
        let tag = call.arguments as! String
        let tagsSet: Set<String> = [tag]
        
        let request = NSBundleResourceRequest(tags: tagsSet, bundle: Bundle.main)
        request.conditionallyBeginAccessingResources { hasCache in
            if hasCache == false {
                // 这些资源不全在本地
                request.beginAccessingResources { error in
                    if let _ = error {
                        // 下载失败
                        print("[ORGIN]Download Failed");
                        result("404 \(String(describing: error))");
                    }
                    else {
                        // 下载成功
                        print("[ORGIN]Download Success");
                        
                        result("200");
                    }
                }
            }
            else {
                // 这些资源已经在本地了
                result("301");
            }
        }
    }
    
    
    ///
    /// 暴露数据给Flutter
    ///
    func loadFile(call: FlutterMethodCall, result: @escaping FlutterResult) {
        let fileName = call.arguments as! String;
        
        print(Bundle.main.bundlePath)
        print(fileName)
        
        let path = Bundle.main.path(forResource: fileName, ofType: "zip")
        
        if(path != nil){
            let data = try! Data(contentsOf: URL(fileURLWithPath: path!))
            result(data)
        }else{
            print("找不到数据")
            result("")
        }
   
        
    }
    
    
}


4、Flutter端调用



import 'dart:io';

import 'package:archive/archive_io.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

///
/// 从苹果服务器下载字体资源到本地
///
const platform = MethodChannel('com.example.odr');

Future<void> requestODR(String tag) async {
 
  debugPrint("开始下载资源");
  try {
    dynamic a = await platform.invokeMethod('requestODR', tag);
    debugPrint("下载反馈:$a");
  
    if (a == '200' || a == '301') {
      await getTagSource('cz_font');
    } else {
      tipBar('$a', time: 30);
    }
  } on PlatformException catch (e) {
    debugPrint("$e");
    tipBar("$e", title: "测试下载错误");
  }
}

Future<void> getTagSource(String tagFile) async {
  /// requestODRFile

  try {
  
    Uint8List data = await platform.invokeMethod('requestODRFile', tagFile);
  
    await loadZipAndRegisterFont(data);

    ///
    box.put(Config.fontDownload, Config.fontVersion);
  } catch (e) {
    debugPrint("$e");
    tipBar("$e", title: "文件异常");
  }
}

///
/// 读取ZIP文件、解压
///
Future<void> loadZipAndRegisterFont(Uint8List data) async {
  /// 字体目录

  Directory doc = await getApplicationDocumentsDirectory();
  Directory docuPath = Directory('${doc.path}/source/vip_fonts');
  bool exists = await docuPath.exists();
  if (!exists) {
    debugPrint(">>>新建该目录 ${docuPath.path}");
    await docuPath.create(recursive: true);
  }

  final archive = ZipDecoder().decodeBytes(data);
  // For all of the entries in the archive
  for (var file in archive.files) {
    // If it's a file and not a directory
    if (file.isFile) {
      // Write the file content to a directory called 'out'.
      // In practice, you should make sure file.name doesn't include '..' paths
      // that would put it outside of the extraction directory.
      // An OutputFileStream will write the data to disk.
      final outputStream = OutputFileStream('${docuPath.path}/${file.name}');
      // The writeContent method will decompress the file content directly to disk without
      // storing the decompressed data in memory.
      file.writeContent(outputStream);
      // Make sure to close the output stream so the File is closed.
      outputStream.close();
    }
  }
}