Android 导入视频到图库(兼容Android10)

近期遇到了问题,通过我司App下载视频后,Android 自带图库中找不到相关视频.

总结一下目前使用过的三种途径:

如果着急解决手上的需求,请直接看方式三的楼主使用方式,一步到位~

方式一:

/**以发广播的形式触发Android 媒体内容提供者更新数据. 低版本Android可能会有效(受厂商限制,某些厂商为了降低手机电量消耗,限定了此类扫描方式的执行间隔,会有延迟或者无法触发扫描的风险)*/
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Video.Media.EXTERNAL_CONTENT_URI));

方式二:

这种方式是方式一的底层实现.

官方文档

 /**
     * Convenience for constructing a {@link MediaScannerConnection}, calling
     * {@link #connect} on it, and calling {@link #scanFile} with the given
     * path and mimeType when the connection is
     * established.
     * @param context The caller's Context, required for establishing a connection to
     * the media scanner service.
     * Success or failure of the scanning operation cannot be determined until
     * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
     * @param paths Array of paths to be scanned.
     * @param mimeTypes Optional array of MIME types for each path.
     * If mimeType is null, then the mimeType will be inferred from the file extension.
     * @param callback Optional callback through which you can receive the
     * scanned URI and MIME type; If null, the file will be scanned but
     * you will not get a result back.
     * @see #scanFile(String, String)
     */
    public static void scanFile(Context context, String[] paths, String[] mimeTypes,
            OnScanCompletedListener callback) {
        ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
        MediaScannerConnection connection = new MediaScannerConnection(context, client);
        client.mConnection = connection;
        connection.connect();
    }

楼主使用方式:

 MediaScannerConnection.scanFile(getApplicationContext(),
                new String[]{"视频存储路径"},
                new String[]{"video/mp4"},
                new MediaScannerConnection.OnScanCompletedListener() {
                    @Override
                    public void onScanCompleted(String path, Uri uri) {
                        /**通常这里的Uri不为空的情况下 我们的数据就已经插入到android 媒体内容提供者数据库了*/
                        Log.e("onScanCompleted path :=", path);
                        Log.e("onScanCompleted uri :=", uri == null ? "uri == null" : uri.toString());
                        /**test start*/
                        ContentResolver contentResolver = getApplicationContext().getContentResolver();
                        Cursor query = contentResolver.query(uri, null, null, null, null);
                        if (query != null) {
                            int count = query.getCount();
                            if (count > 0) {
                                /**如果Count > 0 的情况下,就是数据成功插入Android 媒体库的数据库了,此时我们打开图库,或者在抖音上传视频的时候就可以找到我们刚才保存的视频了*/
                            } else {
                                /**如果Count < 0 的情况下,可能产生了问题,图库中不一定有我们刚才试图写入的数据*/
                            }
                        }
                        /**test end*/
                    }
                });

方式三:

这种方式是我从AOSP源码中的相机源码中找到的解决方案,简单粗暴,兼容性爆表,测试了Android 10,以及miui,蓝/绿,华为,谷歌,三星.都能很好的实现我们的需求.

/**AOSP 相关类路径*/
com.android.camera.VideoModule
/**此类相关代码*/
private void generateVideoFilename(int outputFileFormat) {
        long dateTaken = System.currentTimeMillis();
        String title = createName(dateTaken);
        // Used when emailing.
        String filename = title + convertOutputFormatToFileExt(outputFileFormat);
        String mime = convertOutputFormatToMimeType(outputFileFormat);
        String path = Storage.DIRECTORY + '/' + filename;
        String tmpPath = path + ".tmp";
        mCurrentVideoValues = new ContentValues(9);
        mCurrentVideoValues.put(Video.Media.TITLE, title);
        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
        mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
        mCurrentVideoValues.put(Video.Media.DATA, path);
        mCurrentVideoValues.put(Video.Media.WIDTH, mProfile.videoFrameWidth);
        mCurrentVideoValues.put(Video.Media.HEIGHT, mProfile.videoFrameHeight);
        mCurrentVideoValues.put(Video.Media.RESOLUTION,
                Integer.toString(mProfile.videoFrameWidth) + "x" +
                Integer.toString(mProfile.videoFrameHeight));
        Location loc = mLocationManager.getCurrentLocation();
        if (loc != null) {
            mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
            mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
        }
        mVideoFilename = tmpPath;
        Log.v(TAG, "New video filename: " + mVideoFilename);
    }

private void saveVideo() {
        if (mVideoFileDescriptor == null) {
            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
            if (duration > 0) {
                //
            } else {
                Log.w(TAG, "Video duration <= 0 : " + duration);
            }
            mCurrentVideoValues.put(Video.Media.SIZE, new File(mCurrentVideoFilename).length());
            mCurrentVideoValues.put(Video.Media.DURATION, duration);
            getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
                    mCurrentVideoValues, mOnVideoSavedListener);
            logVideoCapture(duration);
        }
        mCurrentVideoValues = null;
    }
/**接下来是 getServices().getMediaSaver().addVideo(mCurrentVideoFilename,
                    mCurrentVideoValues, mOnVideoSavedListener); 的实现*/
/**AOSP 相关类路径*/
com.android.camera.MediaSaverImpl
@Override
    public void addVideo(String path, ContentValues values, OnMediaSavedListener l) {
        // We don't set a queue limit for video saving because the file
        // is already in the storage. Only updating the database.
        new VideoSaveTask(path, values, l, mContentResolver).execute();
    }
