我们经常会遇到将图片保存到本地的操作,而且有时候我们需要将图片保存到一个单独的相册,比如微信保存的图片是放到微信相册,微博放到了微博相册。
我们保存的图片可能有很多的类型,比如 Bitmap、网络图片、字节数组、资源 ID 等,根据不同的类型我们需要做不同的处理。
我们先看看怎么把 Bitmap 保存到本地相册:
public static void saveImageToGallery(Context context, Bitmap bitmap) {
// 先将图片保存到文件
File imageDir = new File(Environment.getExternalStorageDirectory(), "testGallery");
if (!imageDir.exists()) {
imageDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(imageDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
// 再通知图库更新数据库
Uri uri = Uri.fromFile(file);
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri));
}
这个方法可以将保存的图片在系统图库保存到单独的相册:testGallery,我们还有另一种方法,就直接将图片保存到系统的大图库中:
public static void saveImageToGallery(Context context, Bitmap bitmap) {
// 先将图片保存到文件
File imageDir = new File(Environment.getExternalStorageDirectory(), "testGallery");
if (!imageDir.exists()) {
imageDir.mkdir();
}
String fileName = System.currentTimeMillis() + ".jpg";
File file = new File(imageDir, fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
// 其次把文件插入到系统图库
try {
MediaStore.Images.Media.insertImage(context.getContentResolver(),
file.getAbsolutePath(), "Dolfin", "Translation evidence of Dolfin");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse(file.getAbsolutePath())));
}
这个方法是将图片直接存入到系统图库中,没有新建一个独立的相册。而且我们要知道其实我们的图片并不是直接保存到图库中,因为图库是个应用,我们不能直接将图片保存到别的应用中,我们只是将图片保存到了 SD 卡中,然后发了一个广播来通知系统图库更新数据库,间接将图片保存到了图库中。
我们知道了将 Bitmap 保存到图库中,其实要保存别的形式就可以将别的类型转换为 Bitmap 就行,比如:
// 资源文件保存
public static void saveImageToGallery(Context context, int resId) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resId);
if (bitmap != null) {
saveImageToGallery(context, bitmap);
}
}
// 字节数组保存
public static void saveImageToGallery(Context context, byte[] bytes) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
if (bitmap != null) {
saveImageToGallery(context, bitmap);
}
}
当然了,如果还需要别的类型,我们完全可以使用 BitmapFactory 来转换就行。
上面说了这几种类型,但是并没有网络图片类型,因为网络图片类型我们要转换为 Bitmap 需要先把图片下载下来,而网络操作是不能放在 Android 主线程中的,所以我们必须要使用线程工具,Android 原生的就有线程工具,比如 Handler、AsyncTask,全凭自己喜好去选择,我这里使用的是 AsyncTask。
虽然我们知道这里图片下载完成后是直接保存到图库,但是毕竟是耗时操作,而且也有可能是别的地方会复用,所以我们还是提供一个回调:
public abstract static class OnImageDownloadFinishListener {
public void start(){}
public void finish(Bitmap bitmap){}
public void error(Exception e){}
}
这里我没有使用接口,而是使用了抽象类,主要是因为这里图片保存的回调我们可能就在工具类中使用了,如果使用接口会导致复用的使用去重写一些没必要的方法,使用抽象类可以按需重写。
接下来就是将图片下载下来:
private static Bitmap getBitmap(String imageUrl,OnImageDownloadFinishListener listener) {
HttpURLConnection connection = null;
Bitmap bitmap = null;
try {
connection = (HttpURLConnection) new URL(imageUrl).openConnection();
connection.setConnectTimeout(6000);
connection.setDoInput(true);
connection.setUseCaches(false);
bitmap = BitmapFactory.decodeStream(connection.getInputStream());
} catch (IOException e) {
if (listener != null){
// 回调错误
listener.error(e);
}
e.printStackTrace();
}
return bitmap;
}
这里因为写的 Demo,没有必要去导入各种网络库,大家可以按自己的项目需要使用不同的网络库读出输入流就行。因为这个是耗时操作,所以还是要放入 AsyncTask 中:
private static Bitmap bitmap;
public static void downImage(Context context,String imageUrl, OnImageDownloadFinishListener listener) {
class ImageTask extends AsyncTask<Void,Void,Void> {
@Override
protected void onPreExecute() {
super.onPreExecute();
if (listener != null){
listener.start();
}
}
@Override
protected Void doInBackground(Void... voids) {
bitmap = getBitmap(imageUrl,listener);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
// 结束
if (bitmap == null){
listener.error(new Exception("下载失败"));
}else {
// 保存到相册
saveImageToGallery(context,bitmap);
if (listener != null){
listener.finish(bitmap);
}
}
}
}
new ImageTask().execute();
}
这里我并没有写进度条,大家可以按需重写 AsyncTask 的 onProgressUpdate,不管是在 DoInBackground() 中去调用 publishProgress()还是去添加进度的回调方法都可以。然后我们还是封装一下:
// 网络图片保存
public static void saveImageToGallery(Context context, String imageUrl,OnImageDownloadFinishListener listener) {
downImage(context,imageUrl,listener);
}
这里关于图片保存到图库就是这些,当然还是不能忘了我们的权限问题:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
而这里涉及到了读取文件的敏感权限,所以我们就必须考虑到 Android 6.0 以上的动态权限:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (check == PackageManager.PERMISSION_GRANTED) {
// 有权限就保存图片
ImageUtil.saveImageToGallery(this,R.drawable.hashmap_2_2);
} else {
// 没权限就申请权限
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
} else {
ImageUtil.saveImageToGallery(this,R.drawable.hashmap_2_2);
}
所以我们还要去重写 onRequestPermissionsResult() 方法,去监听权限的变化:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
// 根据我们动态请求权限时传入的 requestCode 做处理
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 允许权限,做处理
} else {
// 不允许权限,做处理
}
break;
default:
break;
}
}
接下来就是具体的使用:
// 本地图片
binding.btnSaveImage.setOnClickListener(view -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
int check = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (check == PackageManager.PERMISSION_GRANTED) {
// 有权限就保存图片
ImageUtil.saveImageToGallery(this, R.drawable.hashmap_2_2);
} else {
// 没权限就申请权限
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
} else {
ImageUtil.saveImageToGallery(this, R.drawable.hashmap_2_2);
}
});
// 网络图片
binding.btnSaveImageInternet.setOnClickListener(view ->
ImageUtil.saveImageToGallery(this, "https://upload-images.jianshu.io/upload_images/2851519-c266af39ef8df081.png?imageMogr2/auto-orient/strip|imageView2/2/w/419/format/webp"
, new ImageUtil.OnImageDownloadFinishListener() {
@Override
public void start() {
System.out.println("开始了");
}
@Override
public void finish(Bitmap bitmap) {
System.out.println("结束了");
}
@Override
public void error(Exception e) {
System.out.println("错误了");
}
})
);
好了,关于保存图片到图库就分享到这了,如果喜欢的话欢迎关注我的个人微信公众号,谢谢。