1. 程式人生 > >安卓6.0許可權處理在專案中的實踐

安卓6.0許可權處理在專案中的實踐

前言

最近公司的app說裝在安卓6.0的系統上程式直接崩潰了,然後呢crash日 志也沒有捕獲到,感覺好煩人因為公司壓根就沒有安卓6.0的測試機,最後呢我還是用genymotion來搞,由於用到了so庫以前下載的那個jar也不知怎的5.0就執行不通過了,然後今天那到處弄Genymotion-ARM-Translation.zip,最後終於還是可以了。

由於公司是使用eclipse開發的也是醉了,然後呢網上對安卓6.0的許可權有了還幾個開源框架

相關開源專案

使用eclipse和grant開源庫許可權管理

1. 更新api到23

這裡我們選擇使用grant來進行專案的演示,因為是使用eclipse來開發,所以還是和android studio裡面的還是有很多的區別,我們genymotion的6.0開啟 ,因為是6.0系統的開發,所以呢我們的api的target 必須是23,如果不是呢那就麻煩你去update下。

2. 獲得v4包

不多扯了,去你的sdk目錄下拷貝出v4包放在你工程目錄的libs下,不要問為什麼因為在接下來的 專案裡面需要用到。如下圖目錄圖:
v4.png

當然我們選擇23.1.1下面的android-support-v4.jar,放入你專案的libs下面:
哈哈為什麼要這樣幹呢?因為在裡面我們會用到
這樣做的話,程式碼會比較複雜,而在support library v4中,已經為我們準備好了更簡單的方法,我們只需要採用library包中的方法來替換上面的方法即可:
- ContextCompat.checkSelfPermission()
不管當前執行的Android版本是多少,該方法都可以正確的方法許可權的許可情況,允許或者拒絕.
- ActivityCompat.requestPermissions()
當該方法在6.0之前被呼叫時,onRequestPermissionsResult回撥方法會立即被呼叫,並返回給你正確的PERMISSION_GRANTED 或者
PERMISSION_DENIED結果.

  • ActivityCompat.shouldShowRequestPermissionRationale()

在6.0之前呼叫該方法,將總會返回false.

切記,你應該總是使用這些方法來取代Activity自身的checkSelfPermission,requestPermissions和shouldShowRequestPermissionsRationale方法.這樣,在不同的Android版本中,你的程式碼將總會完美的執行.

ContextCompat.checkSelfPermission();
ActivityCompat.requestPermissions();

上面的方法是v4包下的方法,然後呢低版本的v4包還不具有這樣的方法,所以as的專案放在eclipse也是相當的蛋疼。
接下我們就實戰專案了,

3. 專案實戰開發

不多扯,來看程式碼:

package com.richerpay.ryshop.permissions;

/**
 * Enum class to handle the different states
 * of permissions since the PackageManager only
 * has a granted and denied state.
 */
enum Permissions {
    GRANTED,
    DENIED,
    NOT_FOUND
}

上面呢就 是許可權的型別了,簡單翻譯下注釋就是 用列舉對應3種狀態:
已授權,授權失敗,未發現的許可權。
再看下我們的PermissionsManager.java

package com.richerpay.ryshop.permissions;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.util.Log;

import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * 許可權管理類 
 */
public class PermissionsManager {

    private static final String TAG = PermissionsManager.class.getSimpleName();

    private final Set<String> mPendingRequests = new HashSet<String>(1);
    private final Set<String> mPermissions = new HashSet<String>(1);
    private final List<WeakReference<PermissionsResultAction>> mPendingActions = new ArrayList<WeakReference<PermissionsResultAction>>(1);

    private static PermissionsManager mInstance = null;

    public static PermissionsManager getInstance() {
        if (mInstance == null) {
            mInstance = new PermissionsManager();
        }
        return mInstance;
    }

    private PermissionsManager() {
        initializePermissionsMap();
    }

    /**
     * 此方法使用反射來讀取清單類中的所有許可權。
     *因為一些許可權不存在於舊版本的安卓系統中,
     *因為他們不存在,他們將被拒絕時,你檢查是否有許可權
     *因為一個新的許可權,往往是補充,那裡沒有以前的
     *所需的許可。我們初始化一組可用的許可權,並檢查組
     *檢查是否有許可權,當我們被拒絕時許可權仍然不存在
     * 
     */
    private synchronized void initializePermissionsMap() {
        Field[] fields = Manifest.permission.class.getFields();
        for (Field field : fields) {
            String name = null;
            try {
                name = (String) field.get("");
            } catch (IllegalAccessException e) {
                Log.e(TAG, "Could not access field", e);
            }
            mPermissions.add(name);
        }
    }

