CameraRollManager
提供了JS与设备相册进行交互的相关功能,它包括两个API:
- saveToCameraRoll 将本地图片存储到媒体库
- getPhotos 获取媒体库中的图片信息
具体实现分析如下。
saveToCameraRoll
-
input
:String
uri 待存储图片的uri("file:///sdcard/img.png"
),在Android端必须是本地图片的uri。 -
output
: Promise 回调 返回存储成功后的uri
saveToCameraRoll方法启动了一个后台异步任务来完成图片的存储工作:
protected void doInBackgroundGuarded(Void... params) {
File source = new File(mUri.getPath()); // 待存储图片文件
FileChannel input = null, output = null;
try {
// SD卡目录
File exportDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
exportDir.mkdirs();
if (!exportDir.isDirectory()) { //没权限
mPromise.reject(ERROR_UNABLE_TO_LOAD, "External media storage directory not available");
return;
}
File dest = new File(exportDir, source.getName());
int n = 0;
// 避免重名覆盖
String fullSourceName = source.getName();
String sourceName, sourceExt;
if (fullSourceName.indexOf('.') >= 0) {
sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.'));
sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.'));
} else {
sourceName = fullSourceName;
sourceExt = "";
}
while (!dest.createNewFile()) {
dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt);
}
// 开始文件复制操作
input = new FileInputStream(source).getChannel();
output = new FileOutputStream(dest).getChannel();
output.transferFrom(input, 0, input.size());
input.close();
output.close();
// 发起扫描
MediaScannerConnection.scanFile(
mContext,
new String[]{dest.getAbsolutePath()},
null,
new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, Uri uri) {
if (uri != null) {
mPromise.resolve(uri.toString());
} else {
mPromise.reject(ERROR_UNABLE_TO_SAVE, "Could not add image to gallery");
}
}
});
} catch (IOException e) {
mPromise.reject(e);
} finally {
if (input != null && input.isOpen()) {
try {
input.close();
} catch (IOException e) {
FLog.e(ReactConstants.TAG, "Could not close input channel", e);
}
}
if (output != null && output.isOpen()) {
try {
output.close();
} catch (IOException e) {
FLog.e(ReactConstants.TAG, "Could not close output channel", e);
}
}
}
}
public void getPhotos(final ReadableMap params, final Promise promise)
-
input
:
{
"first": int,返回的图片个数
"after": String 上一次调用所返回的cursor
"groupName":String 相册名称
"mimeType":Array //指定要返回的图片类型
}
-
output
:示例
{
"page_info": { //本页搜索信息
"end_cursor": "1496373800000",
"has_next_page": true
},
"edges": [// 本次搜索所获取的图片信息, 每个node表示一张图片
{
"node": {
"timestamp": 1496384866,
"group_name": "splash", // 相册名称
"type": "image/jpeg",
"image": {
"height": 1920,
"width": 1080,
"uri": "content://media/external/images/media/7217"
}
}
},
{
"node": {
"timestamp": 1496373800,
"group_name": "a",
"type": "image/png",
"image": {
"height": 64,
"width": 96,
"uri": "content://media/external/images/media/7218"
}
}
}
]
}
getPhotos 在启动了一个后台异步任务使用系统提供的ContentResolver
从SD卡中获取指定的图片。
protected void doInBackgroundGuarded(Void... params) {
StringBuilder selection = new StringBuilder("1");
List<String> selectionArgs = new ArrayList<>();
if (!TextUtils.isEmpty(mAfter)) {
selection.append(" AND " + SELECTION_DATE_TAKEN);
selectionArgs.add(mAfter);
}
if (!TextUtils.isEmpty(mGroupName)) {
selection.append(" AND " + SELECTION_BUCKET);
selectionArgs.add(mGroupName);
}
if (mMimeTypes != null && mMimeTypes.size() > 0) {
selection.append(" AND " + Images.Media.MIME_TYPE + " IN (");
for (int i = 0; i < mMimeTypes.size(); i++) {
selection.append("?,");
selectionArgs.add(mMimeTypes.getString(i));
}
selection.replace(selection.length() - 1, selection.length(), ")");
}
WritableMap response = new WritableNativeMap();
ContentResolver resolver = mContext.getContentResolver();
try {
Cursor photos = resolver.query(
Images.Media.EXTERNAL_CONTENT_URI,
PROJECTION,
selection.toString(),
selectionArgs.toArray(new String[selectionArgs.size()]),
Images.Media.DATE_TAKEN + " DESC, " + Images.Media.DATE_MODIFIED + " DESC LIMIT " +
(mFirst + 1)); // set LIMIT to first + 1 so that we know how to populate page_info
if (photos == null) {
mPromise.reject(ERROR_UNABLE_TO_LOAD, "Could not get photos");
} else {
try {
putEdges(resolver, photos, response, mFirst);
putPageInfo(photos, response, mFirst);
} finally {
photos.close();
mPromise.resolve(response);
}
}
} catch (SecurityException e) {
mPromise.reject(
ERROR_UNABLE_TO_LOAD_PERMISSION,
"Could not get photos: need READ_EXTERNAL_STORAGE permission",
e);
}
}
在JS端,ReactNative将CameraRollManager封装为CameraRoll.js,加入了一些入参和回参的检查。