Pertanyaan Dapatkah Anda meminta izin secara serempak di model izin waktu proses Android Marshmallow (API 23)?


Katakanlah Anda memiliki metode seperti ini:

public boolean saveFile (Url url, String content) {

   // save the file, this can be done a lot of different ways, but
   // basically the point is...

   return save_was_successful;
}

Di seluruh aplikasi Anda, jika Anda ingin menyimpan file ke penyimpanan eksternal, Anda melakukan sesuatu seperti ...

if (saveFile(external_storage_url, "this is a test")) {
   // yay, success!
} else { // notify the user something was wrong or handle the error }

Ini adalah contoh yang disederhanakan, jadi jangan membahas kasus saya tentang memblokir UI, menangani pengecualian dengan benar, dll. Jika Anda tidak menyukai penyimpanan file, Anda dapat membayangkan getContact() atau getPhoneState() atau terserah. Intinya adalah itu adalah operasi yang memerlukan izin yang mengembalikan beberapa nilai dan digunakan di seluruh aplikasi.

Di Android <= Lollipop, jika pengguna telah menginstal dan menyetujui pemberian android.permission.WRITE_EXTERNAL_STORAGE atau apa pun, semuanya akan baik-baik saja.

Tetapi di Marshmallow baru (API 23) model izin runtime, sebelum Anda dapat menyimpan file ke penyimpanan eksternal, Anda seharusnya (1) memeriksa apakah izin telah diberikan. Jika tidak, mungkin (2) menunjukkan alasan untuk permintaan tersebut (jika sistem berpikir itu ide yang bagus) dengan bersulang atau apa pun dan (3) meminta pengguna untuk memberikan izin melalui dialog, kemudian pada dasarnya duduk dan menunggu panggilan balik ...

(jadi aplikasi Anda duduk, menunggu ...)

(4) Saat pengguna akhirnya menanggapi dialog, onRequestPermissionsResult () metode diaktifkan, dan kode Anda sekarang (5) harus menyaring melalui WHICH izin meminta mereka sebenarnya menanggapi, apakah pengguna mengatakan ya atau tidak (setahu saya tidak ada cara untuk menangani "tidak" vs. "tidak dan jangan tanya lagi"), (6) mencari tahu apa yang ingin mereka capai di tempat pertama yang diminta seluruh proses ask-for-permissions, sehingga program bisa akhirnya  (7) lanjutkan dan lakukan hal itu.

Untuk mengetahui apa yang pengguna coba lakukan dalam langkah (6) melibatkan setelah sebelumnya lulus khusus kode ("Respon permintaan perizinan") yang dijelaskan dalam dokumen sebagai pengidentifikasi jenis permintaan izin (kamera / kontak / dll.) tetapi menurut saya lebih seperti "khusus, apa yang Anda coba lakukan ketika Anda menyadari Anda harus meminta izin" kode, mengingat bahwa izin / grup yang sama dapat digunakan untuk beberapa tujuan dalam kode Anda, jadi Anda harus menggunakan kode ini untuk mengembalikan eksekusi ke tempat yang tepat setelah mendapatkan izin.

Saya bisa benar-benar salah paham bagaimana ini seharusnya bekerja-- jadi tolong beritahu saya jika saya jalan keluar-- tetapi yang lebih besar adalah saya sangat tidak yakin bagaimana cara berpikir tentang melakukan semua hal di atas saveFile() metode yang dijelaskan sebelumnya karena bagian "menunggu untuk menanggapi" asynchronous. Ide-ide yang saya anggap cukup sombong dan tentu saja salah.

Hari ini Podcast Pengembang Android mengisyaratkan bahwa mungkin ada solusi sinkron di sekitar tikungan, dan bahkan ada pembicaraan tentang alat masukan alt-enter jenis "add a request request" ajaib di satu langkah di Android Studio. Namun, bagaimana proses izin runtime mungkin didorong menjadi saveFile()atau apa pun-- Saya memikirkan sesuatu di sepanjang baris:

public boolean saveFile(Url url, String content) {
   //   this next line will check for the permission, ask the user
   //   for permission if required, maybe even handle the rationale
   //   situation
   if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        R.string.permission_storage_rationale))
       {
           return false; // or throw an exception or whatever
       } else {

   // try to save the file

   return save_was_successful;

   }
}

