Tải ảnh về bộ nhớ trong của Android với thư viện Picasso
Bài đăng này đã không được cập nhật trong 3 năm
1. Giới thiệu về thư viện Picasso
Chắc hẳn với các developer chúng ta thì Picasso cũng không có gì quá xa lạ , nhưng nếu bạn nào chưa biết thì mình xin giới thiệu :
Picasso ở đây không phải là ông họa sĩ tài danh tác giả của bức tranh "Người đàn bà khóc" đâu Picasso là 1 thư viện hỗ trợ downloading và caching hình ảnh rất mạnh dành cho Android , được phát triển bởi Squareup (tác giả của Retrofit đó)
Okay , và với các bạn đã biết thì Picasso sẽ giúp chúng ta tải những hình ảnh thông qua URL và set vào ImageView một cách mượt mà thông qua phương thức
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);:
Thật là đơn giản , nó thậm chí còn hỗ trợ chúng ta transform lại image
Picasso.with(context)
.load(url)
.resize(50, 50)
.centerCrop()
.into(imageView)
Hay là dùng Place Holder để hiển thị trong khi đang tải Image hoặc image error khi tải lỗi (link die chẳng hạn)
Picasso.with(context)
.load(url)
.placeholder(R.drawable.user_placeholder)
.error(R.drawable.user_placeholder_error)
.into(imageView);
Cũng có thể dùng Picasso để tải Resources
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
Quá nhiều tính năng hay , vậy chúng ta cài đặt Picasso như thế nào ? Chỉ với 1 line code trong Gradle Chúng ta đã tích hợp vào trong dự án của mình
2. Save Image về storage với Picasso
Ở đoạn trên mình vừa giới thiệu sơ qua cho các bạn về thư viện Picasso và cách sử dụng cơ bản , nhưng thật sự thư viện này hỗ trợ chúng ta nhiều hơn thế Giờ đây , không cần phải viết những Async Task để đọc image , chuyển thành Stream rồi chuyển thành Bitmap , thật sự đau đầu với những newbie quá
Với Picasso , chúng ta hoàn toàn đơn giản hóa vấn đề nè , đó là sử dụng interface Target của Picasso Nó sẽ cung cấp cho chúng ta 3 phương thức override
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// Đây là nơi viết code xử lý sau khi tải thành công image
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
// Đây là nơi viết code xử lý sau khi không tải được image
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
// Đây là nơi viết code xử lý khi đang tải image
}
Quá sức đơn giản Nhìn sơ qua các bạn nếu "ma lanh" 1 tí thì đã hiểu là có thể viết code lưu hình ảnh vào máy ngay ở phương thức onBitmapLoaded rồi đúng không ?
3. Cách thực hiện
Okay , dựa vào kiến thức trên , ta hãy tạo ra lớp SaveImageHelper và implement Target từ com.squareup.picasso.Target;
public class SaveImageHelper implements Target {
private Context context;
private WeakReference<AlertDialog> alertDialogWeakReference;
private WeakReference<ContentResolver> contentResolverWeakReference;
private String name;
private String desc;
public SaveImageHelper(Context context,AlertDialog alertDialog, ContentResolver contentResolver, String name, String desc) {
this.context = context;
this.alertDialogWeakReference = new WeakReference<AlertDialog>(alertDialog);
this.contentResolverWeakReference = new WeakReference<ContentResolver>(contentResolver);
this.name = name;
this.desc = desc;
}
@Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
ContentResolver r = contentResolverWeakReference.get();
AlertDialog dialog = alertDialogWeakReference.get();
if(r != null)
MediaStore.Images.Media.insertImage(r,bitmap,name,desc);
dialog.dismiss();
//Open galerry after download
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
context.startActivity(Intent.createChooser(intent,"VIEW PICTURE"));
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
}
Trong này , chúng ta cần chú ý
private Context context; //Thằng này cần để gọi activity mới sau khi tải xong
private WeakReference<AlertDialog> alertDialogWeakReference; // Thằng này sẽ ánh xạ tới cái dialog ở MainActivity
private WeakReference<ContentResolver> contentResolverWeakReference; // Thằng này thì ánh xạ tới contentResolver ở MainActivity
private String name;
private String desc;
Ở trên mình comment sơ qua cho các bạn hiểu đã , chi tiết thì ở đây
public SaveImageHelper(Context context,AlertDialog alertDialog, ContentResolver contentResolver, String name, String desc) {
this.context = context;
this.alertDialogWeakReference = new WeakReference<AlertDialog>(alertDialog);
this.contentResolverWeakReference = new WeakReference<ContentResolver>(contentResolver);
this.name = name;
this.desc = desc;
}
Trong hàm khởi tạo trên , ngoài context,name,desc mình lấy từ tham số đầu vào của hàm khởi tạo thì cái ánh xạ alertDialog và contentResult được tạo mới từ AlertDialog và ContentResolver ở tham số Điều này có nghĩa là , ở MainActivity ta chỉ việc truyền vào cái alertDialog và cái contentResolver ta đã có
AlertDialog để làm gì ? Tại mình muốn trong khi tải hình về thì hiện 1 cái dialog cho người ta hiểu là nó vẫn đang tải ContentResolver để làm gì ? Ở phạm vi bài viết này thì bạn chỉ cần hiểu là nó là THAM SỐ CẦN để hàm insertImage của lớp MediaStore.Images.Media
Và trong hàm onBitmapLoaded ta có
ContentResolver r = contentResolverWeakReference.get(); // get ContentResolver từ ánh xạ
AlertDialog dialog = alertDialogWeakReference.get(); // get AlertDialog từ ánh xạ
if(r != null) // Sau khi get xong thì dùng nó như 1 biến
MediaStore.Images.Media.insertImage(r,bitmap,name,desc); // Truyền tham số vào
dialog.dismiss(); // Dismiss dialog sau khi save xong
//Gọi Action view image lên
Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
context.startActivity(Intent.createChooser(intent,"VIEW PICTURE"));
hehe thật dễ dàng với Picasso đúng ko Và ở MainActivity ta chỉ việc code
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 1000 ;
Button btnDownload;
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case PERMISSION_REQUEST_CODE:
{
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
requestPermissions(new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
},PERMISSION_REQUEST_CODE);
btnDownload = (Button)findViewById(R.id.btnDownload);
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
Toast.makeText(MainActivity.this, "You should grant permission", Toast.LENGTH_SHORT).show();
requestPermissions(new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
},PERMISSION_REQUEST_CODE);
return;
}
else
{
AlertDialog dialog = new SpotsDialog(MainActivity.this);
dialog.show();
dialog.setMessage("Downloading...");
String fileName = UUID.randomUUID().toString()+".jpg";
Picasso.with(getBaseContext())
.load("https://vignette.wikia.nocookie.net/dccu/images/1/17/Batman_Promo_shot.jpg/revision/latest?cb=20160612065804")
.into(new SaveImageHelper(getBaseContext(),
dialog,
getApplicationContext().getContentResolver(),
fileName,
"Image description"));
}
}
});
}
}
Chú ý vì hiện tại từ API level 23 (Marshallow) trở lên thì 1 số quyền cần người dùng đồng ý , và ta phải request runtime , trong trường hợp này là quyền WRITE_EXTERN_STORAGE , cho nên ngay khi chạy ứng dụng lên mình đã lo xin phép anh người dùng liền
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
requestPermissions(new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
},PERMISSION_REQUEST_CODE);
Mà xin phép xong thì tùy thái độ của anh ý mà xử lý nữa
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode)
{
case PERMISSION_REQUEST_CODE:
{
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
}
break;
}
}
Rồi , nếu anh ấy không đồng ý thì đành hỏi dai vậy , đừng trách tại sao hỏi dai tại vì cái này cần anh đồng ý mới chạy đc (đoạn if trong sự kiện onClick của button)
btnDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//Đành hỏi lại 1 lần nữa cho chắc là anh ko đồng ý thì em đành chịu
if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
{
Toast.makeText(MainActivity.this, "You should grant permission", Toast.LENGTH_SHORT).show();
requestPermissions(new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
},PERMISSION_REQUEST_CODE);
return;
}
else // Nếu anh đã đồng ý thì ahihi mãi yêu
{
AlertDialog dialog = new SpotsDialog(MainActivity.this);
dialog.show();
dialog.setMessage("Downloading...");
String fileName = UUID.randomUUID().toString()+".jpg";
Picasso.with(getBaseContext())
.load("https://vignette.wikia.nocookie.net/dccu/images/1/17/Batman_Promo_shot.jpg/revision/latest?cb=20160612065804")
.into(new SaveImageHelper(getBaseContext(),
dialog,
getApplicationContext().getContentResolver(),
fileName,
"Image description"));
}
}
});
Okay và kết quả
All rights reserved