    /**
     * 此方法檢索在應用程式清單中宣告的所有許可權。
     * 它返回一個非空陣列,可以宣告的許可權。
     *
     * @param  檢查我們需要哪些許可權
     * @return 返回在應用程式清單中宣告的非空陣列
     */
    @NonNull
    private synchronized String[] getManifestPermissions(@NonNull final Activity activity) {
        PackageInfo packageInfo = null;
        List<String> list = new ArrayList<String>(1);
        try {
            Log.d(TAG, activity.getPackageName());
            packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "A problem occurred when retrieving permissions", e);
        }
        if (packageInfo != null) {
            String[] permissions = packageInfo.requestedPermissions;
            if (permissions != null) {
                for (String perm : permissions) {
                    Log.d(TAG, "Manifest contained permission: " + perm);
                    list.add(perm);
                }
            }
        }
        return list.toArray(new String[list.size()]);
    }

    /**
     * 在permissionsresultaction物件,它將變更通知這些許可權
     * @param 許可權所需的操作的許可權。
     * @param 將 動作新增到當前正在執行的動作列表中。
     */
    private synchronized void addPendingAction(@NonNull String[] permissions, @Nullable PermissionsResultAction action) {
        if (action == null) {
            return;
        }
        action.registerPermissions(permissions);
        mPendingActions.add(new WeakReference<PermissionsResultAction>(action));
    }

    /**
     * 刪除從佇列中等待的動作和執行該動作
     * @param 移除動作
     */
    private synchronized void removePendingAction(@Nullable PermissionsResultAction action) {
        for (Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
             iterator.hasNext(); ) {
            WeakReference<PermissionsResultAction> weakRef = iterator.next();
            if (weakRef.get() == action || weakRef.get() == null) {
                iterator.remove();
            }
        }
    }

    /**
     *這個靜態方法可以用來檢查你是否有一個特定的許可權。
     *
     * @param 檢查許可權的上下文物件
     * @param 要檢查的許可權
     * @return 返回是否授權了此許可權
     */
    public synchronized boolean hasPermission(@Nullable Context context, @NonNull String permission) {
        return context != null && (ActivityCompat.checkSelfPermission(context, permission)
                == PackageManager.PERMISSION_GRANTED || !mPermissions.contains(permission));
    }

    /**
     * 這個靜態方法可以用來檢查你是否有幾個特定的許可權。
     * 為每一個許可權,並將簡單地返回一個布林值是否你有所有的許可權
     * @param 需要檢查 許可權的上下文
     * @param 許可權 陣列
     * @return 返回你是否有所有的許可權
     */
    public synchronized boolean hasAllPermissions(@Nullable Context context, @NonNull String[] permissions) {
        if (context == null) {
            return false;
        }
        boolean hasAllPermissions = true;
        for (String perm : permissions) {
            hasAllPermissions &= hasPermission(context, perm);
        }
        return hasAllPermissions;
    }

    /**
    *這種方法的是獲取清單裡面的所有許可權。permissionsresultaction  用於通知允許使用者允許或拒絕每一個許可權。
     *
     * @param 需要檢查許可權的activity
     * @param permissionsresultaction用於許可權接受通知你
     */
    public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullable Activity activity,
                                                                      final @Nullable PermissionsResultAction action) {
        if (activity == null) {
            return;
        }
        String[] perms = getManifestPermissions(activity);
        requestPermissionsIfNecessaryForResult(activity, perms, action);
    }

    /**
     *該方法將請求的許可權,如果
     *他們需要被要求(即我們沒有許可),並會增加
     * permissionsresultaction到佇列被通知的許可權被授予或
     *否認。在預Android棉花糖的情況下,將立即授予許可權。
     *活動變數為空,但如果它是無效的,不能執行的方法。
     *這是唯一可作為一種禮貌片段,getactivity()可能產量空
     *如果該片段沒有新增到其父活動中
     * @param 請求許可權的活動
     * @param 許可權列表
     * @param permissionsresultaction通知當權限授予或拒絕。
     */
    public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity,
                                                                    @NonNull String[] permissions,
                                                                    @Nullable PermissionsResultAction action) {
        if (activity == null) {
            return;
        }
        addPendingAction(permissions, action);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            doPermissionWorkBeforeAndroidM(activity, permissions, action);
        } else {
            List<String> permList = getPermissionsListToRequest(activity, permissions, action);
            if (permList.isEmpty()) {
                //if there is no permission to request, there is no reason to keep the action int the list
                removePendingAction(action);
            } else {
                String[] permsToRequest = permList.toArray(new String[permList.size()]);
                mPendingRequests.addAll(permList);
                ActivityCompat.requestPermissions(activity, permsToRequest, 1);
            }
        }
    }

    /**
     * 該方法將請求的許可權,如果
     *他們需要被要求(即我們沒有許可),並會增加
     * permissionsresultaction到佇列被通知的許可權被授予或
     *否認。在預Android棉花糖(6.0)的情況下,將立即授予許可權。
     *但如果 getactivity()返回null,這方法
     *將無法工作作為活動引用,必須檢查許可權。
     * @param 需要檢查 許可權的fragmnet
     * @param 需要請求的許可權列表
     * @param permissionsresultaction通知當權限授予或拒絕。  
     */
    public synchronized void requestPermissionsIfNecessaryForResult(@NonNull Fragment fragment,@NonNull String[] permissions,
@Nullable PermissionsResultAction action) {
        Activity activity = fragment.getActivity();
        if (activity == null) {
            return;
        }
        addPendingAction(permissions, action);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            doPermissionWorkBeforeAndroidM(activity, permissions, action);
        } else {
            List<String> permList = getPermissionsListToRequest(activity, permissions, action);
            if (permList.isEmpty()) {
                //if there is no permission to request, there is no reason to keep the action int the list
                removePendingAction(action);
            } else {
                String[] permsToRequest = permList.toArray(new String[permList.size()]);
                mPendingRequests.addAll(permList);
                fragment.requestPermissions(permsToRequest, 1);
            }
        }
    }

    /**
     * 這個方法通知permissionsmanager,許可權已經改變。如果你正在做
     *使用活動的許可權請求,則應呼叫此方法,將通知所有懸而未決  的,permissionsresultaction當前物件 
     *在佇列中,並將從掛起的請求列表中刪除許可權請求。
     * @param  已更改的許可權。
     * @param  每個許可權的值
     */
    public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) {
        int size = permissions.length;
        if (results.length < size) {
            size = results.length;
        }
        Iterator<WeakReference<PermissionsResultAction>> iterator = mPendingActions.iterator();
        while (iterator.hasNext()) {
            PermissionsResultAction action = iterator.next().get();
            for (int n = 0; n < size; n++) {
                if (action == null || action.onResult(permissions[n], results[n])) {
                    iterator.remove();
                    break;
                }
            }
        }
        for (int n = 0; n < size; n++) {
            mPendingRequests.remove(permissions[n]);
        }
    }

    /**
     * 在安卓裝置前要求許可權(安卓6,api23),根據許可權狀態,直接進行或拒絕工作
     * @param 檢測許可權的activity
     * @param 許可權陣列
     * @param 我們執行某項操作後許可權檢查   
     */
    private void doPermissionWorkBeforeAndroidM(@NonNull Activity activity, @NonNull String[] permissions, @Nullable PermissionsResultAction action) {
        for (String perm : permissions) {
            if (action != null) {
                if (!mPermissions.contains(perm)) {
                    action.onResult(perm, Permissions.NOT_FOUND);
                } else if (ActivityCompat.checkSelfPermission(activity, perm)
                        != PackageManager.PERMISSION_GRANTED) {
                    action.onResult(perm, Permissions.DENIED);
                } else {
                    action.onResult(perm, Permissions.GRANTED);
                }
            }
        }
    }

    /**
     * @param 檢查許可權的activity
     * @param 所有許可權的名字
     * @param 我們執行某項操作後許可權檢查
     * @return 尚未授予的許可權名稱列表
     */
    @NonNull
    private List<String> getPermissionsListToRequest(@NonNull Activity activity,
                                                   @NonNull String[] permissions,@Nullable PermissionsResultAction action) {
        List<String> permList = new ArrayList<String>(permissions.length);
        for (String perm : permissions) {
            if (!mPermissions.contains(perm)) {
                if (action != null) {
                    action.onResult(perm, Permissions.NOT_FOUND);
                }
            } else if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) {
                if (!mPendingRequests.contains(perm)) {
                    permList.add(perm);
                }
            } else {
                if (action != null) {
                   action.onResult(perm,Permissions.GRANTED);
                }
            }
        }
        return permList;
    }

}