Demikian hal di atas checkPermission() akan gagal jika pengguna tidak memiliki dan juga menolak untuk memberikan izin. Mungkin orang bisa menggunakan lingkaran di sekitarnya checkPermission() untuk mencoba menanyakan hingga 3 kali atau sesuatu, atau lebih baik jika kebijakan tidak masuk akal yang menjengkelkan ditangani oleh metode.

Apakah hal semacam itu mungkin? Diinginkan? Apakah solusi semacam itu memblokir utas UI? Dari podcast itu terdengar seperti Google mungkin memiliki solusi seperti ini datang 'di tikungan, tapi saya ingin mendapatkan pemikiran apakah ada sesuatu - kelas kenyamanan, pola, sesuatu-- yang tidak melibatkan semua orang harus refactor semua operasi yang memerlukan izin, yang Saya harus berasumsi bisa sangat berantakan.

Maaf untuk pertanyaan bertele-tele, tapi saya ingin selengkap mungkin. Saya akan mengambil jawaban saya dari udara. Terima kasih!


Memperbarui: Ini adalah transkrip dari podcast yang disebutkan di atas.

Dengarkan sekitar 41:20. Dalam diskusi ini:

Transkrip kasar:

Tor Norbye (tim Tools): "Jadi sepertinya tidak perlu banyak kerja untuk pengembang. Tapi saya mengerti sebagian masalahnya adalah ini bukan panggilan sinkron, kan ?. Jadi apa yang Anda harus - benar-benar mengubah cara aktivitas Anda ditulis untuk memiliki callback-- jadi itu benar-benar seperti mesin negara di mana ... untuk keadaan ini, Anda-"

Poiesz (manajer produk): "Ah- saya pikir- ada a- mungkin ada pilihan untuk respon sinkron--"

Norbye: "Oh itu akan membuat segalanya--"

Poiesz: "Saya dapat berbicara dengan orang-orang secara internal. Saya ingat diskusi tentang sinkron - tetapi kita bisa mengetahuinya."

Norbye: "Ya. Bahkan kita mungkin harus membuatnya menjadi alat. Di mana Anda memiliki refactoring mudah ..."

Kemudian dia berbicara tentang menggunakan anotasi dalam alat untuk menentukan API mana yang memerlukan izin .. (yang seperti sekarang tidak bekerja dengan IMO yang hebat) dan bagaimana dia menginginkan alat suatu hari nanti untuk benar-benar menghasilkan kode yang diperlukan jika menemukan "berbahaya" yang tidak terkendali "pemanggilan metode:

Norbye: "... maka jika Anda berada di M juga akan berkata, 'hei, apakah Anda benar-benar memeriksa izin ini atau apakah Anda menangkap pengecualian keamanan?', dan jika tidak, kami akan mengatakan 'Anda mungkin perlu melakukan sesuatu untuk meminta izin di sini. ' Apa yang saya ingin adalah untuk memperbaiki cepat di mana Anda dapat pergi 'CHING!' dan menyisipkan semua hal yang tepat untuk ditanyakan, tetapi cara semuanya kembali ketika saya melihat, ini membutuhkan restrukturisasi banyak hal - menambahkan antarmuka dan callback, dan mengubah alur, dan itu tidak bisa kami lakukan. Tetapi jika ada mode sinkron yang mudah sebagai benda sementara atau benda permanen, itu akan sangat bagus."


37
2017-08-21 07:05


asal


Jawaban:


Seperti Marshmallow, pengertian saya adalah bahwa Anda tidak bisa.

