העלאת קבצים באמצעות Cloud Storage ב-Android

Cloud Storage for Firebase מאפשרת להעלות קבצים במהירות ובקלות לקטגוריה Cloud Storage ש-Firebase מספקת ומנהלת.

העלאת קבצים

כדי להעלות קובץ אל Cloud Storage, קודם יוצרים הפניה לנתיב המלא של הקובץ, כולל שם הקובץ.

Kotlin+KTX

// Create a storage reference from our app
val storageRef = storage.reference

// Create a reference to "mountains.jpg"
val mountainsRef = storageRef.child("mountains.jpg")

// Create a reference to 'images/mountains.jpg'
val mountainImagesRef = storageRef.child("images/mountains.jpg")

// While the file names are the same, the references point to different files
mountainsRef.name == mountainImagesRef.name // true
mountainsRef.path == mountainImagesRef.path // false

Java

// Create a storage reference from our app
StorageReference storageRef = storage.getReference();

// Create a reference to "mountains.jpg"
StorageReference mountainsRef = storageRef.child("mountains.jpg");

// Create a reference to 'images/mountains.jpg'
StorageReference mountainImagesRef = storageRef.child("images/mountains.jpg");

// While the file names are the same, the references point to different files
mountainsRef.getName().equals(mountainImagesRef.getName());    // true
mountainsRef.getPath().equals(mountainImagesRef.getPath());    // false

אחרי שיוצרים את ההפניה המתאימה, צריך להפעיל את השיטה putBytes(),‏ putFile() או putStream() כדי להעלות את הקובץ אל Cloud Storage.

אי אפשר להעלות נתונים עם הפניה לשורש הקטגוריה Cloud Storage. ההפניה צריכה להפנות לכתובת URL של צאצא.

העלאה מנתונים בזיכרון

השיטה putBytes() היא הדרך הפשוטה ביותר להעלות קובץ אל Cloud Storage. הפונקציה putBytes() מקבלת byte[] ומחזירה UploadTask שאפשר להשתמש בו כדי לנהל את הסטטוס של ההעלאה ולעקוב אחריו.

Kotlin+KTX

// Get the data from an ImageView as bytes
imageView.isDrawingCacheEnabled = true
imageView.buildDrawingCache()
val bitmap = (imageView.drawable as BitmapDrawable).bitmap
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()

var uploadTask = mountainsRef.putBytes(data)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

// Get the data from an ImageView as bytes
imageView.setDrawingCacheEnabled(true);
imageView.buildDrawingCache();
Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] data = baos.toByteArray();

UploadTask uploadTask = mountainsRef.putBytes(data);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

מכיוון ש-putBytes() מקבלת byte[], האפליקציה צריכה לאחסן את כל התוכן של הקובץ בזיכרון בבת אחת. מומלץ להשתמש ב-putStream() או ב-putFile() כדי לחסוך בזיכרון.

העלאה מסטרימינג

השיטה putStream() היא הדרך הגמישה ביותר להעלאת קובץ אל Cloud Storage. הפונקציה putStream() מקבלת InputStream ומחזירה UploadTask שאפשר להשתמש בו כדי לנהל את הסטטוס של ההעלאה ולעקוב אחריו.

Kotlin+KTX

val stream = FileInputStream(File("path/to/images/rivers.jpg"))

uploadTask = mountainsRef.putStream(stream)
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

InputStream stream = new FileInputStream(new File("path/to/images/rivers.jpg"));

uploadTask = mountainsRef.putStream(stream);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

העלאה מקובץ מקומי

אפשר להעלות קבצים מקומיים מהמכשיר, כמו תמונות וסרטונים מהמצלמה, באמצעות השיטה putFile(). הפונקציה putFile() מקבלת File ומחזירה UploadTask, שבעזרתו אפשר לנהל את הסטטוס של ההעלאה ולעקוב אחריו.

Kotlin+KTX

var file = Uri.fromFile(File("path/to/images/rivers.jpg"))
val riversRef = storageRef.child("images/${file.lastPathSegment}")
uploadTask = riversRef.putFile(file)

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener { taskSnapshot ->
    // taskSnapshot.metadata contains file metadata such as size, content-type, etc.
    // ...
}

Java

Uri file = Uri.fromFile(new File("path/to/images/rivers.jpg"));
StorageReference riversRef = storageRef.child("images/"+file.getLastPathSegment());
uploadTask = riversRef.putFile(file);

// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // taskSnapshot.getMetadata() contains file metadata such as size, content-type, etc.
        // ...
    }
});

קבלת כתובת URL להורדה

