Android

Android 6.0 ve Yenilenen İzin İsteme Mekanızması

Bildiğiniz gibi uygulama içinde kullanmak istediğimiz özellikler için AndroidManifest.xml dosyasına gerekli izinleri tanımlamamız yetiyordu. Kullanıcı Google Play'den uygulamayı indiriken, istediğimiz izinler karşısında listeleniyor ve uygulamayı indirmek için bu izinleri kabul etmek zorunda kal&#...

Burhan Aras |

04.08.2016

 

Bildiğiniz gibi uygulama içinde kullanmak istediğimiz özellikler için AndroidManifest.xml dosyasına gerekli izinleri tanımlamamız yetiyordu. Kullanıcı Google Play'den uygulamayı indiriken, istediğimiz izinler karşısında listeleniyor ve uygulamayı  indirmek için  bu izinleri kabul etmek zorunda kalıyor. Sonrasında ise uygulama bu özellikleri tekrar izine ihtiyaç duymadan defalarca kez kullanabiliyordu. Fakat Android 6.0 ile bu izin yapısı biraz değişikliğe uğradı. Şöyle ki, Google kullanılan izinleri NORMAL İZİNLER ve TEHLİKLELİ İZİNLER olarak ikiye ayırdı. (Aslında iki değil dört kategori var fakat ilk ikisi yeterli bizim için.) Tehlikeli olarak tanımlanan türden izinler için AndroidManifest.xml'e tanımlamak yeterli olmuyor artık, uygulama çalışırken izin gereken bir iş yapmak istediğinde anında kullanıcıya sorup, her defasında izin almamız gerekiyor.

Özetle,  tüm izinleri yine AndroidManifest.xml'e tanımlıyoruz. Beraberinde tehlikeli izin gerektiren işler içinse uygulama çalışırken tekrar izin istemek zorundayız. IPhone kullanan kullanıcılar bu yapıya aşinadır aslında. Çünkü IOS bu izin yapısını yıllardır başarıyla kullanıyor kendi uygulamaları içinde.

Bahsettiğimiz izin kategorilerine giren izinleri aşağıdaki gibi  listeleyebiliriz.

Normal İzinler:  https://developer.android.com/guide/topics/security/normal-permissions.html

Tehlikeli İzinler: https://developer.android.com/guide/topics/security/permissions.html?normal-dangerous

Örneğin internete bağlanmak normal bir izin gerektiriken, rehbere ulaşmak, SD karta dosya yazmak ya da bir numarayı aramak tehlikeli izin kategorisine giriyor.

İZİNLER İÇİN YENİ BİR DESIGN PATTERN

Gel gelelim bu izinleri nasıl isteyeceğimize. İzin istemek ve kullanıcının izin verip vermediğini kontrol etmek için yaklaşık 30 satırlık standart bir kod yazmamız gerekli. Uygulama içinde ortalama on yerde izin talebinde bulunduğumuzu düşünürsek 300 satırlık gereksiz ve kendini tekrar eden bir kod yığını oluşur. Üstelik izin istediğimiz iş ve izin isteme işlerini aynı metodda toplarsak bu Single Responsibility Prensibine de aykırı olacaktır.

Peki Çözüm Nedir?

Malum yazılım dünyasında patternlar, prensipler derken her işi yapmanın adam akıllı yolları var. Bunun için de tavsiye edilen yöntem Runtime Permissions Activity Pattern'dir.  Uzun lafın kısası, abstract bir Actvitiy sınıfı yazıyoruz. Bu abstract sınıf içinde izin istemek ve izinin sonucunu kontrol etmek için gerekli metodlar bulunuyor.  Yazdığımız tüm Activity sınıflarında bu abstract sınıfı extend ediyoruz ve gönül rahatlığı ile kullanabiliyoruz.

 

RuntimePermissionsActivity.java

 

public abstract class RuntimePermissionsActivity extends AppCompatActivity {

    private SparseIntArray mErrorString;

    @Override

    protected void onCreate(@Nullable Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        mErrorString = new SparseIntArray();

    }

    @Override

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        int permissionCheck = PackageManager.PERMISSION_GRANTED;

        for (int permission : grantResults) {

            permissionCheck = permissionCheck + permission;

        }