接下來我們來看PermissionsResultAction.java

package com.xxxx.xxxx.permissions;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.util.Log;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 *將例項傳遞給 requestpermissionsifnecessaryforresult方法。結  *果將被送回你無論是ongranted(所有的許可權已被授予),或ondenied(所需*的許可權被拒絕)。
*你把你的ongranted方法和通知功能,使用者什麼都在ondenied方法不工作。
 */
public abstract class PermissionsResultAction {

    private static final String TAG = PermissionsResultAction.class.getSimpleName();
    private final Set<String> mPermissions = new HashSet<String>(1);
    private Looper mLooper = Looper.getMainLooper();

    /**
     * Default Constructor
     */
    public PermissionsResultAction() {}

    /**
     *如果您是在後臺執行緒中使用許可權請求,則希望
     *回撥是在UI執行緒中,通過looper重新整理ui
     */
    public PermissionsResultAction(@NonNull Looper looper) {mLooper = looper;}

    /**
     *當所有許可權已被使用者授予,把所有你的許可權敏感的程式碼,可以只需執行所                        需許可權
     */
    public abstract void onGranted();

    /**
     *當一個許可權被拒絕的時候呼叫
     *
     * @param 被拒絕的許可權
     */
    public abstract void onDenied(String permission);

    /**
     * 忽視未發現的許可權
     */
    @SuppressWarnings({})
    public synchronized boolean shouldIgnorePermissionNotFound(String permission) {
        Log.d(TAG, "Permission not found: " + permission);
        return true;
    }
     /*
     *返回授權 結果
     */
    @CallSuper
    protected synchronized final boolean onResult(final @NonNull String permission, int result) {
        if (result == PackageManager.PERMISSION_GRANTED) {
            return onResult(permission, Permissions.GRANTED);
        } else {
            return onResult(permission, Permissions.DENIED);
        }

    }

