Summary of Android Q Sandbox Adaptation Multimedia Files

Time:2019-7-30

Overview

All content access changes are shown in the following figure:

Summary of Android Q Sandbox Adaptation Multimedia Files

Scanning, Reading and Writing of External Media Files

The easiest way to be trampled is to read or write external media files, photos, videos and pictures.

scanning

The first is scanning. Scanning is still done using query Media Store. MediaStore is a multimedia database in Android system. The code is shown in the following figure, for example, to search for local videos:

protected List<VideoInfo> doInBackground(Void... params) {
    mContentResolver = context.getContentResolver();

    String[] mediaColumns = { MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA,
            MediaStore.Video.Media.TITLE, MediaStore.Video.Media.MIME_TYPE,
            MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.SIZE,
            MediaStore.Video.Media.DATE_ADDED, MediaStore.Video.Media.DURATION,
            MediaStore.Video.Media.WIDTH, MediaStore.Video.Media.HEIGHT };

    Cursor mCursor = mContentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaColumns,
            null, null, MediaStore.Video.Media.DATE_ADDED);


    if (mCursor == null) {
        return null;
    }

    // Note that DATA data represented the path of the file before Android Q, but on Android Q the path could not be accessed, so it was meaningless.
    ixData = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA);
    ixMime = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.MIME_TYPE);
    // ID is the key field for reading files on Android Q
    ixId = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
    ixSize = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE);
    ixTitle = mCursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE);

    allImages = new ArrayList<VideoInfo>();
    mTotalVideoCount = 0;

    mCursor.moveToLast();
    
    while (mCursor.moveToPrevious()) {
       if (addVideo(mCursor) == 0) {
           continue;
       } else if (addVideo(mCursor) == 1) {
           break;
       }
    }

    mCursor.close();
    
    return allImages;
}

Since data is not available, you need to know how to use ID. First, you need to use ID to assemble content uri, as follows:

public getRealPath(String id) {
    return MediaStore.Video.Media.EXTERNAL_CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build().toString();
}

Image is replaced by MediaStore. Images.

Read and write

Second, read the content uri. Note here that File file = new File (contentUri); the file is not available. File. exist () is false.

Then there are two problems: 1. How to determine the existence of a file in the form of ContentUri; 2. How to read or write a file.

First, for content Uri to be read, you have to rely on ContentResolver.

Secondly, for 1, the relatively easy API provided in the Google Document was not found, and only the form of opening FileDescriptor was successful. The code is as follows:

public boolean isContentUriExists(Context context, Uri uri) {
    if (null == context) {
        return false;
    }
    ContentResolver cr = context.getContentResolver();
    try {
        AssetFileDescriptor afd = cr.openAssetFileDescriptor(uri, "r");
        if (null == afd) {
            iterator.remove();
        } else {
            try {
                afd.close();
            } catch (IOException e) {
            }
        }
    } catch (FileNotFoundException e) {
        return false;
    }

    return true;
}

The biggest problem with this method is that a synchronous I/O call can easily cause thread waiting. Therefore, there is no direct and good way to solve the problem that the scanned files in MediaStore may not exist at present.

For Question 2, as shown in Figure 1, you can get the Asset File Descriptor from Content Resolver with the help of Content Uri, and then you can get InputSteam or OutputStream, so the next reading and writing is very natural, as follows:

public static void copy(File src, ParcelFileDescriptor parcelFileDescriptor) throws IOException {
    FileInputStream istream = new FileInputStream(src);
    try {
        FileOutputStream ostream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
        try {
            IOUtil.copy(istream, ostream);
        } finally {
            ostream.close();
        }
    } finally {
        istream.close();
    }
}

public static void copy(ParcelFileDescriptor parcelFileDescriptor, File dst) throws IOException {
    FileInputStream istream = new FileInputStream(parcelFileDescriptor.getFileDescriptor());
    try {
        FileOutputStream ostream = new FileOutputStream(dst);
        try {
            IOUtil.copy(istream, ostream);
        } finally {
            ostream.close();
        }
    } finally {
        istream.close();
    }
}
    
    
public static void copy(InputStream ist, OutputStream ost) throws IOException {
    byte[] buffer = new byte[4096];
    int byteCount = 0;
    While ((byteCount = ist. read (buffer)!=-1) {// Loop reads buffer bytes from input stream
        Ost. write (buffer, 0, byteCount); // Write the read input stream to the output stream
    }
}

Save media files to public areas

With Video as an example, Image and Downloads are basically similar:

public static Uri insertVideoIntoMediaStore(Context context, String fileName) {
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
    contentValues.put(MediaStore.Video.Media.DATE_TAKEN, System.currentTimeMillis());
    contentValues.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");

    Uri uri = context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentValues);
    return uri;
}

What we do here is to insert a new record into the MediaStore, which will return us an empty Content Uri. Then the problem will be written into the Content Uri, and the code described in the previous section will be applied.

Video’s Thumbnail Problem

Video’s Thumbnail path is no longer available on Android Q, and because Video’s Thumbnail ID is not exposed, Video’s Thumbnail can only use the method of real-time acquisition of Bitmap, as follows:

private Bitmap getThumbnail(ContentResolver cr, long videoId) throws Throwable {
    return MediaStore.Video.Thumbnails.getThumbnail(cr, videoId, MediaStore.Video.Thumbnails.MINI_KIND,
            null);
}

You can look at the implementation of Android SDK, the most critical part of which is:

String column = isVideo ? "video_id=" : "image_id=";
c = cr.query(baseUri, PROJECTION, column + origId, null, null);
if (c != null && c.moveToFirst()) {
    bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
    if (bitmap != null) {
        return bitmap;
    }
}

If you go further, you can find that you open the Video/Image file directly to calculate Thumbnail.

private static Bitmap getMiniThumbFromFile(
        Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
    Bitmap bitmap = null;
    Uri thumbUri = null;
    try {
        long thumbId = c.getLong(0);
        String filePath = c.getString(1);
        thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
        ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
        bitmap = BitmapFactory.decodeFileDescriptor(
                pfdInput.getFileDescriptor(), null, options);
        pfdInput.close();
    } catch (FileNotFoundException ex) {
        Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
    } catch (IOException ex) {
        Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
    } catch (OutOfMemoryError ex) {
        Log.e(TAG, "failed to allocate memory for thumbnail "
                + thumbUri + "; " + ex);
    }
    return bitmap;
}

This API is undoubtedly very unreasonable in design. It does not expose the system cache of Thumbnail to developers, which results in a great time-consuming re-I/O calculation every time. It is strongly appealed that the official version of Android Q can correct the design flaw of this API.

Recommended Today

What black technology does the real-time big data platform use behind the glory of the king?

Hello everyone, I’m Xu Zhenwen. Today’s topic is “Tencent game big data service application practice based on Flink + servicemesh”, which is mainly divided into the following four parts: Introduction to background and Solution Framework Real time big data computing onedata Data interface service onefun Microservice & servicemesh 1、 Introduction to the solution framework and […]