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 :D
    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 :D 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á :D

    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ả

    Nguồn: Viblo



Có vẻ như bạn đã mất kết nối tới LaptrinhX, vui lòng đợi một lúc để chúng tôi thử kết nối lại.