Android 6.0動(dòng)態(tài)權(quán)限介紹與處理

從 Android 6.0(API 級(jí)別 23)開始,用戶開始在應(yīng)用運(yùn)行時(shí)向其授予權(quán)限,而不是在應(yīng)用安裝時(shí)授予。 Android 6.0系統(tǒng)6.0以前,所有的權(quán)限,訪問(wèn)網(wǎng)絡(luò)的權(quán)限,讀取SD卡的權(quán)限,訪問(wèn)通訊錄,撥打電話的權(quán)限都在安裝的時(shí)候系統(tǒng)授予了要安裝的應(yīng)用。在系統(tǒng)運(yùn)行的時(shí)候不需要對(duì)權(quán)限做任何的處理。用戶在安裝的時(shí)候一般都不知道這些權(quán)限用在了什么地方,這樣做很不安全。一個(gè)應(yīng)用很容易在一個(gè)Service中讀取用戶所有的通訊錄信息發(fā)送到服務(wù)器上。Android 6.0后的動(dòng)態(tài)權(quán)限讓我們的系統(tǒng)更加安全,犧牲了用戶的方便性,得到的是安全。下圖是Android 6.0系統(tǒng)對(duì)撥打電話和管理電話權(quán)限組的詢問(wèn)。

image.png

Android 6.0把權(quán)限分為兩種:Normal Permissions(正常權(quán)限)和Dangerous Permissions(危險(xiǎn)權(quán)限)。其中危險(xiǎn)權(quán)限又進(jìn)行了分類。把所有的危險(xiǎn)權(quán)限分成了幾個(gè)組。正常權(quán)限不會(huì)給用戶的隱私帶來(lái)不安全,不需要?jiǎng)討B(tài)申請(qǐng),在應(yīng)用安裝的時(shí)候就已經(jīng)被授予了。危險(xiǎn)權(quán)限需要?jiǎng)討B(tài)處理,只有用戶批準(zhǔn)了這些權(quán)限,應(yīng)用才能被授予這些權(quán)限。

所有的普通權(quán)限:

  • ACCESS_LOCATION_EXTRA_COMMANDS
  • ACCESS_NETWORK_STATE
  • ACCESS_NOTIFICATION_POLICY
  • ACCESS_WIFI_STATE
  • BLUETOOTH
  • BLUETOOTH_ADMIN
  • BROADCAST_STICKY
  • CHANGE_NETWORK_STATE
  • CHANGE_WIFI_MULTICAST_STATE
  • CHANGE_WIFI_STATE
  • DISABLE_KEYGUARD
  • EXPAND_STATUS_BAR
  • GET_PACKAGE_SIZE
  • INSTALL_SHORTCUT
  • INTERNET
  • KILL_BACKGROUND_PROCESSES
  • MODIFY_AUDIO_SETTINGS
  • NFC
  • READ_SYNC_SETTINGS
  • READ_SYNC_STATS
  • RECEIVE_BOOT_COMPLETED
  • REORDER_TASKS
  • REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
  • REQUEST_INSTALL_PACKAGES
  • SET_ALARM
  • SET_TIME_ZONE
  • SET_WALLPAPER
  • SET_WALLPAPER_HINTS
  • TRANSMIT_IR
  • UNINSTALL_SHORTCUT
  • USE_FINGERPRINT
  • VIBRATE
  • WAKE_LOCK
  • WRITE_SYNC_SETTINGS

所有的危險(xiǎn)權(quán)限:

image.png

二、Android 6.0+危險(xiǎn)權(quán)限的動(dòng)態(tài)處理

在開發(fā)應(yīng)用的時(shí)候不管是正常權(quán)限還是危險(xiǎn)權(quán)限都必須在應(yīng)用的Manifest.xml文件中聲明。如果設(shè)備運(yùn)行的是Android 5.1或更低的系統(tǒng),或者應(yīng)用的目標(biāo)SDK小于23,那么在Manifest.xml文件中列出的危險(xiǎn)權(quán)限在安裝的時(shí)候用戶必須接受,要不應(yīng)用沒(méi)法安裝。如果設(shè)備運(yùn)行的是Android 6.0或更高的系統(tǒng),或者應(yīng)用的目標(biāo)SDK大于等于23,那么在Manifest.xml文件中列出的危險(xiǎn)權(quán)限,會(huì)在應(yīng)用運(yùn)行的時(shí)候被用戶授予或拒絕。開發(fā)者需要在代碼中對(duì)危險(xiǎn)權(quán)限進(jìn)行處理。