Saya harus menyelesaikan masalah yang sama di aplikasi saya. Begini cara saya melakukannya:

  1. Refactoring: Pindahkan setiap bongkahan kode yang bergantung pada izin dari beberapa jenis ke dalam metode sendiri.
  2. Lebih banyak refactoring: Identifikasi pemicu untuk setiap metode (seperti memulai suatu aktivitas, mengetuk kontrol, dll.) Dan metode grup bersama-sama jika mereka memiliki pemicu yang sama. (Jika dua metode berakhir dengan pemicu yang sama DAN membutuhkan kumpulan izin yang sama, pertimbangkan untuk menggabungkannya.)
  3. Bahkan lebih banyak refactoring: Cari tahu apa yang tergantung pada metode baru yang telah dipanggil sebelumnya, dan pastikan itu fleksibel tentang kapan metode ini disebut: Entah pindah ke metode itu sendiri, atau setidaknya pastikan bahwa apa pun yang Anda lakukan tidak membuang pengecualian jika metode Anda belum pernah dipanggil sebelumnya, dan itu mulai berperilaku seperti yang diharapkan segera setelah metode dipanggil.
  4. Meminta kode: Untuk masing-masing metode ini (atau kelompoknya), tentukan konstanta integer yang akan digunakan sebagai kode permintaan.
  5. Memeriksa dan meminta izin: Bungkus masing-masing metode / kelompok metode ini ke dalam kode berikut:

.

if (ContextCompat.checkSelfPermission(this, Manifest.permission.SOME_PERMISSION) == PackageManager.PERMISSION_GRANTED)
    doStuffThatRequiresPermission();
else
    ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SOME_PERMISSION}, Const.PERM_REQUEST_DO_STUFF_THAT_REQUIRES_PERMISSION);
  1. Tanggapan penanganan: Tulis implementasi untuk onRequestPermissionsResult() di setiap Activity yang meminta izin. Periksa apakah izin yang diminta telah diberikan, dan gunakan kode permintaan untuk menentukan metode apa yang perlu dipanggil.

Ketahuilah bahwa API membutuhkan Activity untuk permintaan izin runtime. Jika Anda memiliki komponen non-interaktif (seperti a Service), melihat Cara meminta izin dari layanan di Android Marshmallow untuk saran tentang cara mengatasi ini. Pada dasarnya, cara termudah adalah dengan menampilkan pemberitahuan yang kemudian akan memunculkan Activity yang tidak melakukan apa pun kecuali menampilkan dialog izin waktu proses. Sini adalah cara saya menangani ini di aplikasi saya.


9
2017-07-09 14:38



Jawaban singkat: tidak, tidak ada operasi sinkronisasi hari ini. Anda harus memeriksa apakah Anda memiliki izin yang tepat sebelum menyelesaikan operasi atau sebagai pilihan terakhir, Anda bisa meletakkan blok coba / tangkap untuk pengecualian keamanan. Di blok tangkap Anda dapat memberi tahu pengguna bahwa operasi gagal karena masalah izin. Selain itu, ada titik lain: ketika izin dicabut, aplikasi tidak memulai ulang dari aktivitas utama, jadi Anda harus memeriksa izin bahkan di onResume Anda ().


2
2017-08-21 15:55



Jadi saya benci menjawab pertanyaan saya sendiri secara khusus sehubungan dengan android.permission.WRITE_EXTERNAL_STORAGE izin digunakan dalam contoh saya, tetapi apa sih.

Untuk membaca dan / atau menulis file, sebenarnya ada cara untuk benar-benar menghindari harus meminta dan kemudian memeriksa izin dan dengan demikian memotong seluruh aliran yang saya jelaskan di atas. Dengan cara ini, saveFile (Url url, String content) metode yang saya berikan sebagai contoh dapat terus bekerja serentak.

Solusinya, yang saya percaya bekerja di API 19+, menghilangkan kebutuhan akan WRITE_EXTERNAL_STORAGE izin dengan memiliki DocumentsProvider bertindak sebagai "perantara" pada dasarnya meminta pengguna atas nama aplikasi Anda "pilih file tertentu untuk ditulis ke" (yaitu, "pemilih file") dan kemudian setelah pengguna memilih file (atau ketik nama file baru ), aplikasi ini sekarang secara ajaib diberikan izin untuk melakukannya untuk Uri itu, karena pengguna telah secara khusus memberikannya.

Tidak ada "resmi" WRITE_EXTERNAL_STORAGE izin diperlukan.

Cara ini jenis izin pinjaman adalah bagian dari Kerangka Akses Penyimpanan, dan diskusi tentang ini dibuat di Big Android BBQ oleh Ian Lake. Ini video bernama Lupakan Izin Penyimpanan: Alternatif untuk berbagi dan berkolaborasi yang membahas dasar-dasar dan bagaimana Anda secara khusus menggunakannya untuk memintas WRITE_EXTERNAL_STORAGE persyaratan izin sepenuhnya.

