在我的 Expo(react-native)应用程序中,即使应用程序处于后台或被杀死,我也想执行上传任务。
uploadFile
方法)。UIBackgroundModes
键,但它只有 audio,location,voip,external-accessory,bluetooth-central,bluetooth-peripheral,fetch,remote-notification,processing
作为可能的值。如果您至少可以指导我从哪里开始或搜索什么,即使应用程序在后台被杀死/终止也能够上传文件,我将不胜感激。
import { getStorage, ref, uploadBytes } from "firebase/storage";
const storage = getStorage();
const storageRef = ref(storage, 'videos');
const uploadFile = async (file)=>{
// the file is Blob object
await uploadBytes(storageRef, file);
}
react-native-background-fetch
,react-native-background-upload
,react-native-background-job
。 upload
应该弹出Expo,job
不支持iOS,fetch
是一个为间隔执行任务而设计的抓取任务。
如果有办法使用提到的库来达到我的目的,请指导我:)asyncUpload
可能无需弹出。希望这还不算太晚,有帮助。 我最近一直在处理各种博览会 <-> firebase 存储集成,这里有一些可能有用的信息。
首先,我建议 not 使用 Firebase 中的
uploadBytes
/ uploadBytesResumable
方法。 这个线程对此有一个长期持续的讨论,但基本上它在 v9 中被破坏了。也许将来 Firebase 团队会解决这些问题,但现在与 Expo 的关系已经很糟糕了。
相反,我建议要么沿着编写一个小型 Firebase 函数的路线,该函数要么提供 signed-upload-url 要么处理上传本身。
基本上,如果您可以通过http端点让存储上传工作,您就可以让任何类型的上传机制工作。(例如,您可能在这里寻找的
FileSystem.uploadAsync()
方法,就像@brentvatne指出的那样,或者fetch 或 axios 我将在最后展示基本的接线)。
基本上,有一个小的 firebase 函数可以返回签名的 url。您的应用程序调用像
/get-signed-upload-url
这样的云函数,它会返回 URL,然后您可以使用该 URL。查看:https://cloud.google.com/storage/docs/access-control/signed-urls 了解如何进行此操作。
这可能适合您的用例。它可以像任何
httpsCallable
功能一样进行配置,因此与选项 2 相比,设置工作并不多。
但是,这不适用于 firebase 存储/函数模拟器!因此,我不使用这种方法,因为我喜欢集中使用模拟器,而它们只提供所有功能的子集。
这有点毛茸茸的,但可以让您的上传保真度更高,并且可以在模拟器上运行!我也喜欢这个,因为它允许在端点执行中执行上传过程,而不是作为副作用。
例如,您可以让照片上传端点生成缩略图,如果端点是 201,那么就可以了!而不是传统的 Firebase 方法,即拥有一个云存储侦听器,这种方法会生成缩略图作为副作用,从而产生各种不良的竞争条件(通过指数退避检查处理完成情况?恶心!)
以下是我建议采用此方法的三个资源:
基本上,如果您可以制作一个接受 formdata 中的文件的 Firebase 云端点,您可以让 Busboy 解析它,然后您可以用它做任何您想做的事情...比如将其上传到云存储!
大纲:
import * as functions from "firebase-functions";
import * as busboy from "busboy";
import * as os from "os";
import * as path from "path";
import * as fs from "fs";
type FieldMap = {
[fieldKey: string]: string;
};
type Upload = {
filepath: string;
mimeType: string;
};
type UploadMap = {
[fileName: string]: Upload;
};
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
export const uploadPhoto = functions.https.onRequest(async (req, res) => {
verifyRequest(req); // Verify parameters, auth, etc. Better yet, use a middleware system for this like express.
// This object will accumulate all the fields, keyed by their name
const fields: FieldMap = {};
// This object will accumulate all the uploaded files, keyed by their name.
const uploads: UploadMap = {};
// This will accumulator errors during the busboy process, allowing us to end early.
const errors: string[] = [];
const tmpdir = os.tmpdir();
const fileWrites: Promise<unknown>[] = [];
function cleanup() {
Object.entries(uploads).forEach(([filename, { filepath }]) => {
console.log(`unlinking: ${filename} from ${path}`);
fs.unlinkSync(filepath);
});
}
const bb = busboy({
headers: req.headers,
limits: {
files: 1,
fields: 1,
fileSize: MAX_FILE_SIZE,
},
});
bb.on("file", (name, file, info) => {
verifyFile(name, file, info); // Verify your mimeType / filename, etc.
file.on("limit", () => {
console.log("too big of file!");
});
const { filename, mimeType } = info;
// Note: os.tmpdir() points to an in-memory file system on GCF
// Thus, any files in it must fit in the instance's memory.
console.log(`Processed file ${filename}`);
const filepath = path.join(tmpdir, filename);
uploads[filename] = {
filepath,
mimeType,
};
const writeStream = fs.createWriteStream(filepath);
file.pipe(writeStream);
// File was processed by Busboy; wait for it to be written.
// Note: GCF may not persist saved files across invocations.
// Persistent files must be kept in other locations
// (such as Cloud Storage buckets).
const promise = new Promise((resolve, reject) => {
file.on("end", () => {
writeStream.end();
});
writeStream.on("finish", resolve);
writeStream.on("error", reject);
});
fileWrites.push(promise);
});
bb.on("close", async () => {
await Promise.all(fileWrites);
// Fail if errors:
if (errors.length > 0) {
functions.logger.error("Upload failed", errors);
res.status(400).send(errors.join());
} else {
try {
const upload = Object.values(uploads)[0];
if (!upload) {
functions.logger.debug("No upload found");
res.status(400).send("No file uploaded");
return;
}
const { uploadId } = await processUpload(upload, userId);
cleanup();
res.status(201).send({
uploadId,
});
} catch (error) {
cleanup();
functions.logger.error("Error processing file", error);
res.status(500).send("Error processing file");
}
}
});
bb.end(req.rawBody);
});
然后,该
processUpload
函数可以对文件执行任何您想要的操作,例如将其上传到云存储:
async function processUpload({ filepath, mimeType }: Upload, userId: string) {
const fileId = uuidv4();
const bucket = admin.storage().bucket();
await bucket.upload(filepath, {
destination: `users/${userId}/${fileId}`,
{
contentType: mimeType,
},
});
return { fileId };
}
然后,在移动端,你可以像这样与之交互:
async function uploadFile(uri: string) {
function getFunctionsUrl(): string {
if (USE_EMULATOR) {
const origin =
Constants?.manifest?.debuggerHost?.split(":").shift() || "localhost";
const functionsPort = 5001;
const functionsHost = `http://${origin}:${functionsPort}/{PROJECT_NAME}/${PROJECT_LOCATION}`;
return functionsHost;
} else {
return `https://{PROJECT_LOCATION}-{PROJECT_NAME}.cloudfunctions.net`;
}
}
// The url of your endpoint. Make this as smart as you want.
const url = `${getFunctionsUrl()}/uploadPhoto`;
await FileSystem.uploadAsync(uploadUrl, uri, {
httpMethod: "POST",
uploadType: FileSystem.FileSystemUploadType.MULTIPART,
fieldName: "file", // Important! make sure this matches however you want bussboy to validate the "name" field on file.
mimeType,
headers: {
"content-type": "multipart/form-data",
Authorization: `${idToken}`,
},
});
});
将 Cloud Storage 包装在您自己的端点中,将其视为普通的 http 上传,一切都很好。
你有没有找到一个干净的解决方案?
我已经做了类似你想要的事情,你可以使用expo-task-manager和expo-background-fetch。这是我使用的代码。我希望这对你有用。
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_FETCH_TASK = 'background-fetch';
const [isRegistered, setIsRegistered] = useState(false);
const [status, setStatus] = useState(null);
//Valor para que se ejecute en IOS
BackgroundFetch.setMinimumIntervalAsync(60 * 15);
// Define the task to execute
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const now = Date.now();
console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`);
// Your function or instructions you want
return BackgroundFetch.Result.NewData;
});
// Register the task in BACKGROUND_FETCH_TASK
async function registerBackgroundFetchAsync() {
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 60 * 15, // 1 minutes
stopOnTerminate: false, // android only,
startOnBoot: true, // android only
});
}
// Task Status
const checkStatusAsync = async () => {
const status = await BackgroundFetch.getStatusAsync();
const isRegistered = await TaskManager.isTaskRegisteredAsync(
BACKGROUND_FETCH_TASK
);
setStatus(status);
setIsRegistered(isRegistered);
};
// Check if the task is already register
const toggleFetchTask = async () => {
if (isRegistered) {
console.log('Task ready');
} else {
await registerBackgroundFetchAsync();
console.log('Task registered');
}
checkStatusAsync();
};
useEffect(() => {
toggleFetchTask();
}, []);