Cách xin Permission trên Android Marshmallow 6.0 khi runtime

Ở bài 22 hướng dẫn về cấp quyền trong lập trình android căn bản mình đã nó qua cho các bạn các quyền cơ bản nhất rồi đúng không nào, và trong đó có một loại quyền là Permission nguy hiểm thì quyền này bắt buộc chúng ta phải xin cả ở AndroidManifest và cả trong lúc ứng dụng khởi chạy (runtime).

Trước tiên các bạn xem lại các quyền nào thuộc quyền nguy hiểm để các bạn xin cấp quyền khi run time nhé chứ những quyền bình thường thì không cần thiết phải xin đâu.

Permisson nguy hiểm trong android

Permission nguy hiểm trong android

 

Và những quyền này bạn chỉ cần xin cấp phát 1 lần duy nhất thôi và từ đó về sau ứng dụng sẽ không xin thêm một lần nào nữa , và hôm nay chúng ta sẽ làm một ví dụ như sau để demo nhé:

Tạo một ứng dụng tải ảnh trên mạng về và sau đó lưu vào trong thư mục thư viện hay tiếng anh là Gallery và đồng thời hiển thị ảnh đó lên ImageView luôn. Ở đây khi bạn lưu ảnh vào điện thoại thì bạn bắt buộc phải xin quyền ghi file đó là : android.permission.WRITE_EXTERNAL_STORAGE, mà quyền này nằm trong danh mục quyền nguy hiểm nên ta sẽ xin trong file AndroidManifest và cả khi ứng dụng đang run time.

Ngoài ra còn phải xin thêm quyền kết nối tới internet nữa nên bạn sẽ khai báo thêm quyền android.permission.INTERNET, đây là quyền bình thường không phải quyền nguy hiểm nhé mọi người.

Đây là ứng dụng sau khi mình hoàn thành:

Ứng dụng tải hình ảnh trên android về điện thoại

Ứng dụng tải hình ảnh trên android về điện thoại

Khi bật app nó sẽ hỏi quyền bạn như thế này , và sau đó thì bạn mới có quyền ghi file được:

Xin cấp quyền trên android 6.0 Marshmallow

Xin cấp quyền trên android 6.0 Marshmallow

Chúng ta bắt đầu bắt tay vào làm thôi nào!

Dưới đây là cấu hình config file AndroidManifest.xml nhé:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cheng.permissonandroidmarshmallow">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Tiếp đến là file layout bạn mình sẽ một EditText để điền link vào ,một Button để click vào sẽ download hình ảnh đó về lưu lại trong Gallery thiết bị đồng thời hiển thị lên ImageView trên màn hình luôn, code layout như sau:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_margin="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/edt_url"
        android:hint="@string/input_url_image"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:layout_gravity="center"
        android:id="@+id/btn_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/download_image" />
    <ImageView
        android:layout_gravity="center"
        android:src="@drawable/thangcoder_image"
        android:id="@+id/img_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

***  Ở trên mình dùng một hình ảnh trong Imageview là thangcoder_image, bạn có thể xoá nó đi chứ không lỗi nếu như bạn không tải project về mà tự code tay nhé.

Bạn nhớ thêm chuỗi string này vào file strings.xml trong thư mục value -> strings.xml nhé:

strings.xml

<resources>
    <string name="app_name">permissonandroidmarshmallow</string>
    <string name="input_url_image">Input Url Image</string>
    <string name="download_image">Download Image</string>
</resources>

Và đây là toàn bộ code mình xử lý trong MainActivity và cũng chính nội dung bài viết nằm ở đây:

MainActivity.java

package com.cheng.permissonandroidmarshmallow;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;

public class MainActivity extends AppCompatActivity {

    private EditText edtUrl;
    private Button btnDownload;
    private ImageView imgShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initPermission();
        edtUrl = (EditText) findViewById(R.id.edt_url);
        btnDownload = (Button) findViewById(R.id.btn_download);
        imgShow = (ImageView) findViewById(R.id.img_show);

        btnDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new ImageLoadTask(MainActivity.this,edtUrl.getText().toString(),imgShow).execute();
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults.length == 1 &&
                    grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "Permision Write File is Granted", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Permision Write File is Denied", Toast.LENGTH_SHORT).show();

            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    public void initPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

