+4

Tải ảnh về bộ nhớ trong của Android với thư viện Picasso

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí