You need to enable JavaScript to run this app.
导航

Android 10 分区存储

最近更新时间2022.10.18 20:15:20

首次发布时间2022.10.18 20:15:20

背景信息

以 Android 10 为目标平台,开启分区存储,不能直接通过文件路径(File)访问非应用专属存储空间的文件。因此,需要您通过 MediaStore 和 SAF 读取数据,并传给上传 SDK。详细说明请参考分区存储

实现 BDMediaDataReader

public interface BDMediaDataReader {
    /**
     * 打开对应 fileIndex 的文件
     *
     *  @param fileIndex 对应的文件 index,用于图片上传中多个文件上传。
     *        例如第一个文件,fileIndex 为 0。
     *  @return 如果打开成功则返回 1,否则返回- 1.
     */
    int open(int fileIndex);
    /**
     * 读取 size 大小的数据
     *
     *  @param  fileIndex 对应的文件 index,用于图片上传中多个文件上传。
     *  @param  offset 文件的 offset,
     *  @param  data 读取到的数据需要填到里面
     *  @param  size 读取数据的大小
     *  @return  如果读到文件末尾返回 ReadFileEnd,读文件出错返回 ReadFileError。
     */
    int read(int fileIndex,long offset,byte[] data,int size);
    
    // 关闭对应 fileIndex 的文件
    int close(int fileIndex);
    
    // 获取文件信息,例如 key == KeyIsGetFileSize 代表获取文件大小
    long getValue(int fileIndex,int key);
}

设置 Reader

public class BDMediaDataReaderImpl implements BDMediaDataReader {
  ...
}

// 第二个参数为要上传的文件个数
mUploader.setMediaDataReader(new BDMediaDataReaderImpl(), 1);

示例

使用 FileDescriptor 实现 BDMediaDataReader,其中需要注意如下信息:

  1. mContext.getContentResolver().openFileDescriptor(mUri, "r") 返回的对象在上传周期内,需要保证不能被释放
  2. 仅保证 mContext.getContentResolver().openFileDescriptor(mUri, "r").getFileDescriptor() 返回的 FileDescriptor 对象不被释放,是不可以的。否则,会 crash 或者导致各种场景上传失败。

以下代码示例实现了单个文件上传的功能,仅供参考。

package com.bytedance.vcloud.bduploader;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.Log;
import com.ss.bduploader.BDMediaDataReader;
import java.io.FileDescriptor;
public class BDReaderImpl implements BDMediaDataReader {
    private static final String TAG = "ttmn BDReaderImpl";
    private final Context mContext;
    private Uri mUri;
    private long mFileLength;
    private FileDescriptor mFd;
    private ParcelFileDescriptor mPfd;
    BDReaderImpl(Context context) {
        mContext = context;
        getVideoUri(context);
    }
    @Override
    public int open(int fileIndex) {
        Log.d(TAG, "open fileIndex:" + fileIndex);
        if (mContext == null || mUri == null) {
            return -1;
        }
        if (mFd != null) {
            return 1;
        }
        try {
            ContentResolver resolver = mContext.getApplicationContext().getContentResolver();
            mPfd = resolver.openFileDescriptor(mUri, "r");
            mFd = mPfd.getFileDescriptor();
            mFileLength = Os.lseek(mFd, 0, OsConstants.SEEK_END);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 1;
    }
    @Override
    public int read(int fileIndex, long offset, byte[] data, int size) {
        Log.d(TAG, "read fileIndex:" + fileIndex + ", offset:" + offset
                   + ", dataSize:" + data.length + ", size:" + size);
        if (mFd == null) {
            return ReadFileError;
        }
        if (offset >= mFileLength) {
            return ReadFileEnd;
        }
        try {
            Os.lseek(mFd, offset, OsConstants.SEEK_SET);
            return Os.read(mFd, data, 0, size);
        } catch (Exception e) {
            e.printStackTrace();
            return ReadFileError;
        }
    }
    @Override
    public int close(final int fileIndex) {
        if (mFd == null) {
            return 0;
        }
        try {
            Os.close(mFd);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        mFd = null;
        mPfd = null;
        return 0;
    }
    @Override
    public long getValue(int fileIndex, int key) {
        if (key == KeyIsGetFileSize) {
            return mFileLength;
        }
        return 0;
    }
    // android 10 分区存储,获取视频 Uri
    private void getVideoUri(Context context) {
        if (context == null) {
            return;
        }
        ContentResolver resolver = context.getApplicationContext().getContentResolver();
        Uri collection;
        if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.Q) {
            collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
        } else {
            collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        }
        String[] projection = new String[] {
                MediaStore.Video.Media._ID,
                MediaStore.Video.Media.DISPLAY_NAME,
        };
        String selection = MediaStore.Images.Media.MIME_TYPE  + "=?";
        String[] selectionArgs = { "video/mp4", };
        String sortOrder = MediaStore.Video.Media.DISPLAY_NAME  + " ASC";
        try (Cursor cursor = resolver.query(collection, projection,
                selection, selectionArgs, sortOrder
        )) {
            int idColumn = cursor.getColumnIndex(MediaStore.Video.Media._ID);
            int nameColumn = cursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME);
            while (cursor.moveToNext()) {
                long id = cursor.getLong(idColumn);
                String name = cursor.getString(nameColumn);
                // 找到要上传的视频文件
                if (TextUtils.equals(name, "testUpload.mp4")) {
                    mUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                            id);
                }
            }
        }
    }
}