                //Permisson don't granted
                if (shouldShowRequestPermissionRationale(
                        Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    Toast.makeText(MainActivity.this, "Permission isn't granted ", Toast.LENGTH_SHORT).show();
                }
                // Permisson don't granted and dont show dialog again.
                else {
                    Toast.makeText(MainActivity.this, "Permisson don't granted and dont show dialog again ", Toast.LENGTH_SHORT).show();
                }
                //Register permission
                requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);

            }
        }
    }
    public static class ImageLoadTask extends AsyncTask<Void, Void, Bitmap> {
        private Context context;
        private String url;
        private ImageView imgShow;

        public ImageLoadTask(Context context, String url, ImageView imgShow) {
            this.url = url;
            this.context = context;
            this.imgShow=imgShow;
        }

        @Override
        protected Bitmap doInBackground(Void... params) {
            Bitmap myBitmap = null;

            try {
                URL urlConnection = new URL(url);
                HttpURLConnection connection = (HttpURLConnection) urlConnection
                        .openConnection();
                connection.setDoInput(true);
                connection.connect();
                InputStream input = connection.getInputStream();
                myBitmap = BitmapFactory.decodeStream(input);
                if (myBitmap != null && context != null) {
                    MediaStore.Images.Media.insertImage(context.getContentResolver(), myBitmap, "Anpanman", "Image");
                }
                return myBitmap;
            }catch (SecurityException e){
                e.printStackTrace();
                Log.d(getClass().getSimpleName(), "doInBackground: ");
            }
            catch (Exception e) {
                e.printStackTrace();

            }
            return myBitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (bitmap != null && this.context != null) {
                Toast.makeText(this.context, "Download finish", Toast.LENGTH_SHORT).show();
                imgShow.setImageBitmap(bitmap);
            }else if (this.context!= null){
                Toast.makeText(this.context,"Download fail", Toast.LENGTH_SHORT).show();
            }
        }

    }

}

Mình sẽ không giải thích hết code bên trong mà chỉ dừng lại ở đoạn xin cấp quyền và trong bài viết khác mình sẽ hướng dẫn chi tiết phần này sau nhé, đoạn code xin cấp quyền ghi file là đây:

public void initPermission(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {

                //Permisson don't granted
                if (shouldShowRequestPermissionRationale(
                        Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    Toast.makeText(MainActivity.this, "Permission isn't granted ", Toast.LENGTH_SHORT).show();
                }
                // Permisson don't granted and dont show dialog again.
                else {
                    Toast.makeText(MainActivity.this, "Permisson don't granted and dont show dialog again ", Toast.LENGTH_SHORT).show();
                }
                //Register permission
                requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);

            }
        }
    }

Nếu như bạn xoá đoạn code này thì vẫn chạy được trên android 5.0 trở xuống nhé, chỉ 6.0 mới lỗi thôi.

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M

Đoạn trên là để kiểm tra xem phiên bản android thiết bị hiện có lớn hơn hoặc bằng android 6.0, M ở đây có nghĩa là Marshmallow, nếu đúng thì sẽ tiến hành kiểm tra xem trong file AndroidManifest.xml có khai báo quyền READ_EXTERNAL_STORAGE, nếu có thì tiến hành yêu cầu cung cấp quyền này.

Đây là hàm check:

if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)

Còn đây là hàm yêu cầu cung cấp quyền :

requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);

Ở đoạn lênh này có thể có hay không cũng chẳng sao cả nhé, nhưng nên thêm vào để xử lý chứ có thể xảy ra lỗi:

if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
                    Toast.makeText(MainActivity.this, "Permission isn't granted ", Toast.LENGTH_SHORT).show();
                }
                // Permisson don't granted and dont show dialog again.
                else {
                    Toast.makeText(MainActivity.this, "Permisson don't granted and dont show dialog again ", Toast.LENGTH_SHORT).show();
                }

Đoạn code trên kiểm tra xem thiết bị này đã từng từ chối quyền ghi file trên ứng dụng này chưa, có nghĩa là ứng dụng này từng xin cấp quyền đọc ghi file nhưng người dùng đã từ chối thì nó sẽ trả về true khi gọi hàm :

shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)

Và sẽ trả về false nếu như người dùng từng từ chối quyền và tick chọn không hiển thị lại lần sau, bạn xem ảnh dưới đây sẽ hiểu

Từ chối quyền và không cho hiện thông báo lần sau

Từ chối quyền và không cho hiện thông báo lần sau

Nếu người dùng chọn Never ask again rồi Denny, thì lần sau ứng dụng sẽ không được xin quyền nữa đâu nhé, còn nếu người dùng Allow thì ứng dụng được cấp quyền và lần sau sẽ không hỏi nữa.

Ở phía trên còn một đoạn code như sau:

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "Permision Write File is Granted", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Permision Write File is Denied", Toast.LENGTH_SHORT).show();

            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

Đây là phương thức lắng nghe xem khi hiển thông dialog thông báo xin quyền thì người dùng click vào đâu, nếu như người dùng chọn Allow thì nó sẽ vào hàm này:

if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(MainActivity.this, "Permision Write File is Granted", Toast.LENGTH_SHORT).show();
            }

Ngược lại nếu chọn Denny thì nó sẽ vào hàm else còn lại:

else {
            Toast.makeText(MainActivity.this, "Permision Write File is Denied", Toast.LENGTH_SHORT).show();
        }

Thực ra thằng này không cần thiết nên bạn có thể không cần @Override nó làm gì cả.Và đây là source code của ứng dụng bạn có thể tải nó xuống ở link github phía dưới và nếu chỗ nào không hiểu thì xem qua video nhé.

Mời bạn tải về project ở link bên dưới!

[sociallocker id=635]

[/sociallocker]

Chúc các bạn thành công!

8 Comments

  1. beut April 27, 2017
  2. red_devils May 30, 2017
  3. mr.Hung June 18, 2017
  4. Francis July 26, 2017
  5. test October 27, 2017
  6. rainbow October 30, 2017
    • trangchongcheng October 30, 2017
  7. Bình Vi June 13, 2019

Add Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.