        if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) {

            onPermissionsGranted(requestCode);

        } else {

            Snackbar.make(findViewById(android.R.id.content), mErrorString.get(requestCode),

                    Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",

                    new View.OnClickListener() {

                        @Override

                        public void onClick(View v) {

                            Intent intent = new Intent();

                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);

                            intent.addCategory(Intent.CATEGORY_DEFAULT);

                            intent.setData(Uri.parse("package:" + getPackageName()));

                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                            intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

                            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);

                            startActivity(intent);

                        }

                    }).show();

        }

    }

    public void requestAppPermissions(final String[] requestedPermissions,

                                      final int stringId, final int requestCode) {

        mErrorString.put(requestCode, stringId);

        int permissionCheck = PackageManager.PERMISSION_GRANTED;

        boolean shouldShowRequestPermissionRationale = false;

        for (String permission : requestedPermissions) {

            permissionCheck = permissionCheck + ContextCompat.checkSelfPermission(this, permission);

            shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || ActivityCompat.shouldShowRequestPermissionRationale(this, permission);

        }

        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {

            if (shouldShowRequestPermissionRationale) {

                Snackbar.make(findViewById(android.R.id.content), stringId,

                        Snackbar.LENGTH_INDEFINITE).setAction("GRANT",

                        new View.OnClickListener() {

                            @Override

                            public void onClick(View v) {

                                ActivityCompat.requestPermissions(RuntimePermissionsActivity.this, requestedPermissions, requestCode);

                            }

                        }).show();

            } else {

                /*User has chosen "Never ask again."  So we shouldn't request again.. DO NOTHING*/

            }

        } else {

            onPermissionsGranted(requestCode);

        }

    }

    public abstract void onPermissionsGranted(int requestCode);

}

RuntimePermissionsActivity.java sınıfımız içinde üç tane metod bulunuyor. İlki requestAppPermissions()  isimli metod izin istemek için çağrılması gereken metoddur. Dikkat ederseniz ilk parametresi String değil String dizisidir.(String[]) Yani aynı anda birden fazla izin istemek için kullanılabilir bu metod.

İkinci metod onRequestPermissionsResult()    bizim yazmadığımız, FragmentActivity.java sınıfından miras alıp override ettiğimiz metoddur. Bu metod kullanıcının bize izin verip vermediğini anlayacağımız bir callback yani geri bildirim metodudur.   Eğer kullanıcı bize izin vermişse onPermissionsGranted()    isimli abstract metodu çağırarak, izin isteğinin yapıldığı asıl Activity sınıfına haber veriyoruz. bu metod bu yüzden abstract olarak tanımlandı. Eğer kullanıcı izin vermemişse de elimizden gelen birşey yok. bu özelliği kullanamayacağız demektir.

Sıklıkla yapılan bir hataya dikkat çekmek isterim.  Herhangi bir izin kullanıcıdan red almışsa, bu izin  ikinci kez istendiğinde Android işletim Sistemi, açılan pencereye "Bir daha sorma" şeklinde bir checkbox çıkarır. Kullanıcı bu kısmı işaretlerse, bu izin tekrar istenmemelidir. Bu durum kullanıcıyı rahatsız eder.  Fakat yazılım geliştiriciler genelde bu kontrolü görmezden gelerek bu ayar işaretli olsa da olmasa da izin isteme yoluna giderler. Bu durum kullanıcıyı rahatsız eder.

Kullanıcının bu izin için bir daha rahatsız edilmek istemediğini  requestAppPermissions()   metodu içerisindeki  shouldShowRequestPermissionRationale  değişkeninden anlayabiliyoruz. Eğer false  gelirse kullanıcıyı rahatsız etmiyoruz.

 

Son olarak, bu metodları nasıl çağıracağımıza bakıyoruz. Yazacağımız her Activity sınıfında RuntimePermissionAbstractActivity.java sınıfını extend etmemiz gerekiyor.

 MainActivity.java

public class MainActivity extends RuntimePermissionsActivity {

    private static final int REQUEST_PERMISSION_READ_CONTACTS = 1;

    private static final int REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE = 2;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);



               

MainActivity.super.requestAppPermissions(new

                        String[]{Manifest.permission.READ_CONTACTS,}, R.string

                        .runtime_permissions_txt, REQUEST_PERMISSION_READ_CONTACTS);

               

MainActivity.super.requestAppPermissions(new

                        String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, R.string.runtime_permissions_txt, REQUEST_PERMISSION_READ_CONTACTS);

    }

    @Override

    public void onPermissionsGranted(final int requestCode) {

        if (requestCode == REQUEST_PERMISSION_READ_CONTACTS) {

            Toast.makeText(this, "Permissions Received for reading contacts.", Toast.LENGTH_LONG).show();

        } else if (requestCode == REQUEST_PERMISSION_WRITE_EXTERNAL_STORAGE) {

            Toast.makeText(this, "Permissions Received for writing SD card.", Toast.LENGTH_LONG).show();

        }

    }

}

 

Override ettiğimiz  onPermissionsGranted()   metodu,  kabul edilen her izin için tetiklenecek kabul edilmeyenler içinse gereksiz gürültü yapmayacaktır.

Projenin kodlarını Github hesabımızda indirebilir, bedava uygulamanın keyfine varabilirsiniz.

Github : https://github.com/burhanaras/Android-Runtime-Permissions

Teşekkürler.

Burhan ARAS

 

Burhan Aras |

04.08.2016

Yorumlar

murat
13.04.2017 - 11:59

Güzel anlatımınız için teşekkürler.

Emre Aklan
07.01.2017 - 08:43

Kesinlikle çok faydalı olmuş. Teşekkürler.

hakan
07.02.2017 - 02:13

teşekkürler çok faydalı ve güzel anlatım

Berkhan
20.08.2016 - 09:19

Teşekkürler.

Sezgin Karagülle
21.08.2016 - 11:28

Çok teşekkürler