public class MainActivity extends AppCompatActivity {

    final public static int REQUEST_CODE_ASK_CALL_PHONE = 123;
    private String mMobile;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.button).setOnClickListener(new                                                   

        View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onCallWrapper("12345678912");
            }
        });

    }
    public void onCallWrapper(String mobile) {
        this.mMobile = mobile;
        //檢測(cè)權(quán)限
        int checkCallPhonePermisssion = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
        if (checkCallPhonePermisssion != PackageManager.PERMISSION_GRANTED || !checkOpsPermission(this,Manifest.permission.CALL_PHONE)) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,          Manifest.permission.CALL_PHONE)) {
                Toast.makeText(this, "shouldShowRequestPermissionRationale", Toast.LENGTH_SHORT).show();
                new AlertDialog.Builder(this)
                        .setTitle("提示")
                        .setMessage("應(yīng)用需要開啟拍照的權(quán)限,是否繼續(xù)?")
                        .setPositiveButton("確定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ActivityCompat.requestPermissions(MainActivity.this, new           String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
                            }
                        })
                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {

                            }
                        }).show();
            } else {
                //請(qǐng)求權(quán)限
                ActivityCompat.requestPermissions(this, new String[]             {Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
            }

        } else {
            callDirectly(mobile);
        }
    }

  //權(quán)限檢測(cè)
    private static boolean checkOpsPermission(Context context, String permission) {
        //6.0 api 23
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
            try {
                AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
                String opsName = null;
                opsName = AppOpsManager.permissionToOp(permission);
                if (opsName == null) {
                    return true;
                }
                int opsMode = appOpsManager.checkOpNoThrow(opsName, Process.myUid(), context.getPackageName());
                return opsMode == AppOpsManager.MODE_ALLOWED;
            } catch (Exception ex) {
                return true;
            }
        }
        return true;
    }

    //請(qǐng)求權(quán)限返回的結(jié)果
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,        
@NonNull int[] grantResults) {

        switch (requestCode) {
            case REQUEST_CODE_ASK_CALL_PHONE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // Permission Granted 授予權(quán)限
                    callDirectly(mMobile);
                } else {
                    // Permission Denied 權(quán)限被拒絕
                    Toast.makeText(MainActivity.this, "Permission Denied", 
                  Toast.LENGTH_SHORT).show();
                }

                break;
            default:
                break;
        }

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

    private void callDirectly(String mobile) {
        Intent intent = new Intent();
        intent.setAction("android.intent.action.CALL");
        intent.setData(Uri.parse("tel:" + mobile));
        this.startActivity(intent);
    }
}

上面這個(gè)簡(jiǎn)單的例子是通過(guò)點(diǎn)擊一個(gè)按鈕撥打電話。在6.0系統(tǒng)之前,我們只需要直接調(diào)用方法callDirectly()方法就可以了。增加了系統(tǒng)的安全性,但是多了很多代碼上的處理。ContextCompat和
ActivityCompat分別是Context和Activity的兼容類,在上的代碼中我們就不用去判斷當(dāng)前系統(tǒng)的API是多好了,在API23之前也能用上面代碼中的方法。ContextCompat.checkSelfPermission()方法是用來(lái)判斷,當(dāng)前應(yīng)用是否擁有某個(gè)權(quán)限。如果擁有我們可以直接調(diào)用callDirectly()方法撥打電話。如果當(dāng)前應(yīng)用沒(méi)有撥打電話的權(quán)限,會(huì)調(diào)ActivityCompat.shouldShowRequestPermissionRationale()方法,注意當(dāng)前系統(tǒng)還沒(méi)有彈出讓用戶去選擇是允許還是拒絕,這個(gè)時(shí)候shouldShowRequestPermissionRationale()方法返回false。所以接下來(lái)會(huì)執(zhí)ActivityCompat.requestPermissions()方法會(huì)向系統(tǒng)去請(qǐng)求,系統(tǒng)會(huì)彈出一個(gè)對(duì)話框讓用戶去選擇。onRequestPermissionsResult()方法用來(lái)處理用戶的選擇。這個(gè)方法可以監(jiān)聽(tīng)用戶選擇的是允許還是拒絕。當(dāng)用戶選擇了允許,那么直接調(diào)用callDirectly()方法撥打電話。當(dāng)用戶選擇了拒絕,上面的代碼只是提示一個(gè)Toast。