אחרי העלאת קובץ, אפשר לקבל כתובת URL להורדת הקובץ על ידי קריאה ל-method‏ getDownloadUrl() ב-StorageReference:

Kotlin+KTX

val ref = storageRef.child("images/mountains.jpg")
uploadTask = ref.putFile(file)

val urlTask = uploadTask.continueWithTask { task ->
    if (!task.isSuccessful) {
        task.exception?.let {
            throw it
        }
    }
    ref.downloadUrl
}.addOnCompleteListener { task ->
    if (task.isSuccessful) {
        val downloadUri = task.result
    } else {
        // Handle failures
        // ...
    }
}

Java

final StorageReference ref = storageRef.child("images/mountains.jpg");
uploadTask = ref.putFile(file);

Task<Uri> urlTask = uploadTask.continueWithTask(new Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
    @Override
    public Task<Uri> then(@NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
        if (!task.isSuccessful()) {
            throw task.getException();
        }

        // Continue with the task to get the download URL
        return ref.getDownloadUrl();
    }
}).addOnCompleteListener(new OnCompleteListener<Uri>() {
    @Override
    public void onComplete(@NonNull Task<Uri> task) {
        if (task.isSuccessful()) {
            Uri downloadUri = task.getResult();
        } else {
            // Handle failures
            // ...
        }
    }
});

הוספת מטא-נתונים של קובץ

אפשר גם לכלול מטא-נתונים כשאתם מעלים קבצים. המטא-נתונים האלה מכילים מאפיינים אופייניים של מטא-נתונים של קבצים, כמו name,‏ size ו-contentType (שנקראים בדרך כלל סוג MIME). השיטה putFile() מסיקה באופן אוטומטי את סוג ה-MIME מהתוסף File, אבל אפשר לשנות את הסוג שזוהה באופן אוטומטי על ידי ציון contentType במטא-נתונים. אם לא מציינים ערך ל-contentType ו-Cloud Storage לא יכול להסיק ערך ברירת מחדל מסיום הקובץ, Cloud Storage משתמש ב-application/octet-stream. מידע נוסף על מטא-נתונים של קבצים זמין בקטע שימוש במטא-נתונים של קבצים.

Kotlin+KTX

// Create file metadata including the content type
var metadata = storageMetadata {
    contentType = "image/jpg"
}

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata)

Java

// Create file metadata including the content type
StorageMetadata metadata = new StorageMetadata.Builder()
        .setContentType("image/jpg")
        .build();

// Upload the file and metadata
uploadTask = storageRef.child("images/mountains.jpg").putFile(file, metadata);

נהל העלאות

בנוסף להפעלת העלאות, אפשר להשהות, להמשיך ולבטל העלאות באמצעות השיטות pause(), ‏resume() ו-cancel(). אירועי השהיה והפעלה מחדש מפעילים שינויים בסטטוס pause ו-progress, בהתאמה. ביטול ההעלאה גורם לכך שההעלאה נכשלת עם שגיאה שמציינת שההעלאה בוטלה.

Kotlin+KTX

uploadTask = storageRef.child("images/mountains.jpg").putFile(file)

// Pause the upload
uploadTask.pause()

// Resume the upload
uploadTask.resume()

// Cancel the upload
uploadTask.cancel()

Java

uploadTask = storageRef.child("images/mountains.jpg").putFile(file);

// Pause the upload
uploadTask.pause();

// Resume the upload
uploadTask.resume();

// Cancel the upload
uploadTask.cancel();

מעקב אחר התקדמות ההעלאה

אפשר להוסיף מאזינים כדי לטפל בהצלחה, בכישלון, בהתקדמות או בהשהיות של משימה ההעלאה:

סוג ה-listener שימוש רגיל
OnProgressListener הבורר הזה נקרא מדי פעם במהלך העברת הנתונים, וניתן להשתמש בו כדי לאכלס אינדיקטור להעלאה/הורדה.
OnPausedListener הבורר הזה נקרא בכל פעם שהמשימה מושהית.
OnSuccessListener המערכת קוראת למאזין הזה כשהמשימה הושלמה בהצלחה.
OnFailureListener המערכת קוראת למאזין הזה בכל פעם שההעלאה נכשלה. זה יכול לקרות בגלל תפוגת זמן הקצוב לרשת, כשלים באימות או אם מבטלים את המשימה.

OnFailureListener נקרא עם מופע של Exception. אחרים מתבצעים באמצעות אובייקט UploadTask.TaskSnapshot. האובייקט הזה הוא תצוגה קבועה של המשימה בזמן האירוע. ל-UploadTask.TaskSnapshot יש את המאפיינים הבאים:

נכס סוג תיאור
getDownloadUrl String כתובת URL שאפשר להשתמש בה כדי להוריד את האובייקט. זו כתובת URL ציבורית שלא ניתן לנחש אותה, שאפשר לשתף עם לקוחות אחרים. הערך הזה מאוכלס בסיום ההעלאה.
getError Exception אם המשימה נכשלה, הסיבה תהיה חריגה.
getBytesTransferred long המספר הכולל של הבייטים שהועברו כשצילמתם את קובץ ה-snapshot הזה.
getTotalByteCount long המספר הכולל של הבייטים שצפויים להעלאה.
getUploadSessionUri String URI שאפשר להשתמש בו כדי להמשיך את המשימה הזו באמצעות קריאה נוספת ל-putFile.
getMetadata StorageMetadata לפני שההעלאה מסתיימת, אלה המטא-נתונים שנשלחים לשרת. אחרי שההעלאה מסתיימת, אלה המטא-נתונים שמוחזרים מהשרת.
getTask UploadTask המשימה שיצרה את קובץ ה-snapshot הזה. משתמשים במשימה הזו כדי לבטל, להשהות או להמשיך את ההעלאה.
getStorage StorageReference ה-StorageReference ששימש ליצירת ה-UploadTask.

פונקציות ה-event listener של UploadTask מספקות דרך פשוטה ויעילה למעקב אחרי אירועי העלאה.

Kotlin+KTX

// Observe state change events such as progress, pause, and resume
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}

Java

// Observe state change events such as progress, pause, and resume
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
});

טיפול בשינויים במחזור החיים של הפעילות

ההעלאות ממשיכות ברקע גם אחרי שמחזור החיים של הפעילות משתנה (למשל, הצגת תיבת דו-שיח או סיבוב המסך). גם המאזינים שתיצרו להם קישור יישארו מחוברים. אם יקראו להן אחרי שהפעילות תיפסק, יכול להיות שהן יגרמו לתוצאות לא צפויות.

כדי לפתור את הבעיה הזו, אפשר להירשם למאזינים ברמת הפעילות כדי לבטל את הרישום שלהם באופן אוטומטי כשהפעילות תיפסק. לאחר מכן, כשהפעילות תתחיל מחדש, תוכלו להשתמש ב-method‏ getActiveUploadTasks כדי לקבל משימות העלאה שעדיין פועלות או שהושלמו לאחרונה.

הדוגמה הבאה ממחישה את זה, ומראה גם איך לשמור את נתיב האחסון שבו נעשה שימוש.

Kotlin+KTX

override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    // If there's an upload in progress, save the reference so you can query it later
    outState.putString("reference", storageRef.toString())
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
    super.onRestoreInstanceState(savedInstanceState)

    // If there was an upload in progress, get its reference and create a new StorageReference
    val stringRef = savedInstanceState.getString("reference") ?: return

    storageRef = Firebase.storage.getReferenceFromUrl(stringRef)

    // Find all UploadTasks under this StorageReference (in this example, there should be one)

    val tasks = storageRef.activeUploadTasks

    if (tasks.size > 0) {
        // Get the task monitoring the upload
        val task = tasks[0]

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this) {
            // Success!
            // ...
        }
    }
}

Java

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // If there's an upload in progress, save the reference so you can query it later
    if (mStorageRef != null) {
        outState.putString("reference", mStorageRef.toString());
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);

    // If there was an upload in progress, get its reference and create a new StorageReference
    final String stringRef = savedInstanceState.getString("reference");
    if (stringRef == null) {
        return;
    }
    mStorageRef = FirebaseStorage.getInstance().getReferenceFromUrl(stringRef);

    // Find all UploadTasks under this StorageReference (in this example, there should be one)
    List<UploadTask> tasks = mStorageRef.getActiveUploadTasks();
    if (tasks.size() > 0) {
        // Get the task monitoring the upload
        UploadTask task = tasks.get(0);

        // Add new listeners to the task using an Activity scope
        task.addOnSuccessListener(this, new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot state) {
                // Success!
                // ...
            }
        });
    }
}

הפונקציה getActiveUploadTasks מאחזרת את כל משימות ההעלאה הפעילות שמתחת למזהה שצוין, ולכן יכול להיות שתצטרכו לטפל בכמה משימות.

המשך ההעלאות אחרי הפעלה מחדש של תהליכים