    /**
     *當一個特定的許可權已更改。
     *此方法將呼叫所有許可權,所以該方法確定
     *如果授權影響到該狀態,是否可以繼續進行
     */
    @CallSuper
    protected synchronized final boolean onResult(final @NonNull String permission, Permissions result) {
         //先從許可權列表裡移除當前許可權
        mPermissions.remove(permission);
        if (result == Permissions.GRANTED) {
            if (mPermissions.isEmpty()) {
                new Handler(mLooper).post(new Runnable() {
                    @Override
                    public void run() {
                        onGranted();
                    }
                });
                return true;
            }
        } else if (result == Permissions.DENIED) {//許可權被拒
            new Handler(mLooper).post(new Runnable() {
                @Override
                public void run() {
                    onDenied(permission);
                }
            });
            return true;
        } else if (result == Permissions.NOT_FOUND) {
            if (shouldIgnorePermissionNotFound(permission)) {
                if (mPermissions.isEmpty()) {//許可權為空
                    new Handler(mLooper).post(new Runnable() {
                        @Override
                        public void run() {
                            onGranted();//去授權
                        }
                    });
                    return true;
                }
            } else {
                new Handler(mLooper).post(new Runnable() {
                    @Override
                    public void run() {
                         //拒絕許可權
                        onDenied(permission);
                    }
                });
                return true;
            }
        }
        return false;
    }

    /**
     *註冊指定的許可權物件的permissionsresultaction
     *讓它知道哪些許可權來查詢更改。
     *
     * @param permissions名單
     */
    @CallSuper
    protected synchronized final void registerPermissions(@NonNull String[] perms) {
        //把名單加到許可權集合裡
        Collections.addAll(mPermissions, perms);
    }
}

好了,最後我們就來使用許可權管理:

// 先對app獲取所有需要的授權(6.0需要去獲取許可權)
grantPermissions();

/***
* 獲取清單檔案中的所有許可權
*/
private void grantPermissions() {
@Override                       public void onGranted() {                                   
    Toast.makeText(MainActivity.this,                                         
    Toast.LENGTH_SHORT).show();
}   
@Override                       public void onDenied(String permission) {                            
String message = String.format(                                       
Locale.getDefault(),"Permission \"%1$s\" has been denied",                                   permission);                          Toast.makeText(MainActivity.this, message,                                   
        Toast.LENGTH_SHORT).show();
}});
}

上面呢 我們在程式一開始呢就去授權所有的清單裡面的許可權,你會看到和ios一樣的許可權dialog提示,這裡呢我們直接來圖片展示下。
如下圖所示:
grant.gif

接下來我們要去通知許可權授權的改變

@Override
    public void onRequestPermissionsResult(intrequestCode,@NonNull String[] permissions, @NonNull int[]   grantResults) {
    PermissionsManager.getInstance().notifyPermissionsChange(permissions,grantResults);
    }