private class VideoSaveTask extends AsyncTask  {
        private String path;
        private final ContentValues values;
        private final OnMediaSavedListener listener;
        private final ContentResolver resolver;

        public VideoSaveTask(String path, ContentValues values, OnMediaSavedListener l,
                             ContentResolver r) {
            this.path = path;
            this.values = new ContentValues(values);
            this.listener = l;
            this.resolver = r;
        }

        @Override
        protected Uri doInBackground(Void... v) {
            Uri uri = null;
            try {
                Uri videoTable = Uri.parse(VIDEO_BASE_URI);
                uri = resolver.insert(videoTable, values);

                // Rename the video file to the final name. This avoids other
                // apps reading incomplete data.  We need to do it after we are
                // certain that the previous insert to MediaProvider is completed.
                String finalName = values.getAsString(Video.Media.DATA);
                File finalFile = new File(finalName);
                if (new File(path).renameTo(finalFile)) {
                    path = finalName;
                }
                resolver.update(uri, values, null, null);
            } catch (Exception e) {
                // We failed to insert into the database. This can happen if
                // the SD card is unmounted.
                Log.e(TAG, "failed to add video to media store", e);
                uri = null;
            } finally {
                Log.v(TAG, "Current video URI: " + uri);
            }
            return uri;
        }

        @Override
        protected void onPostExecute(Uri uri) {
            if (listener != null) {
                listener.onMediaSaved(uri);
            }
        }
    }

楼主使用方式:

private static final String VIDEO_BASE_URI = "content://media/external/video/media";
private void insertVideo(String videoPath) {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        retriever.setDataSource(videoPath);
        int nVideoWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
        int nVideoHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
        int duration = Integer
                .parseInt(retriever
                        .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
        long dateTaken = System.currentTimeMillis();
        File file = new File(videoPath);
        String title = file.getName();
        String filename = file.getName();
        String mime = "video/mp4";
        ContentValues mCurrentVideoValues = new ContentValues(9);
        mCurrentVideoValues.put(MediaStore.Video.Media.TITLE, title);
        mCurrentVideoValues.put(MediaStore.Video.Media.DISPLAY_NAME, filename);
        mCurrentVideoValues.put(MediaStore.Video.Media.DATE_TAKEN, dateTaken);
        mCurrentVideoValues.put(MediaStore.MediaColumns.DATE_MODIFIED, dateTaken / 1000);
        mCurrentVideoValues.put(MediaStore.Video.Media.MIME_TYPE, mime);
        mCurrentVideoValues.put(MediaStore.Video.Media.DATA, videoPath);
        mCurrentVideoValues.put(MediaStore.Video.Media.WIDTH, nVideoWidth);
        mCurrentVideoValues.put(MediaStore.Video.Media.HEIGHT, nVideoHeight);
        mCurrentVideoValues.put(MediaStore.Video.Media.RESOLUTION, Integer.toString(nVideoWidth) + "x" + Integer.toString(nVideoHeight));
        mCurrentVideoValues.put(MediaStore.Video.Media.SIZE, new File(videoPath).length());
        mCurrentVideoValues.put(MediaStore.Video.Media.DURATION, duration);
        /**相关定位信息逻辑,我司需求不涉及到这一块,故注释,各位请酌情使用~*/
//        Location loc = mLocationManager.getCurrentLocation();
//        if (loc != null) {
//            mCurrentVideoValues.put(MediaStore.Video.Media.LATITUDE, loc.getLatitude());
//            mCurrentVideoValues.put(MediaStore.Video.Media.LONGITUDE, loc.getLongitude());
//        }
        ContentResolver contentResolver = getApplication().getContentResolver();
        Uri videoTable = Uri.parse(VIDEO_BASE_URI);
        Uri uri = contentResolver.insert(videoTable, mCurrentVideoValues);
        /**通常这里的uri不为空的情况下 我们的数据就已经插入到android 媒体内容提供者数据库了*/
        /**test start*/
                        ContentResolver contentResolver = getApplicationContext().getContentResolver();
                        Cursor query = contentResolver.query(uri, null, null, null, null);
                        if (query != null) {
                            int count = query.getCount();
                            if (count > 0) {
                                /**如果Count > 0 的情况下,就是数据成功插入Android 媒体库的数据库了,此时我们打开图库,或者在抖音上传视频的时候就可以找到我们刚才保存的视频了*/
                            } else {
                                /**如果Count < 0 的情况下,可能产生了问题,图库中不一定有我们刚才试图写入的数据*/
                            }
                        }
                        /**test end*/
    }

总结

三种方式,第三种性能高,更可靠,推荐大家使用第三种.

文件存储位置不能在外置存储/DCIM/下,已知问题:miui 11不识别,就算我们的信息已经写入Android 媒体内容提供者的数据库中.

文件存储位置不能在App的私有沙箱内(Android 7.0 的安全策略不允许跨应用分享私有沙箱存储,除非使用特定手段)

Android 10 系统,应用不能向外置存储非Download路径之外的位置存储应用数据.

/**Download 路径获取方式*/
Environment.getExternalStoragePublicDirectory(DOWNLOAD_SERVICE)

你可能感兴趣的:(Android 导入视频到图库(兼容Android10))