當(dāng)用戶選擇了拒絕,那么在下次點(diǎn)擊按鈕撥打電話的時(shí)候ActivityCompat.shouldShowRequestPermissionRationale()方法會(huì)返回true,在上面的代碼中我們會(huì)彈出一個(gè)對(duì)話框給用戶,給用戶一個(gè)提示。當(dāng)用戶選擇確定,會(huì)向系統(tǒng)請(qǐng)求權(quán)限。當(dāng)用戶選擇取消,關(guān)閉對(duì)話框什么也不做。當(dāng)我們選擇了不在提示并且選擇了拒絕的時(shí)候ActivityCompat.shouldShowRequestPermissionRationale()方法返回false。

三、用Easy Permissions開源庫(kù)處理權(quán)限

1.配置

在app層的build.gradle中

dependencies {
    // EasyPermissions
    compile 'pub.devrel:easypermissions:1.2.0'
}

github : https://github.com/googlesamples/easypermissions

2.舉例

EasyPermissions是谷歌封裝的一個(gè)運(yùn)行時(shí)權(quán)限申請(qǐng)的庫(kù),簡(jiǎn)化了操作的過(guò)程

1、builde gradle中依賴
2、清單文件中聲明權(quán)限
3、重寫onRequestPermissionsResult()方法,把執(zhí)行操作給easyPermissions來(lái)
4、通過(guò)hasPermissions檢查權(quán)限,或者原生的也行,然后去申請(qǐng)權(quán)限
5、實(shí)現(xiàn)EasyPermissions.PermissionCallbacks接口,重寫兩個(gè)方法,成功或失敗
6、在成功或者失敗方法中編寫要具體做的事。
public class SecondActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {

    private static final int RC_CAMERA_PERM = 123;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cameraTask();
            }
        });
    }

//RC_CAMERA_PERM 標(biāo)識(shí)請(qǐng)求碼, (可選)@AfterPermissionGranted()注解 .這里的方法名可以自己取,
//主要是權(quán)限都申請(qǐng)完,就調(diào)用這個(gè)方法,執(zhí)行里面的操作。其實(shí)就相當(dāng)于在onPermissionsGranted()調(diào)用這個(gè)方法而已: 
    @AfterPermissionGranted(RC_CAMERA_PERM)
    public void cameraTask() {
        //通過(guò)hasPermissions檢查權(quán)限
        if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
            // 該應(yīng)用已經(jīng)有打電話的權(quán)限
            Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
        } else {
          //請(qǐng)求權(quán)限
            EasyPermissions.requestPermissions(this, "需要獲取系統(tǒng)的拍照的權(quán)限!", RC_CAMERA_PERM, Manifest.permission.CAMERA);
        }
    }

//重新以下三個(gè)方法  1,把執(zhí)行結(jié)果的操作給EasyPermissions
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
    }

  //請(qǐng)求成功執(zhí)行相應(yīng)的操作
    @Override
    public void onPermissionsGranted(int requestCode, List<String> perms) {
        Toast.makeText(this, "onPermissionsGranted", Toast.LENGTH_SHORT).show();
    }

//申請(qǐng)失敗時(shí)調(diào)用
    @Override
    public void onPermissionsDenied(int requestCode, List<String> perms) {
        Toast.makeText(this, "onPermissionsDenied", Toast.LENGTH_SHORT).show();
        //不一定執(zhí)行,有些手機(jī)會(huì)執(zhí)行系統(tǒng)的彈框
        new AppSettingsDialog.Builder(this)
                .setTitle("權(quán)限申請(qǐng)")
                .setPositiveButton("確認(rèn)")
                .setNegativeButton("取消")
                .setRationale("當(dāng)前App需要申請(qǐng)camera權(quán)限,需要打開設(shè)置頁(yè)面么?")
                .setRequestCode(RC_CAMERA_PERM)
                .build()
                .show();
    }
}

也可以通過(guò)原生來(lái)檢查權(quán)限,比如有多個(gè)權(quán)限,可以抽取一個(gè)工具類

public final class CheckPermissionUtils {
    private CheckPermissionUtils() {
    }

    //需要申請(qǐng)的權(quán)限
    private static String[] permissions = new String[]{
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.CAMERA
    };