這裡呢我們以百度定位的例子來使用:

        /**
     * 定位資訊
     */
    private void initLocation() {
        LocationClientOption option = new LocationClientOption();
        // 可選,預設高精度,設定定位模式,高精度(LocationMode.Hight_Accuracy),
        // 低功耗(LocationMode.Battery_Saving),
        // 僅裝置(LocationMode.Device_Sensors)
        option.setLocationMode(LocationMode.Hight_Accuracy);
        option.setCoorType("bd09ll");// 可選,預設gcj02,設定返回的定位結果座標系,
                                        // tempcoor="gcj02";//國家測繪局標準"bd09"//百度墨卡託標準"bd09ll"//百度經緯度標準
        // int span=1000;
        option.setScanSpan(0);// 可選,預設0,即僅定位一次,設定發起定位請求的間隔需要大於等於1000ms才是有效的
        option.setIsNeedAddress(true);// 可選,設定是否需要地址資訊,預設不需要
        option.setOpenGps(true);// 可選,預設false,設定是否使用gps
        option.setLocationNotify(true);// 可選,預設false,設定是否當gps有效時按照1S1次頻率輸出GPS結果
        option.setIgnoreKillProcess(false);// 可選,預設true,定位SDK內部是一個SERVICE,並放到了獨立程序,設定是否在stop的時候殺死這個程序,預設不殺死
        mLocationClient.setLocOption(option);
    }

/**
*頁面 停止狀態下關閉定位
*/
@Override
    protected void onStop() {
        if (mLocationClient != null) {
            mLocationClient.stop();// 介面消失後取消定位
            mLocationClient = null;
        }

        super.onStop();
    }

我們先來判斷定位許可權是否已授權成功:

// 判斷是否授權定位許可權
        boolean hasPermission = PermissionsManager.getInstance().hasPermission(
                this, Manifest.permission.ACCESS_FINE_LOCATION);

接下來我們根據是否授權成功來進行相關的操作:

    if (hasPermission == false) {//沒有授權成功
            grantLoactionPermissons();//去授權
        }else{//授權成功去定位
            mLocationClient = ((RYApplication) getApplication()).mLocationClient;
            initLocation();// 定位
            mLocationClient.start();// 定位SDK
            // start之後會預設發起一次定位請求,開發者無須判斷isstart並主動呼叫request
            mLocationClient.requestLocation();
        }

根據定位需要的許可權去授權

/**
     * 授權定位許可權
     */
    private void grantLoactionPermissons() {  PermissionsManager.getInstance().requestPermissionsIfNeces         saryForResult(this,new String[] {
  Manifest.permission.ACCESS_COARSE_LOCATION,                
  Manifest.permission.ACCESS_FINE_LOCATION,              
  Manifest.permission.WRITE_EXTERNAL_STORAGE },new PermissionsResultAction() {                      @Override                       public void onGranted() {                        
      mLocationClient = ((RYApplication)    getApplication()).mLocationClient;                           
       initLocation();// 定位                                
       mLocationClient.start();// 定位SDK                          // start之後會預設發起一次定位請求,開發者無須判斷isstart並主動呼叫 
       requestmLocationClient.requestLocation();
    }
@Override                        
public void onDenied(String permission) {

    }
    });
    }

當然還有一些許可權你無須去判斷:

預設許可許可權(無需授權)

還有一些許可權是在安裝的時候預設許可並且不可撤銷的,我們把它們叫做普通許可權,這些許可權如下:

android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
這些許可權和以前一樣使用,你無需檢查它們是否被撤銷了

總結和建議

現在你應該很清楚整個新的許可權系統了,同時你也應該知道我們面臨的問題了.在Android6.0中動態許可權系統已經取代了原有的許可權系統,我們別無選擇,退無可退,唯一能做的就是去做好適配工作.
好訊息是需要進行動態申請的許可權是少量的,大部分常用的許可權是預設獲得許可,並不需要你處理的.總而言之,你只需要修改一小部分程式碼來完成適配工作.
在這裡給出兩個建議:
1.優先對重要的部分進行適配,確保不會出現崩潰問題.
2.在完成適配工作前,不要將應用的targetSdkVersion設為23,尤其是在使用AndroidStudio建立新工程的時候,記得手動修改一下工程的targetSdkVersion,因為AndroidStudio會預設使用最新的SDK版本.

說到修改程式碼,我必須承認工作量很大.如果之前的架構設計不合理的話,你可能需要花費一些時間來重新設計了,就像之前說的,我們別無選擇,除了去做好它.

我建議你列出應用的功能所依賴的所有的許可權,然後考慮如果許可權被拒絕,怎樣使你的應用的功能儘可能可用,並考慮好如果部分許可權被拒絕的情況下,你應該怎麼做.恩,這也很複雜,最好能記錄好各種情況.

好累啊,一不小心到12點,睡覺,加油吧。第一次使用markDown感覺好辛苦,寫個東西都