אם התהליך יושבת, כל ההעלאות שעדיין בתהליך יופסקו. עם זאת, אפשר להמשיך את ההעלאה אחרי שהתהליך יתחיל מחדש על ידי התחלת סשן ההעלאה מחדש עם השרת. כך אפשר לחסוך זמן ורוחב פס, כי ההעלאה לא מתחילה מתחילת הקובץ.

כדי לעשות זאת, מתחילים להעלות דרך putFile. ב-StorageTask שנוצר, קוראים ל-getUploadSessionUri ושומרים את הערך שנוצר באחסון מתמיד (כמו SharedPreferences).

Kotlin+KTX

uploadTask = storageRef.putFile(localFile)
uploadTask.addOnProgressListener { taskSnapshot ->
    sessionUri = taskSnapshot.uploadSessionUri
    if (sessionUri != null && !saved) {
        saved = true
        // A persisted session has begun with the server.
        // Save this to persistent storage in case the process dies.
    }
}

Java

uploadTask = mStorageRef.putFile(localFile);
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        Uri sessionUri = taskSnapshot.getUploadSessionUri();
        if (sessionUri != null && !mSaved) {
            mSaved = true;
            // A persisted session has begun with the server.
            // Save this to persistent storage in case the process dies.
        }
    }
});

אחרי שהתהליך מתחיל מחדש עם העלאה מופסקת, צריך לבצע שוב קריאה ל-putFile. אבל הפעם מעבירים גם את ה-Uri שנשמר.

Kotlin+KTX

// resume the upload task from where it left off when the process died.
// to do this, pass the sessionUri as the last parameter
uploadTask = storageRef.putFile(
    localFile,
    storageMetadata { },
    sessionUri,
)

Java

//resume the upload task from where it left off when the process died.
//to do this, pass the sessionUri as the last parameter
uploadTask = mStorageRef.putFile(localFile,
        new StorageMetadata.Builder().build(), sessionUri);

הסשנים נמשכים שבוע אחד. אם תנסו להמשיך סשן אחרי שפג התוקף שלו או אם אירעה בו שגיאה, תקבלו קריאה חוזרת (callback) עם סטטוס 'כישלון'. באחריותכם לוודא שהקובץ לא השתנה בין ההעלאות.

טיפול בשגיאות

יש כמה סיבות לכך שעשויות להתרחש שגיאות בהעלאה, כולל הקובץ המקומי לא קיים או שהמשתמש לא קיבל הרשאה להעלות את הקובץ הרצוי. מידע נוסף על שגיאות זמין בקטע טיפול בשגיאות במסמכים.

דוגמה מלאה

בהמשך מופיעה דוגמה מלאה להעלאה עם מעקב אחר התקדמות וטיפול בשגיאות:

Kotlin+KTX

// File or Blob
file = Uri.fromFile(File("path/to/mountains.jpg"))

// Create the file metadata
metadata = storageMetadata {
    contentType = "image/jpeg"
}

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/${file.lastPathSegment}").putFile(file, metadata)

// Listen for state changes, errors, and completion of the upload.
// You'll need to import com.google.firebase.storage.component1 and
// com.google.firebase.storage.component2
uploadTask.addOnProgressListener { (bytesTransferred, totalByteCount) ->
    val progress = (100.0 * bytesTransferred) / totalByteCount
    Log.d(TAG, "Upload is $progress% done")
}.addOnPausedListener {
    Log.d(TAG, "Upload is paused")
}.addOnFailureListener {
    // Handle unsuccessful uploads
}.addOnSuccessListener {
    // Handle successful uploads on complete
    // ...
}

Java

// File or Blob
file = Uri.fromFile(new File("path/to/mountains.jpg"));

// Create the file metadata
metadata = new StorageMetadata.Builder()
        .setContentType("image/jpeg")
        .build();

// Upload file and metadata to the path 'images/mountains.jpg'
uploadTask = storageRef.child("images/"+file.getLastPathSegment()).putFile(file, metadata);

// Listen for state changes, errors, and completion of the upload.
uploadTask.addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onProgress(UploadTask.TaskSnapshot taskSnapshot) {
        double progress = (100.0 * taskSnapshot.getBytesTransferred()) / taskSnapshot.getTotalByteCount();
        Log.d(TAG, "Upload is " + progress + "% done");
    }
}).addOnPausedListener(new OnPausedListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onPaused(UploadTask.TaskSnapshot taskSnapshot) {
        Log.d(TAG, "Upload is paused");
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // Handle unsuccessful uploads
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // Handle successful uploads on complete
        // ...
    }
});

עכשיו, אחרי שהעליתם קבצים, נסביר איך להוריד אותם מ-Cloud Storage.