    //檢測(cè)權(quán)限
    public static String[] checkPermission(Context context){
        List<String> data = new ArrayList<>();//存儲(chǔ)未申請(qǐng)的權(quán)限
        for (String permission : permissions) {
            int checkSelfPermission = ContextCompat.checkSelfPermission(context, permission);
            if(checkSelfPermission == PackageManager.PERMISSION_DENIED){//未申請(qǐng)
                data.add(permission);
            }
        }
        return data.toArray(new String[data.size()]);
    }
}

以下是在activity中調(diào)用該工具類

//初始化權(quán)限
private void initPermission() {
        //檢查權(quán)限
        String[] permissions = CheckPermissionUtils.checkPermission(this);
        if (permissions.length == 0) {
            //權(quán)限都申請(qǐng)了
           
        } else {
            //申請(qǐng)權(quán)限 ,參數(shù)2:權(quán)限數(shù)組,參數(shù)3:請(qǐng)求碼code
            ActivityCompat.requestPermissions(this, permissions, 100);
          //把執(zhí)行結(jié)果的操作給EasyPermissions的onRequestPermissionsResult
        }
    }

這個(gè)例子是點(diǎn)擊一個(gè)按鈕,處理拍照的事情,所以我們需要獲取相機(jī)的權(quán)限。首先我們要復(fù)寫Activity或者Fragment的onRequestPermissionsResult()方法。SecondActivity實(shí)現(xiàn)了EasyPermissions.PermissionCallbacks這個(gè)接口復(fù)寫了onPermissionsGranted()和onPermissionsDenied()這兩個(gè)接口。當(dāng)用戶選擇了允許那么調(diào)用onPermissionsGranted()方法,當(dāng)用戶選擇了拒絕那么調(diào)用onPermissionsDenied()方法。AppSettingsDialog是一個(gè)詢問(wèn)用戶是否跳轉(zhuǎn)到運(yùn)行應(yīng)用的設(shè)置界面去開啟權(quán)限的對(duì)話框。

四、用Permissions Dispatcher開源庫(kù)處理權(quán)限

1.配置

在app層的build.gradle中

dependencies {
    // Permissions Dispatcher
    compile 'com.github.hotchemi:permissionsdispatcher:3.0.1'
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.0.1'
}

這種配置需要有個(gè)條件:Android Gradle Plugin >= 2.2

2.舉例

這個(gè)庫(kù)用到了5個(gè)注解:

注解 是否必須 描述
@RuntimePermissions 注冊(cè)在Acttivity或者Fragment上
@NeedsPermission 再需要權(quán)限的方法上注冊(cè)
@OnShowRationale 被注解的方法可以提示為什么需要這個(gè)權(quán)限
@OnPermissionDenied 如果用戶拒絕了權(quán)限申請(qǐng)那么調(diào)用該方法
@OnNeverAskAgain 如果用戶選擇了不再詢問(wèn),調(diào)用該方法

這個(gè)庫(kù)用到了Annotion Processor,需要用到一個(gè)應(yīng)用編譯期間產(chǎn)生的類,這個(gè)類的命名是:類名+PermissionsDispatcher。


image.png
//注冊(cè)在Acttivity或者Fragment上
@RuntimePermissions
public class ThirdActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);

        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ThirdActivityPermissionsDispatcher.showContactsWithCheck(ThirdActivity.this);
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        ThirdActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

