پیاده سازی قابلیت Runtime Permission
در اندروید ۶ (Marshmallow) قابلیت امنیتی جدیدی با نام Runtime Permission به سیستم عامل اندروید اضافه شد. با معرفی این قابلیت، از اندروید ۶ و به بالا کاربر بجای مشاهده و تایید دسته جمعی مجوزهای موردنیاز برنامه در هنگام نصب، پس از نصب اپلیکیشن تعیین میکند برنامه مجوز دسترسی به کدامیک از امکانات را داشته باشد. در این جلسه به نحوه پیاده سازی قابلیت Runtime Permisson در اندروید ۶ و به بالا میپردازیم.
Runtime Permission چیست؟
به نام خدا. اگر تجربه کار با یک موبایل یا تبلت اندرویدی قدیمی یعنی قبل از اندروید مارشمالو را دارید، حتما لیست مجوزهای مورد نیاز اپلیکیشنها هنگام نصب را بخاطر دارید. مجوزهایی مانند دسترسی به مخاطبین، دوربین، ارسال و دریافت پیامک، بلوتوث، وای فای و… .
قبل از آغاز عملیات نصب برنامه روی دیوایس اندرویدی، لیستی از تمامی دسترسیهایی که توسط آن از سیستم عامل اخذ خواهد شد برای کاربر نمایش داده میشد. در تصویر بالا کاربر قبل از آغاز نصب برنامه باید دسترسی به مخاطبین، موقعیت مکانی و میکروفون را تایید کند. در اینجا دو مشکل اساسی وجود داشت که میبایست چارهای برای آن اندیشیده میشد:
۱: کاربر دو انتخاب داشت. باید همه دسترسیها را میپذیرفت تا بتواند برنامه مدنظر خود را نصب کند در غیر اینصورت اگر حتی یک مورد از مجوزها باب میلش نبود میبایست از نصب و استفاده از آن برنامه به طور کامل صرف نظر میکرد.
۲: به دلیل طولانی بودن لیست مجوزها و همچنین دریافت یکباره آنها، اکثر اشخاص بخصوص کاربرانی که اطلاعات کمتری در مورد ماهیت مجوزها و ارتباط آن با حریم شخصی خود داشتند، توجه زیادی به نوع مجوزهای دریافتی نداشته و بدون مطالعه دقیق لیست، اقدام به تایید و نصب نرم افزارها روی دیوایس میکردند که نتیجه آن چیزی جز به خطر افتادن حریم شخصی افراد نبود.
سرانجام گوگل همزمان با معرفی اندروید Marshmallow در اکتبر ۲۰۱۵ ویژگی جدیدی برای رفع این مشکل به سیستم عامل اندروید اضافه کرد. این قابلیت Runtime Permission نام داشت؛ یعنی “اخذ مجوز هنگام اجرا“.
بنابراین حالا دیگر خبری از لیست مجوزها در هنگام نصب برنامه نیست و کاربر ابتدا برنامههای مدنظر خود را بدون تایید هیچگونه مجوز حساسی نصب میکند. بعد از اتمام نصب بنا به سلیقه و صلاح دید توسعه دهنده برنامه، مجوزها میتوانند هنگام اولین اجرا و یا صرفا هنگامی که لازم هست از کاربر اخذ گردد.
در روش گذشته مدیریت مجوزها برای توسعه دهنده و برنامه نویس اپلیکیشنهای اندرویدی سادهتر بود و تنها کاری که باید انجام میشد، تعریف مجوزها در مانیفست بود. اما در عوض کاربر کنترلی بر روی مجوزها نداشت. مسلم است که در اینجا باید رضایت کاربر در اولویت قرار میگرفت.
یک اپلیکیشن شبکه اجتماعی مانند Twitter یا Instagram را درنظر بگیرید. هنگام نصب هیچ مجوزی از شما دریافت نمیشود اما به محض اینکه بخواهید برای اولین بار از گالری تصویر یک عکس را انتخاب و ارسال کنید، قبل از آنکه برنامه بتواند به گالری عکس دسترسی داشته باشد درخواستی برای تایید یا رد مجوز دسترسی برنامه به محتوای گالری نمایش داده میشود. یا در اولین اتصال برنامه به دوربین، درخواست تایید یا رد دسترسی به دوربین صادر خواهد شد.
اینکه درخواست مجوز چه هنگامی به کاربر نمایش داده شود به انتخاب و استراتژی توسعه دهنده برمیگردد. یعنی این عملیات به طور خودکار انجام نمیشود و لازم است برنامه نویس آن را کنترل کند در غیر اینصورت برنامه هنگام نیاز به دسترسی به یک مجوز خاص، دچار مشکل شده و کرش (Crash) میکند.
انواع دسترسیها در سیستم عامل اندروید
در اندروید مجوزهای دسترسی به قابلیتهای سخت افزاری و نرم افزاری به دو دستهی مجوزهای نرمال (Normal) و مجوزهای خطرناک یا حساس (Dangerous) تقسیم بندی میشود که در ادامه توضیحات لازم را ارائه میدهم.
مجوزهای نرمال:
به آن دسته از مجوزهایی گفته میشود که به صورت خودکار و هنگام نصب از طرف سیستم عامل به برنامه داده شده و نیازی به دریافت موافقت و تایید آن از کاربر نیست. مانند مجوز دسترسی به اینترنت و بلوتوث.
لیست زیر شامل مجوزهای نرمال است:
WRITE_SYNC_SETTINGS ACCESS_LOCATION_EXTRA_COMMANDS ACCESS_NETWORK_STATE ACCESS_NOTIFICATION_POLICY ACCESS_WIFI_STATE BLUETOOTH BLUETOOTH_ADMIN BROADCAST_STICKY CALL_COMPANION_APP CHANGE_NETWORK_STATE CHANGE_WIFI_MULTICAST_STATE CHANGE_WIFI_STATE DISABLE_KEYGUARD EXPAND_STATUS_BAR FOREGROUND_SERVICE GET_PACKAGE_SIZE INSTALL_SHORTCUT INTERNET KILL_BACKGROUND_PROCESSES MANAGE_OWN_CALLS MODIFY_AUDIO_SETTINGS NFC NFC_TRANSACTION_EVENT READ_SYNC_SETTINGS READ_SYNC_STATS RECEIVE_BOOT_COMPLETED REORDER_TASKS REQUEST_COMPANION_RUN_IN_BACKGROUND REQUEST_COMPANION_USE_DATA_IN_BACKGROUND REQUEST_DELETE_PACKAGES REQUEST_IGNORE_BATTERY_OPTIMIZATIONS REQUEST_PASSWORD_COMPLEXITY SET_ALARM SET_WALLPAPER SET_WALLPAPER_HINTS TRANSMIT_IR USE_BIOMETRIC USE_FINGERPRINT USE_FULL_SCREEN_INTENT WAKE_LOCK WRITE_SYNC_SETTINGS
مجوزهای خطرناک یا حساس:
مجوزهایی هستند که از لحاظ امنیتی در سطح بالاتری قرار داشته و تا زمانی که کاربر با دسترسی به آنها موافقت نکند اپلیکیشن اجازه دسترسی به هیچکدام را نخواهد داشت. مانند مجوز دسترسی به دوربین، فهرست مخاطبین، سنسورها و… .
لیست زیر شامل مجوزهای خطرناک اندروید است:
ACCEPT_HANDOVER ACCESS_BACKGROUND_LOCATION ACCESS_COARSE_LOCATION ACCESS_FINE_LOCATION ACCESS_MEDIA_LOCATION ACTIVITY_RECOGNITION ADD_VOICEMAIL ANSWER_PHONE_CALLS BODY_SENSORS CALL_PHONE CAMERA GET_ACCOUNTS PROCESS_OUTGOING_CALLS READ_PHONE_NUMBERS READ_PHONE_STATE READ_SMS RECEIVE_SMS RECEIVE_MMS RECEIVE_WAP_PUSH RECORD_AUDIO USE_SIP WRITE_CALENDAR READ_CALENDAR WRITE_CALL_LOG READ_CALL_LOG WRITE_CONTACTS READ_CONTACTS READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE
فکر میکنم توضیحات فوق کفایت میکند. در ادامه و در قالب دو پروژه مجزا، ویژگی ران تایم پرمیشن (Runtime Permission) را به صورت عملی از حالت ساده تا پیشرفته بررسی میکنیم. هر سطح را در یک پروژه جداگانه انجام میدهم تا درک آن و همچنین دسترسی به سورس کدها سادهتر باشد.
پروژه شماره ۱: درخواست یک مجوز از کاربر توسط قابلیت Runtime Permission
در این قسمت ویژگی Runtime Permission را در ساده ترین حالت بررسی و تمرین میکنیم. در این پروژه فقط یک مجوز از کاربر درخواست میشود که اگر آنرا تایید یا رد کند، پیغام متناسب با آنرا دریافت خواهد نمود.
یک پروژه جدید در اندروید استودیو با نام Runtime Permission و یک Empty Activity ایجاد میکنم. همچنین زبان Java را برای پروژه انتخاب کردم.
ابتدا یک Button در layout اکتیویتی میسازم تا با کلیک روی آن، درخواست مجوز اجرا شود:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/btn_request" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:text="Request Permission" /> </RelativeLayout>
در جلسه گذشته یعنی آموزش کار با Camera2 API در اندروید مجوز دسترسی به دوربین (CAMERA) و نوشتن روی کارت حافظه (WRITE_EXTERNAL_STORAGE) را از کاربر دریافت کردیم. در این جلسه هم از همین مجوزها استفاده میکنم.
در پروژه شماره ۱ فقط یک مجوز را از کاربر دریافت میکنم بنابراین مجوز دسترسی به دوربین را در مانیفست تعریف کردم:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ir.android_studio.runtimepermission"> <uses-permission android:name="android.permission.CAMERA"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" 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>
در مرحله بعد رویداد setOnClickListener دکمه را تعریف کرده و یک شرط درون آن قرار میدهم:
MainActivity.java
package ir.android_studio.runtimepermission; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private Button requestButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestButton = findViewById(R.id.btn_request); requestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission(); } else { Toast.makeText(MainActivity.this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show(); } } }); } }
این شرط چک میکند اگر مجوز دسترسی به دوربین قبلا توسط کاربر تایید نشده، متد requestCameraPermission اجرا و در غیر اینصورت یک پیغام از جنس Toast نمایش داده شود با این مضمون که مجوز دسترسی قبلا دریافت شده است. که در یک پروژه واقعی، بجای این Toast کد مربوط به اتصال برنامه به دوربین را مینویسیم.
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED
برای بررسی فعال بودن یا نبودن مجوز مدنظر از متد checkSelfPermission استفاده میکنیم. این شرط دائما و برای هربار استفاده از دوربین باید اجرا شده و وضعیت را بررسی نماید زیرا ممکن است کاربر بعد از تایید یک مجوز، از طریق تنظیمات برنامه و به صورت دستی آنرا غیرفعال کند. این متد دو پارامتر دارد. پارامتر اول کانتکست و پارامتر دوم مجوز یا Permission مدنظر باید تعریف شود. مجوزها با فرمت زیر تعریف میشوند:
Manifest.permission.PERMISSION_NAME
در واقع همان مجوزی که قبلا در مانیفست پروژه تعریف کردیم اینجا نیز باید درخواست شود.
متد checkSelfPermission بعد از بررسی وضعیت مجوز، دو مقدار را برمیگرداند:
- PERMISSION_GRANTED: واژه Granted یعنی “موافقت شده”. بنابراین هنگامی که مجوز مدنظر قبلا تایید شده باشد این مقدار را برمیگرداند.
- PERMISSION_DENIED: واژه Denied یعنی “رد شده”. بنابراین هنگامی که مجوز مدنظر قبلا تایید نشده باشد این مقدار را برمیگرداند.
لذا در این شرط بررسی میکنیم اگر برای مجوز CAMERA نتیجهی “موافقت شده” برنگشت، یعنی برابر نبود (!=) با PERMISSION_GRANTED، متدی که با نام دلخواه requestCameraPermission نوشتهام را اجرا کن در غیر اینصورت اگر مقدار برگشتی PERMISSION_GRANTED بود، پیغام Toast مدنظر را نمایش بده.
روی متد requestCameraPermission کلیدهای ترکیبی alt + enter را میزنم تا متد بصورت خودکار و بدون نیاز به نوشتن دستی به اکتیویتی اضافه شود:
متد درون کلاس MainActivity و بعد از متد onCreate ساخته شد:
private void requestCameraPermission() { }
متد را به اینصورت کامل میکنم:
private void requestCameraPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.CAMERA)) { new AlertDialog.Builder(this) .setTitle("درخواست مجوز") .setMessage("برای دسترسی به دوربین باید مجوز را تایید کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { reqPermission(); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create() .show(); } else { reqPermission(); } }
در اینجا ما باید تاییدیه مجوز دسترسی به دوربین را از کاربر دریافت کنیم. قبل از درخواست مستقیم مجوز، از یک متد دیگر برای مدیریت بهتر Runtime Permission و به عبارتی بهبود UX یا همان تجربه کاربری استفاده میکنم. با استفاده از متد shouldShowRequestPermissionRationale یک شرط تعریف میکنیم. اگر دستور دریافت مجوز را مستقیما درون متد requestCameraPermission تعریف کنم ممکن است برای کاربر آماتور باعث بروز ابهام در استفاده از برنامه شود.
فرض کنید شخص قصد دارد توسط اپلیکیشن شبکه اجتماعی خود برای اولین بار عکس بگیرد. درخواست تایید مجوز را مشاهده میکند اما چرایی دسترسی برنامه به دوربین برایش واضح نیست. یا اصلا متن درخواست را به درستی مطالعه نکرده و طبق عادت گزینه DENY یعنی لغو را انتخاب میکند. طبیعتا برنامه به دوربین متصل نشده. دوباره داخل برنامه سعی میکند تا بتواند یک تصویر ثبت کند اما باز هم درخواست مجوز تکرار میگردد. اینجا متد shouldShowRequestPermissionRationale وارد عمل میشود. با استفاده از این متد میتوانیم تعیین کنیم اگر کاربر قبلا یک بار درخواست مجوزی را رد کرده، برای دفعات بعد، قبل از درخواست مجدد مجوز ابتدا یک پیغام حاوی توضیحات لازم نیز نمایش داده شود. کاربرد این متد از ترجمه تحت الفظی نام آن نیز مشخص میشود: “باید علت درخواست مجوز نمایش داده شود”. فکر میکنم حالا ماهیت شرطی که تعریف شده برایتان روشن شده. در اینجا تعریف کردیم اگر shouldShowRequestPermissionRationale مقدار true برگرداند، به عبارتی اگر لازم است توضیحاتی به کاربر ارائه شود، قسمت اول شرط و در غیر اینصورت قسمت دوم شرط را اجرا شود. در قسمت اول شرط یک AlertDialog تعریف کردم که یک توضیح خلاصه را به کاربر نمایش میدهد. سپس دو دکمه “موافقم” و “لغو” اضافه شده. اگر گزینه موافق را انتخاب کند متد reqPermission اجرا خواهد شد در غیر اینصورت دیالوگ بسته شده و اتفاقی نمیافتد. اگرهم نیازی به نمایش توضیحات نیست و اولین بار است که این مجوز درخواست میشود، بدون هیچ عمل اضافهای متد reqPermission را اجرا کن.
حالا باید دستور مربوط به دریافت مجوز را درون متد reqPermission تعریف کنم. روی یکی از آنها alt + enter زده و متد را به اکتیویتی اضافه میکنم. سپس دستور درخواست مجوز را مینویسم:
private void reqPermission() { ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE); }
برای درخواست مجوز از متد requestPermissions استفاده میشود. این متد سه پارامتر میپذیرد. پارامتر اول کلاس اکتیویتی، پارامتر دوم مجوز مدنظر که در قالب یک String[] تعریف شده و پارامتر سوم یک کد جهت بررسی نتیجه درخواست است. این کد را قبلا درون اکتیویتی تعریف کردم. کد کامل MainActivity را بررسی کنید:
package ir.android_studio.runtimepermission; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final int CAMERA_REQUEST_CODE = 100; private Button requestButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestButton = findViewById(R.id.btn_request); requestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission(); } else { Toast.makeText(MainActivity.this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show(); } } }); } private void requestCameraPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.CAMERA)) { new AlertDialog.Builder(this) .setTitle("درخواست مجوز") .setMessage("برای دسترسی به دوربین باید مجوز را تایید کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { reqPermission(); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create() .show(); } else { reqPermission(); } } private void reqPermission() { ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE); } }
نیاز به توضیح نیست که مقدار تعیین شده برای CAMERA_REQUEST_CODE کاملا اختیاری میباشد.
در نهایت برای مدیریت نتیجه (result) درخواست مجوز لازم است متد onRequestPermissionsResult را درون اکتیویتی Override کنیم:
متد به اکتیویتی اضافه میشود:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
برای بررسی نتیجه درخواست از کد CAMERA_REQUEST_CODE استفاده میکنیم. پارامتر نخست onRequestPermissionsResult از نوع int و با نام requestCode است. همچنین تعداد مجوزهای تایید شده (granted) نیز درون پارامتر grantResults ذخیره میگردد. متد را تکمیل میکنم:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == CAMERA_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "مجوز تایید شد", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "مجوز رد شد", Toast.LENGTH_SHORT).show(); } } }
ابتدا بررسی میشود کد موجود در requestCode با کد مجوز مدنظر ما یکسان باشد. سپس درون این شرط یک شرط دیگر قرار دارد. شرط دوم بررسی میکند اولا طول (length) grantResults بزرگتر از صفر باشد (یعنی حداقل یک مجوز تایید شده باشد) و ثانیا درخواست مجوز نخست (یعنی موقعیت صفر) نیز تایید شده باشد که در این پروژه ما فقط یک مجوز درخواست کردهایم. حالا اگر شرط برقرار بود، پیغام “مجوز تایید شد” و در غیر اینصورت پیغام “مجوز رد شد” اجرا شود.
خب! حالا پروژه را روی یک دیوایس با API 23 (اندروید ۶) یا بالاتر اجرا میکنم:
مشاهده میکنید با کلیک روی دکمه درخواست مجوز، دیالوگی با آیکون دوربین اجرا میشود که از کاربر میخواهد دسترسی جهت ثبت عکس یا ضبط ویدئو را به برنامه بدهد. روی گزینه ALLOW کلیک میکنم:
مجوز دسترسی به دوربین برای برنامه صادر شده و پیغام Toast مرتبط با آن نیز اجرا شد. حالا دوباره روی دکمه کلیک میکنم:
پیغام “مجوز قبلا دریافت شده” اجرا میشود.
اگر بخاطر داشته باشید گفتیم متد checkSelfPermission باید هر بار اجرا شود تا بررسی کند آیا مجوز فعال است یا خیر. ممکن است بگویید اگر کاربر یکبار مجوز را تایید کند دیگر امکان غیر فعال کردن برایش وجود ندارد. در صورتی که اینطور نیست و در قسمت مدیریت اپلیکیشنها در اندروید لیست مجوزهای هر اپ نمایش داده میشود:
روی Permissions کلیک میکنم:
ملاحظه میکنید مجوز دسترسی به CAMERA در اینجا فعال است که کاربر میتواند آنرا غیر فعال کند.
حالا میخواهم حالت دوم را تست کنم. یعنی هنگامی که کاربر مجوز درخواستی Runtime Permission را تایید نکرده و گزینه DENY را انتخاب کند. ابتدا اپ فعلی را از روی دیوایس Uninstall میکنم تا تنظیمات قبلی برنامه کاملا حذف شود. سپس دوباره پروژه را Run کرده و روی دکمه کلیک میکنم. اینبار گزینه DENY را انتخاب میکنم:
پیغام “مجوز رد شد” اجرا شد.
دومرتبه روی دکمه کلیک میکنم:
اینبار بجای نمایش دیالوگ مربوط به درخواست مجوز، AlertDialog اجرا میشود. اگر روی گزینه “موافقم” کلیک شود، درخواست مجوز مجدد انجام خواهد شد:
البته اینبار یک تفاوت با گذشته وجود دارد. گزینهای با عنوان Never ask again (یعنی دوباره سوال نکن) به درخواست اضافه شده. اگر کاربر این گزینه را تیک بزند و DENY را انتخاب کند با کلیک مجدد روی دکمه این درخواست اجرا نخواهد شد و بازهم پیغام “مجوز رد شد” اجرا میگردد. اگر در آینده شخص بخواهد این مجوز را تایید کند باید به صورت دستی و در تنظیمات برنامه (قسمت Permissions) آنرا فعال کند. در پروژه بعدی این عملیات یعنی انتقال از برنامه به صفحه تنظیمات را به صورت خودکار انجام میدهیم.
کد کامل MainActivity.java
package ir.android_studio.runtimepermission; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final int CAMERA_REQUEST_CODE = 100; private Button requestButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestButton = findViewById(R.id.btn_request); requestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission(); } else { Toast.makeText(MainActivity.this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show(); } } }); } private void requestCameraPermission() { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.CAMERA)) { new AlertDialog.Builder(this) .setTitle("درخواست مجوز") .setMessage("برای دسترسی به دوربین باید مجوز را تایید کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { reqPermission(); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create() .show(); } else { reqPermission(); } } private void reqPermission() { ActivityCompat.requestPermissions(MainActivity.this, new String[] {Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == CAMERA_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "مجوز تایید شد", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "مجوز رد شد", Toast.LENGTH_SHORT).show(); } } } }
پروژه شماره ۲: درخواست چند مجوز همزمان در Runtime Permission
در پروژه قبل فقط درخواست تایید یک مجوز را از کاربر گرفتیم. در این پروژه قصد داریم همزمان تاییدیه سه مجوز را دریافت کنیم. سپس گزینه Never ask Again را طوری مدیریت کنیم که در صورت انتخاب آن توسط کاربر، در دفعات بعدی درخواست، کاربر مستقیم به صفحه تنظیمات برنامه هدایت شود تا بدین ترتیب از سردرگمی کاربر در عملکرد برنامه جلوگیری کنیم.
یک پروژه جدید با نام Multiple Runtime Permission در اندروید استودیو ایجاد میکنم.
ابتدا مجوزهای مدنظرم را در مانیفست پروژه تعریف میکنم:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ir.android_studio.multipleruntimepermission"> <uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" 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>
مجوزها به ترتیب: دسترسی به دوربین، خواندن از کارت حافظه و نوشتن روی کارت حافظه.
در مرحله اول اکتیویتی را به صورت زیر تکمیل کردهام:
MainActivity.java
package ir.android_studio.multipleruntimepermission; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final int PERMISSION_REQUEST_CODE = 100; private Button requestButton; String[] requiredPermissions = new String[] {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestButton = findViewById(R.id.btn_request); requestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[0]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[1]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[2]) != PackageManager.PERMISSION_GRANTED) { requestAppPermissions(); } else { Toast.makeText(MainActivity.this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show(); } } }); } private void requestAppPermissions() { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[0]) || ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[1]) || ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[2])) { new AlertDialog.Builder(MainActivity.this) .setTitle("درخواست مجوز") .setMessage("برای دسترسی به دوربین و کارت حافظه باید مجوز را تایید کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { reqPermissions(); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create() .show(); } else { reqPermissions(); } } private void reqPermissions() { ActivityCompat.requestPermissions(MainActivity.this, requiredPermissions, PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { boolean allPermissionsGranted = false; for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { allPermissionsGranted = true; } else { allPermissionsGranted = false; break; } } if (allPermissionsGranted) { Toast.makeText(MainActivity.this, "مجوزها تایید شدند", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "مجوزها تایید نشدند", Toast.LENGTH_SHORT).show(); } } } }
ابتدا درون بدنه اکتیویتی یک متغیر از جنس int با مقدار دلخواه ۱۰۰ تعریف کردم که مانند پروژه قبل برای بررسی نتیجه درخواست مجوز استفاده میشود. سپس برای تعریف مجوزهای مدنظر از جنس String[] متغیری با نام requiredPermissions نوشتم که ۳ مجوز در آن تعریف شده است.
سپس داخل متد onCreate اکتیویتی یک setOnClickListener برای دکمه تعریف شده که با استفاده از متد checkSelfPermission شرطی درون آن تعریف شده. ما میخواهیم ۳ شرط بطور همزمان بررسی شود بنابراین متد checkSelfPermission نیز باید برای هر مجوز جداگانه نوشته شود:
if (ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[0]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[1]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[2]) != PackageManager.PERMISSION_GRANTED)
واضح است که requiredPermissions با اندیس ۰ اولین مجوز تعریف شده یعنی CAMERA را برمیگرداند و به همین ترتیب برای دو مورد دیگر. در اینجا ما گفتیم اگر مجوز اول یا مجوز دوم یا مجوز سوم قبلا تایید نشده، متد requestAppPermissions را اجرا کن در غیر اینصورت پیغام “مجوز قبلا دریافت شده” را نمایش بده.
روی requestAppPermissions کلیدهای ترکیبی alt + enter زده و متد را به اکتیویتی اضافه کردهام. سپس درون آن متد shouldShowRequestPermissionRationale را برای هرکدام از مجوزها فراخوانی کرده و تعیین کردم در صورتی که هرکدام از این شروط برقرار بود (یعنی حداقل یکی از مجوزها نیاز به نمایش توضیحات دارد) یک AlertDialog نمایش بده که با کلیک روی دکمه “موافقم” متد reqPermissions اجرا شود در غیر اینصورت مستقیما reqPermissions اجرا گردد:
private void reqPermissions() { ActivityCompat.requestPermissions(MainActivity.this, requiredPermissions, PERMISSION_REQUEST_CODE); }
در اینجا و توسط متد requestPermissions مجوزهایی که قبلا در requiredPermissions تعریف کردیم از کاربر درخواست میشوند.
در ادامه لازم است متد onRequestPermissionsResult را مانند پروژه گذشته به اکتیویتی اضافه و تکمیل کنم:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { boolean allPermissionsGranted = false; for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { allPermissionsGranted = true; } else { allPermissionsGranted = false; break; } } if (allPermissionsGranted) { Toast.makeText(MainActivity.this, "مجوزها تایید شدند", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, " مجوزها تایید نشدند ", Toast.LENGTH_SHORT).show(); } } }
ابتدا کد درخواست و کد نتیجه دریافتی بررسی میشود. درون این شرط ابتدا یک boolean با نام allPermissionsGranted و مقدار اولیه false تعریف کردم. سپس یک حلقه for تعریف شده که از اولین تا آخرین نتیجه درخواست مجوز را بررسی میکند و چنانچه همه موارد توسط کاربر تایید شده باشد (PERMISSIONS_GRANTED) مقدار allPermissionsGranted برابر با true میشود در غیر اینصورت همان false باقی میماند.
حالا یک if تعریف میکنم که اگر allPermissionsGranted برقرار باشد (یعنی true باشد) پیغام “مجوزها تایید شدند” نمایش داده شود و در غیر اینصورت پیغام “مجوزها تایید نشدند” ظاهر خواهد شد.
پروژه را اجرا میکنم:
بعد از کلیک روی دکمه، دیالوگ مجوزها نمایش داده میشود. ابتدا تایید دسترسی به دوربین درخواست شده که آنرا تایید میکنم. سپس دسترسی به کارت حافظه درخواست میشود. آنرا نیز تایید میکنم. در نهایت پیغام “مجوزها تایید شدند” اجرا شده است.
بعد از تایید مجوزها دوباره روی دکمه کلیک میکنم:
اینبار پیغام “مجوز قبلا دریافت شده” اجرا میشود. بنابراین عملیاتی که بعد از دریافت مجوزها باید انجام شود (مانند باز شدن دوربین و ذخیره عکس روی کارت حافظه) باید در قسمتی که این Toast تعریف شده قرار گیرد.
سپس حالت دوم را بررسی میکنیم. یعنی زمانی که کاربر مجوزها را تایید نکند.
ابتدا اپلیکیشن این پروژه را از روی دیوایس را حذف کرده و مجدد پروژه را اجرا میکنم. روی دکمه کلیک کرده و یک و یا هردو مجوز را DENY میکنم. برای مرتبه دوم روی Button کلیک میکنم:
به دلیل اینکه قبلا یکبار درخواست مجوزها تایید نشده ابتدا AlertDialog اجرا شده که با تایید آن مجددا دیالوگ درخواست مجوزها اجرا میشود:
اینبار هم برای یکی از دو مورد یا هردو، گزینه Never ask again را انتخاب و DENY میکنم. حالا در صورتی که کاربر برای مرتبه سوم روی دکمه کلیک کند، هیچ اتفاقی رخ نخواهد داد زیرا قبلا تایید کرده که مجوزها هیچگاه درخواست نشوند. اگر شخص این گزینه را از روی نا آگاهی انتخاب کرده و یا قبلا مایل به اعطای مجوز به برنامه نبوده و حالا تصمیمش عوض شده، در اینجا اگر آماتور باشد احتمال زیاد دچار سردرگمی خواهد شد. راه حل مناسب این است که در این مرحله او را به صورت خودکار به صفحه تنظیمات برنامه هدایت کنیم.
کد کامل اکتیویتی را در ادامه قرار داده و سپس به بررسی کدهای جدید میپردازم:
MainActivity.java
package ir.android_studio.multipleruntimepermission; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private final int PERMISSION_REQUEST_CODE = 100; private static final int REQUEST_PERMISSION_SETTINGS = 101; private Button requestButton; String[] requiredPermissions = new String[] {Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; SharedPreferences permissionStatus; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); permissionStatus = getSharedPreferences("permsStatus", MODE_PRIVATE); requestButton = findViewById(R.id.btn_request); requestButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[0]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[1]) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(MainActivity.this, requiredPermissions[2]) != PackageManager.PERMISSION_GRANTED) { requestAppPermissions(); } else { Toast.makeText(MainActivity.this, "مجوز قبلا دریافت شده", Toast.LENGTH_SHORT).show(); } } }); } private void requestAppPermissions() { if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[0]) || ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[1]) || ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, requiredPermissions[2])) { new AlertDialog.Builder(MainActivity.this) .setTitle("درخواست مجوز") .setMessage("برای دسترسی به دوربین و کارت حافظه باید مجوز را تایید کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { reqPermissions(); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.dismiss(); } }) .create() .show(); } else if (permissionStatus.getBoolean(requiredPermissions[0], false)) { new AlertDialog.Builder(MainActivity.this) .setTitle("تایید دستی مجوزها") .setMessage("لطفا در تنظیمات برنامه (گزینه Permissions) مجوزها را فعال کنید") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); Intent mIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri packageUri = Uri.fromParts("package", getPackageName(), null); mIntent.setData(packageUri); startActivityForResult(mIntent, REQUEST_PERMISSION_SETTINGS); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); } }) .create() .show(); } else { reqPermissions(); } SharedPreferences.Editor shEditor = permissionStatus.edit(); shEditor.putBoolean(requiredPermissions[0], true); shEditor.apply(); } private void reqPermissions() { ActivityCompat.requestPermissions(MainActivity.this, requiredPermissions, PERMISSION_REQUEST_CODE); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { boolean allPermissionsGranted = false; for (int i = 0; i < grantResults.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { allPermissionsGranted = true; } else { allPermissionsGranted = false; break; } } if (allPermissionsGranted) { Toast.makeText(MainActivity.this, "مجوزها تایید شدند", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "مجوزها تایید نشدند", Toast.LENGTH_SHORT).show(); } } } }
ابتدا یک متغیر با نام REQUEST_PERMISSION_SETTINGS از جنس int و با مقدار دلخوه ۱۰۱ و همچنین یک شیء از SharedPreferences با نام permissionStatus تعریف کردم. قبلا در آموزش ذخیره اطلاعات با SharedPreferences در اندروید با این متد آشنا شدیم. از این متد برای ذخیره وضعیت مجوزها استفاده میکنیم. برای دسترسی به آن، خط زیر را در onCreate تعریف کردم:
permissionStatus = getSharedPreferences("permsStatus", MODE_PRIVATE);
در ادامه در شرط تعریف شده در متد requestAppPermissions یک else if اضافه شده:
else if (permissionStatus.getBoolean(requiredPermissions[0], false)) { new AlertDialog.Builder(MainActivity.this) .setTitle("تایید دستی مجوزها") .setMessage("لطفا در تنظیمات برنامه (گزینه Permissions) مجوزها را فعال کنید ") .setPositiveButton("موافقم", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); Intent mIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri packageUri = Uri.fromParts("package", getPackageName(), null); mIntent.setData(packageUri); startActivityForResult(mIntent, REQUEST_PERMISSION_SETTINGS); } }) .setNegativeButton("لغو", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { dialogInterface.cancel(); } }) .create() .show(); }
به خط زیر توجه کنید:
permissionStatus.getBoolean(requiredPermissions[0], false)
در اینجا گفتیم اگر حداقل یکی از درخواست مجوزها تایید نشد (یعنی getBoolean مقدار false برگرداند) شرط را اجرا کن. در اینجا هم یک AlertDialog داریم که به کاربر توضیح میدهد باید به صورت دستی مجوزها را فعال کند. در صورت تایید پیغام، دستورات زیر اجرا خواهد شد:
Intent mIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri packageUri = Uri.fromParts("package", getPackageName(), null); mIntent.setData(packageUri); startActivityForResult(mIntent, REQUEST_PERMISSION_SETTINGS);
کاری که انجام دادیم این است که با استفاده از Intent کاربر را به صفحه تنظیمات برنامه هدایت میکنیم. برای اینکه مشخص شود کاربر باید به صفحه تنظیمات چه برنامهای هدایت شود از Uri استفاده شده که توسط Uri.fromParts و پارامتر getPackageName این کار انجام میپذیرد. همانطور که از نام getPackageName پیداست وظیفه آن برگرداندن نام پکیج برنامه است.
اینکه نتیجه دریافت مجوز true هست یا false در کد زیر در SharedPreferences قبل از اجرای شرط ذخیره شده است:
SharedPreferences.Editor shEditor = permissionStatus.edit(); shEditor.putBoolean(requiredPermissions[0], true); shEditor.apply();
در ابتدا مقدار پیش فرض true برای وضعیت دریافت مجوز ثبت میشود اما چنانچه یک مورد در وضعیت false قرار گرفته باشد (یعنی گزینه Never ask again انتخاب شده باشد)، مقدار false توسط putBoolean به shEditor فرستاده میشود.
پروژه را اجرا میکنم. من قبلا گزینه Never ask again را انتخاب کرده بودم بنابراین اگر کد من ایرادی نداشته باشد باید AlertDialog مربوط به مجوز دستی اجرا شود:
هدف ما با موفقیت انجام شد.
اگر معتقدید که این حجم از کد برای دریافت یک مجوز ساده آزار دهنده است باید بگویم که شما تنها نفری نیستید که چنین عقیدهای دارید! برای همین توسعه دهندگان زیادی دست به کار شده و کتابخانههای مختلفی را برای سادهتر کردن روند دریافت تاییدیه مجوز یا همان Runtime Permission منتشر کردهاند. معروفترین کتابخانهها عبارت اند از: Dexter، Android-Permissions، EasyPermissions و Let.
کار با این کتابخانهها بسیار ساده است. با اینحال در صورتی که فرصتی فراهم شود آموزشی پیرامون معتبرترین کتابخانه یعنی Dexter تهیه خواهم کرد.
امیدوارم در این آموزش به اکثریت نکات مربوط به قابلیت Runtime Permission پرداخته باشم و نکته مبهمی برای شما عزیزان باقی نمانده باشد. چنانچه سوالی به ذهنتان خطور کرد در دیدگاهها مطرح کنید تا در حد دانش خودم راهنمایی کنم.
موفق و پیروز باشید.
مطالعهی بیشتر:
https://developer.android.com/guide/topics/permissions/overview
https://developer.android.com/training/permissions/requesting.html
https://developer.android.com/reference/android/Manifest.permission.html
توجه : سورس پروژهها درون پوشه Exercises قرار دارد
تعداد صفحات : ۴۵
حجم : ۳ مگابایت
قیمت : رایگان
دانلود رایگان با حجم ۳ مگابایت لینک کمکی
چجوری با اجرای برنامه درخواست چنتا دسترسی از کاربر بشه اونو توضیح ندادید خیلی پیچیدش کردید همون بهترین روش الان واسه ماست لطفا بگید
مطالعه کنید:
https://stackoverflow.com/q/34342816
https://wajahatkarim.com/2018/11/multiple-runtime-permissions-in-android-without-any-third-party-libraries
سلام
من با این ارور مواجه شدم
Couldn’t get json from server, Check LogCat for possible error!
بعد فقط از اندروید ۶ به بالا اینطور اروری میاد
الانم گفتن باید مجوز ها رو بدی
و من اومدم سایت شما
که اینجا دیدم دسرسی اینترنت هیچ مجوزی نمیخواد درسته ؟
پس چرا اینطوری میشه
اگ میشه بگین خیلی نیاز دارم
اینم از دسرسی!
ولی واسه اندروید ۶ به بالا هیچ واکونشی انجام نمیده!
مجوز دسترسی به اینترنت باید داخل مانیفست تعریف بشه
سلام و عرض خدا قوت خدمت شما
من چطور میتونم
اپلیکیشنی که برای کاربران میسازم رو مدیریت کنم
مثلا چند نفر دانلود کردند
چند نفر حق عضویت پرداخت کردن
اصلا همین پرداخت هزینه چطوری هست
چطور میشه بعد پرداخت هزینه به کاربر اجازه دسترسی داد
هرکدومش یه مبحث مفصله
برای شمارش دانلود اگه داخل مارکت ها باشه که آمار دانلود همونجا هست. اگه لینک مستقیم بخواید بدید سرویسهایی هستن که آنالیز آمار دانلود رو در اختیارتون قرار میدن
پرداخت هزینه هم میتونه با درگاه خودتون باشه هم از طریق مارکت مثل بازار
سلام
وقتی ک میخوام مجوز رو فعال کنم (مجوز نمایش روی سایر برنامه ها) کلا نمیشه فعال کرد چرا؟.
منظورتون از مجوز نمایش روی سایر برنامه ها چیه؟
چطور باید سازگار سازی کرد با اندروید ۱۱ (گرفتن مجوز در اندروید ۱۱)
تفاوتی با نسخه های قبل نکرده
سلام
ممنون میشم راهنمایی کنید
من دسترسی حافظه رو گرفتم و کد مربوط به ساخت پوشه اما هر کاری میکنم پوشه ایجاد نمشه
logcat رو بررسی کنید موقع اجرای پوژه روی دیوایس یا شبیه ساز
سلام داداش من یه برنامه دارم میخوام یه مجوز خیلی مهم به اون بدم که نه ربطی به دوربین.میکروفون و… نداره و بدون اون برنامه کار نمیکنه وقتی میرم مجوز رو فعال کنم تو بخش مجوز ها مینویسه (این ویدیو در این دستگاه در دسترس نیست) و من یک ماه هست که سر گردونم لطفا بهم کمک کنید اندروید گوشیم ۸.۱.۰ هست و(mblandrahim@gmail.com) جیمیلمه لطفا جواب بدید
چیه مجوزتون؟
به نام خدا سلام خدمت شما دوست عزیز چرا میزنم روی دانلود سورس بالا نمیاره یا روی لینک کمکی هم میزنم نمیاره؟
تست کردم هردو لینک در دسترس بود
سلام ببخشید شما اجازه میدید به من که کد هاتون کپی کنم وا از اونا استفاده کنم برای ساخت اپلیکیشنم؟
بزرگوار این کدها و آموزش ها برای اینه که ازش استفاده کنید دیگه
سلام خسته نباشید سوالی داشتم ازتون مهندس عزیز
داخل برنامه خودم از webview استفاده کردم و مجوز اینترنت رو دادم و هیچ مشکلی نداره در اندروید های بالاتر هم جواب میده ولی وقتی داخل minfest دستور چک کردن اینترنت یعنی کد android.permission.access_NETWORK_STATE رو می نویستم، در اندروید ۵ به بالا کار نمیکنه و توو WEB VIEW مینویسه NET::ERR_CLEARTEXT_NOT_PERMITTED
لطفا راهنماییم کنید
ممنون
همین اروری که گفتید رو سرچ کنید. اولین نتیجه مربوط به stackoverflow.com هست. توضیح داده برای رفع مشکل چه کاری باید انجام بدید. برای بقیه مشکلاتتون هم ارور رو گوگل کنید در اکثر مواقع خیلی سریع به جواب میرسید و لازم نیست منتظر جواب من و بقیه بمونید
سلام در برنامه من با زدن یک دکمه دسترسی به این حافظه گرفته میشه و یک فایل کپی میشه تو پوشه دانلود
در اندروید ۱۰ مجوز دسترسی داده میشه ولی فایلی کپی نمیشه در حافظه! فقط هم در اندروید ۱۰ این مشکل هست!
ممنون میشم راهنمایی کنید
logcat رو بررسی کنید ببینید علتش چی هست
سلام وقت شما بخیر
من یک برنامه دارم و از یک ریسایکلر و یک دیتابیس افلاین درست شده و میخوام اول دیتابیس روی کارت حافظه گوشی کپی بشه بعد هم ریسایکلر با اون پر بشه
اگه اندروید پایین باشه مشکل نداره ولی اندروید بالا باشه همون شروع برنامه اجازه دسترسی رو میگیرم ولی خب دیتابیس رو کپی نمیکنه برنامه اگه مجددا بسته و باز بشه برنامه کرش میکنه و دیتابیس کپی میشه و بار سوم درست انجام میده
حالا سوال اینجاست من میتونم همون اول که کاربر مجوز رو تایید کرد دیتابیس هم بدون کرش کردن کپی کنم ؟
موقعی که دسترسی گرفته میشه داخل logcat بررسی کنید ببینید علت کپی نشدن دیتابیس چی هست
سلام و خدا قوت به هرکی که واسه این مطلب زحمت کشیده.
بالاخره بعد از ۵ ساعت جستجو تو اینترنت، اینجا به نتیجه رسیدم.
تشکر میکنم، سایتتون فوق العادست.
سلام ممنون از مطلبتون.من یه مشکلی دارم اونم اینکه فرض کنید دوتا دکمه دارم تو صفحه با زدن یکی قراره فقط پرمیژن کال کردن گرفته بشه با زدن اون یکی دو تا پرمیژن کال و پیامک….مشکل من اینه که میخوام وقتی مثلا میزنم رو دکمه کال بعد از اینکه کاربر پرمیژنو تایید کرد بلافاصله کال اتفاق بیوفته و کاربر مجبور نباشه دوباره دکه رو بزنه….من متد کالو بردم تو onRequestPermissionsResult نوشتم ولی بعدش رو دکمه دوم که میزنم بعد که پرمیژن پیامک هم تایید میشه کال اتفاق میوفته که نمیخوام زیر دکمه دومم این اتفاق بیوفته….امیدوارم سوالمو خوب توضیح داده باشم.ممنون میشم راهنماییم کنید
مشکل حل شد ممنون
سلام. خیلی ممنونم، من رو نجات داد این نوشته.
با عرض سلام وخسته نباشید.
یه سوال داشتم:
توی پروژه دوم ما از یک گروه مجوز ها استفاده کردیم.
اگر کاربر یکی از مجوز ها رو اجازه داده باشه و یکی دیگه رو نه ،
ما میتونیم طوری کد بزنیم که اون مجوزی که اجازه نداده رو فقط برای کاربر نشون بدیم و فقط اون یکی رو اجازه بده؟
یا اینکه نه. داخل گروه مجوز ها در همه حالت ها باید تمام مجوز ها نمایش داده بشن؟
ممنون میشم راهنمایی کنین
حقیقتش دنبال این مورد نبودم. یه سرچ بکنید (فارسی یا انگلیسی) اگه به نتیجه ای نرسیدید بگید منم سرچ کنم
سلام خستن نباشید توضیحاتتون خیلی عالیه و واضحه فقط اگه میشه یه کمکی به من بکنید من یه مدت لبتابم خرابه پروژه هامو با Aide موبایل توسعه میدم ولی از وقتی کتابخانه Android.support تبدیل شده بهandroidx من هیچ دسترسی به این کتابخانه ندارم میشه راهنمایی کنید من چجوری تو aide این کتابخانه رو اضافه کنم؟؟؟
من با AIDE کار نکردم متاسفانه
ممنون که جواب دادین ولی شاید سوالمو درست نگفتم درواقع من نیاز دارم این کتابخونه رو از یه جایی بصورت دستی دانلود کنم شما نمیتونین تو سایتتون اپلود کنین؟
نه متاسفانه
سلام داداش
من می خواستم یه سیستم با مشخصات زیر ببندم به نظرت نسبت به قیمتش خوبه تو این وضع بازار و تا چند سال می تونم باهاش برنامه نویسی رو بدون مشکل سخت افزار ادامه بدم؟
CPU: Core I7 9750H
RAM: 16GB DDR4
HDD: 1TB+500GB SSD
GPU: nVIDIA Geforce GTX 1650 DDR5
RES: 4K
۱۷میلیون
این کانفیگ حالاحالاها جواب میده براتون ولی حقیقتا قیمت دستم نیست