近期遇到了问题,通过我司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)