//處理當(dāng)用戶允許該權(quán)限時(shí)需要處理的方法
    @NeedsPermission({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void showContacts() {
        Toast.makeText(this, "讀取通訊錄", Toast.LENGTH_SHORT).show();
    }

//如果用戶拒絕了權(quán)限申請(qǐng)那么調(diào)用該方法
    @OnPermissionDenied({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void onContactsDenied() {
        Toast.makeText(this, "onContactsDenied", Toast.LENGTH_SHORT).show();

    }

//如果用戶選擇了不再詢問(wèn),調(diào)用該方法
    @OnNeverAskAgain({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void onContactsNeverAskAgain() {
        Toast.makeText(this, "onContactsNeverAskAgain", Toast.LENGTH_SHORT).show();
    }

//// 提示用戶權(quán)限使用的對(duì)話框
    @OnShowRationale({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
    public void showRationaleContacts(final PermissionRequest request) {
//        Toast.makeText(this, "showRationaleContacts", Toast.LENGTH_SHORT).show();

        new AlertDialog.Builder(this)
                .setPositiveButton("繼續(xù)", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.proceed();
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        request.cancel();
                    }
                })
                .setCancelable(false)
                .setMessage("需要通訊錄權(quán)限")
                .show();
    }

}

其實(shí)這個(gè)dispather就是對(duì)動(dòng)態(tài)權(quán)限處理的那些操作進(jìn)行了封裝,通過(guò)查看生成的MainActivityPermissionsDispatcher文件就可以驗(yàn)證這一點(diǎn)。這里的權(quán)限說(shuō)的是危險(xiǎn)權(quán)限,普通權(quán)限只用在Manifest中聲明就可以了。

五、一句代碼搞定權(quán)限請(qǐng)求,XXPermissions

image.png

github : https://github.com/getActivity/XXPermissions

集成步驟

dependencies {
    implementation 'com.hjq:xxpermissions:3.2'
}

使用

XXPermissions.with(this)
        //.constantRequest() //可設(shè)置被拒絕后繼續(xù)申請(qǐng),直到用戶授權(quán)或者永久拒絕
        //.permission(Permission.REQUEST_INSTALL_PACKAGES, Permission.SYSTEM_ALERT_WINDOW) //支持請(qǐng)求安裝權(quán)限和懸浮窗權(quán)限
        .permission(Permission.Group.STORAGE) //支持多個(gè)權(quán)限組進(jìn)行請(qǐng)求,不指定則默以清單文件中的危險(xiǎn)權(quán)限進(jìn)行請(qǐng)求
        .request(new OnPermission() {

          
                    @Override
                    public void hasPermission(List<String> granted, boolean isAll) {
                        if (isAll) {
                            Toast.makeText(MainActivity.this, "獲取權(quán)限成功", Toast.LENGTH_SHORT).show();
                        }else {
                            Toast.makeText(MainActivity.this, "獲取權(quán)限成功,部分權(quán)限未正常授予", Toast.LENGTH_SHORT).show();
                        }
                    }

                    @Override
                    public void noPermission(List<String> denied, boolean quick) {
                        if(quick) {
                            Toast.makeText(MainActivity.this, "被永久拒絕授權(quán),請(qǐng)手動(dòng)授予權(quán)限", Toast.LENGTH_SHORT).show();
                            //如果是被永久拒絕就跳轉(zhuǎn)到應(yīng)用權(quán)限系統(tǒng)設(shè)置頁(yè)面
                            XXPermissions.gotoPermissionSettings(MainActivity.this);
                        }else {
                            Toast.makeText(MainActivity.this, "獲取權(quán)限失敗", Toast.LENGTH_SHORT).show();
                        }
                    }
        });

參考文獻(xiàn):

https://github.com/googlesamples/easypermissions

https://github.com/hotchemi/PermissionsDispatcher
http://hotchemi.github.io/PermissionsDispatcher/

https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en

https://developer.android.com/guide/topics/security/permissions.html#normal-dangerous
系統(tǒng)所需要的權(quán)限
https://developer.android.com/reference/android/Manifest.permission.html
普通權(quán)限
https://developer.android.com/guide/topics/permissions/normal-permissions.html
危險(xiǎn)權(quán)限
https://developer.android.com/guide/topics/security/permissions.html#perm-groups
在運(yùn)行時(shí)請(qǐng)求權(quán)限
https://developer.android.com/training/permissions/requesting.html

解讀Android官方開發(fā)指導(dǎo) - 運(yùn)行時(shí)權(quán)限
http://www.itdecent.cn/p/0beb6243d650

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,111評(píng)論 25 709
  • 目前搭載android6.0以上版本系統(tǒng)手機(jī)占有量將近三分之一,具體如下圖所示: 所以有必要將升級(jí)app的targ...
    flywfk閱讀 3,214評(píng)論 0 4
  • 1. Android 6.0 在運(yùn)行時(shí)請(qǐng)求權(quán)限介紹 從 Android 6.0(API 級(jí)別 23)開始,用戶開始...
    conio閱讀 4,368評(píng)論 0 6
  • 許久沒(méi)有吃我喜歡的串串啦!甚是想念! 工具:普通彩色鉛筆,網(wǎng)格本,黑水筆。 簡(jiǎn)單易學(xué)畫起來(lái)啦! 只需要一個(gè)小小本,...
    影子倒了閱讀 689評(píng)論 3 6
  • 女人最重要的是什么呢,姣好的面容,豐滿性感的身材,還是鍍金的好學(xué)歷? 學(xué)生時(shí)代羨慕那些貌美好身材的姑娘,長(zhǎng)的漂亮?xí)?..
    星光漫漫閱讀 304評(píng)論 0 0

友情鏈接更多精彩內(nèi)容