Ini tidak sepenuhnya memecahkan masalah perizinan sinkronisasi / async untuk semua kasus, tetapi untuk semua jenis dokumen eksternal, atau bahkan yang ditawarkan oleh penyedia (seperti gDrive, Box.net, Dropbox, dll.) Ini mungkin solusi yang layak untuk dicoba.


1
2018-04-01 05:01



Inilah bagaimana saya memecahkan masalah "synchronicity" tanpa harus secara eksplisit memblokir (dan sibuk-menunggu), atau tanpa memerlukan "boot-loader" Aktivitas terpisah. saya Tampilan splashored splash Activity sebagai berikut:

memperbarui: Contoh yang lebih lengkap dapat ditemukan sini.


catatan: Karena requestPermissions() Panggilan API startActivityForResult()

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

logika penciptaan tampilan utama dipindahkan OnCreate() untuk OnCreate2(), dan OnCreate() sekarang menangani pemeriksaan izin. Jika RequestPermissions() perlu dipanggil, maka yang terkait OnRequestPermissionsResult() memulai ulang aktivitas ini (meneruskan salinan bundel asli).


[Activity(Label = "MarshmellowActivated",
    MainLauncher = true,      
    Theme = "@style/Theme.Transparent",
    Icon = "@drawable/icon"        
    ////////////////////////////////////////////////////////////////
    // THIS PREVENTS OnRequestPermissionsResult() from being called
    //NoHistory = true
)]
public class MarshmellowActivated : Activity
{
    private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 112;
    private Bundle _savedInstanceState;

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
    {
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode)
        {
            case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:
                if (grantResults.Length > 0 && grantResults[0] == Permission.Granted)
                {       
                    Intent restartThisActivityIntent = new Intent(this, this.GetType());
                    if (_savedInstanceState != null)
                    {
                        // *ref1: Forward bundle from one intent to another
                        restartThisActivityIntent.PutExtras(_savedInstanceState);
                    }
                    StartActivity(restartThisActivityIntent);
                }
                else
                {                   
                    throw new Exception("SD Card Write Access: Denied.");
                }
                break;
        }
    }

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);

        ///////////////////////////////////////////////////////////////////////////////////////////////////////////
        // Android v6 requires explicit permission granting from user at runtime for extra sweet security goodness
        Permission extStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);

        //if(extStoragePerm == Permission.Denied)
        if (extStoragePerm != Permission.Granted)
        {
            _savedInstanceState = savedInstanceState;
            // **calls startActivityForResult()**
            RequestPermissions(new[] { Android.Manifest.Permission.WriteExternalStorage }, ANDROID_PERMISSION_REQUEST_CODE__SDCARD);
        }
        else
        {
            OnCreate2(savedInstanceState);
        }
    }

    private void OnCreate2(Bundle savedInstanceState)
    {
        //...
    }
}

ref1:  Meneruskan bundel dari satu niat ke yang lain

catatan: Ini dapat direfaktor untuk menangani izin lebih umumnya. Saat ini menangani izin menulis sdcard saja, yang harus menyampaikan logika yang bersangkutan dengan kejelasan yang cukup.


1
2018-06-05 16:34



Anda dapat menambahkan metode pemblokir pemblokiran seperti ini:

@TargetApi(23) 
public static void checkForPermissionsMAndAboveBlocking(Activity act) {
    Log.i(Prefs.TAG, "checkForPermissions() called");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (act.checkSelfPermission(
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {


            // No explanation needed, we can request the permission.
            act.requestPermissions(
                    new String[]{
                          Manifest.permission.WRITE_EXTERNAL_STORAGE
                    },
                    0);

            while (true) {
                if (act.checkSelfPermission(
                        Manifest.permission.WRITE_EXTERNAL_STORAGE)
                        == PackageManager.PERMISSION_GRANTED) {

                    Log.i(Prefs.TAG, "Got permissions, exiting block loop");
                    break;
                }
                Log.i(Prefs.TAG, "Sleeping, waiting for permissions");
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            }

        }
        // permission already granted
        else {
            Log.i(Prefs.TAG, "permission already granted");
        }
    }
    else {
        Log.i(Prefs.TAG, "Below M, permissions not via code");
    }

}

0
2018-06-24 08:07