Android targetSdkVersion 從22提到25 你需要知道的一切

本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家發布

Android 6.0

  • 運行時權限

相機,圖庫,下載,語音,定位....
此版本引入了一種新的權限模式,如今,用戶可直接在運行時管理應用權限。這種模式讓用戶能夠更好地了解和控制權限,同時為應用開發者精簡了安裝和自動更新過程。用戶可為所安裝的各個應用分別授予或撤銷權限。
對于以 Android 6.0(API 級別 23)或更高版本為目標平臺的應用,請務必在運行時檢查和請求權限。要確定您的應用是否已被授予權限,請調用新增的 checkSelfPermission()方法。要請求權限,請調用新增的requestPermissions() 方法。即使您的應用并不以 Android 6.0(API 級別 23)為目標平臺,您也應該在新權限模式下測試您的應用。如需了解有關在您的應用中支持新權限模式的詳情,請參閱使用系統權限。如需了解有關如何評估新模式對應用的影響的提示,請參閱權限最佳做法
權限管理工具類

package cn.loveshow.live.util;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;

import java.util.ArrayList;
import java.util.Arrays;


/**
 * Created by Fungo_Xiaoke on 2017/5/4 14:18.
 * eamil:luoxiaoke@yuntutv.net
 * 權限工具類
 */
public class PermissionUtils {


/**
 * @param context     上下文
 * @param activity    activity
 * @param permissions 權限數組
 * @param requestCode 申請碼
 * @return true 有權限  false 無權限
 */
public static boolean checkAndApplyfPermissionActivity(Activity activity, String[] permissions, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        permissions = checkPermissions(activity, permissions);
        if (permissions != null && permissions.length > 0) {
            ActivityCompat.requestPermissions(activity, permissions, requestCode);
            return false;
        } else {
            return true;
        }
    } else {
        return true;
    }
}

/**
 * @param context     上下文
 * @param mFragment   fragment
 * @param permissions 權限數組
 * @param requestCode 申請碼
 * @return true 有權限  false 無權限
 */
public static boolean checkAndApplyfPermissionFragment( Fragment mFragment, String[] permissions, int requestCode) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        permissions = checkPermissions(mFragment.getActivity(), permissions);
        if (permissions != null && permissions.length > 0) {
            if (mFragment.getActivity() != null) {
                mFragment.requestPermissions(permissions, requestCode);
            }
            return false;
        } else {
            return true;
        }
    } else {
        return true;
    }
}

/**
 * @param context     上下文
 * @param permissions 權限數組
 * @return 還需要申請的權限
 */
private static String[] checkPermissions(Context context, String[] permissions) {
    if (permissions == null || permissions.length == 0) {
        return new String[0];
    }
    ArrayList<String> permissionLists = new ArrayList<>();
    permissionLists.addAll(Arrays.asList(permissions));
    for (int i = permissionLists.size() - 1; i >= 0; i--) {
        if (ContextCompat.checkSelfPermission(context, permissionLists.get(i)) == PackageManager.PERMISSION_GRANTED) {
            permissionLists.remove(i);
        }
    }

    String[] temps = new String[permissionLists.size()];
    for (int i = 0; i < permissionLists.size(); i++) {
        temps[i] = permissionLists.get(i);
    }
    return temps;
    }


    /**
     * 檢查申請的權限是否全部允許
     */
    public static boolean checkPermission(int[] grantResults) {
        if (grantResults == null || grantResults.length == 0) {
            return true;
        } else {
            int temp = 0;
            for (int i : grantResults) {
                if (i == PackageManager.PERMISSION_GRANTED) {
                    temp++;
                }
            }
            return temp == grantResults.length;
        }
    }

/**
 * 沒有獲取到權限的提示
 *
 * @param permissions 權限名字數組
 */
public static void showPermissionsToast(Activity activity, @NonNull String[] permissions) {
    if (permissions.length > 0) {
        for (String permission : permissions) {
            showPermissionToast(activity, permission);
        }
     }
   }

/**
 * 沒有獲取到權限的提示
 *
 * @param permission 權限名字
 */
private static void showPermissionToast(Activity activity, @NonNull String permission) {
    if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
        //用戶勾選了不再詢問,提示用戶手動打開權限
        switch (permission) {
            case Manifest.permission.CAMERA:
                ToastUtils.showShort("相機權限已被禁止,請在應用管理中打開權限");
                break;
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                ToastUtils.showShort("文件權限已被禁止,請在應用管理中打開權限");
                break;
            case Manifest.permission.RECORD_AUDIO:
                ToastUtils.showShort("錄制音頻權限已被禁止,請在應用管理中打開權限");
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                ToastUtils.showShort("位置權限已被禁止,請在應用管理中打開權限");
                break;
        }
    } else {
        //用戶沒有勾選了不再詢問,拒絕了權限申請
        switch (permission) {
            case Manifest.permission.CAMERA:
                ToastUtils.showShort("沒有相機權限");
                break;
            case Manifest.permission.WRITE_EXTERNAL_STORAGE:
                ToastUtils.showShort("沒有文件讀取權限");
                break;
            case Manifest.permission.RECORD_AUDIO:
                ToastUtils.showShort("沒有錄制音頻權限");
                break;
            case Manifest.permission.ACCESS_FINE_LOCATION:
                ToastUtils.showShort("沒有位置權限");
                break;
          }
     }
  }
}

用法

   if (PermissionUtils.checkAndApplyfPermissionActivity(this,
        new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 
        REQUESRCARMEA)) {
       //獲取到權限的操作  沒有權限會申請權限  然后在onRequestPermissionsResult處理申請的結果
   }

   @Override
   public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
       super.onRequestPermissionsResult(requestCode, permissions, grantResults);
       if (PermissionUtils.checkPermission(grantResults)) {
           //申請權限成功
            switch (requestCode) {
                 case 0x001:
                     //dosomething
                     break;
                 case 0x002:
                    //dosomething
                     break;
                 ...
            }
       } else {
           //提示沒有什么權限
           PermissionUtils.showPermissionsToast(activity, permissions);
            //or 去權限管理界面
            //gotoPermissionManager(mContext);
       }
   }

沒有權限去權限管理界面

/**
 * 去應用權限管理界面
 */
public static void gotoPermissionManager(Context context) {
    Intent intent;
    ComponentName comp;
    //防止刷機出現的問題
    try {
        switch (Build.MANUFACTURER) {
            case "Huawei":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "Meizu":
                intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                context.startActivity(intent);
                break;
            case "Xiaomi":
                String rom = getSystemProperty("ro.miui.ui.version.name");
                if ("v5".equals(rom)) {
                    Uri packageURI = Uri.parse("package:" + context.getApplicationInfo().packageName);
                    intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                } else {//if ("v6".equals(rom) || "v7".equals(rom)) {
                    intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
                    intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
                    intent.putExtra("extra_pkgname", context.getPackageName());
                }
                context.startActivity(intent);
                break;
            case "Sony":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "OPPO":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "LG":
                intent = new Intent("android.intent.action.MAIN");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            case "Letv":
                intent = new Intent();
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
                comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps");
                intent.setComponent(comp);
                context.startActivity(intent);
                break;
            default:
                getAppDetailSettingIntent(context);
                break;
        }
    } catch (Exception e) {
        getAppDetailSettingIntent(context);
    }
}

/**
 * 獲取系統屬性值
 */
public static String getSystemProperty(String propName) {
    String line;
    BufferedReader input = null;
    try {
        Process p = Runtime.getRuntime().exec("getprop " + propName);
        input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
        line = input.readLine();
        input.close();
    } catch (IOException ex) {
        Log.e(TAG, "Unable to read sysprop " + propName, ex);
        return null;
    } finally {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                Log.e(TAG, "Exception while closing InputStream", e);
            }
        }
    }
    return line;
}

  //以下代碼可以跳轉到應用詳情,可以通過應用詳情跳轉到權限界面(6.0系統測試可用)
 public static void getAppDetailSettingIntent(Context context) {
    Intent localIntent = new Intent();
    localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (Build.VERSION.SDK_INT >= 9) {
        localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        localIntent.setData(Uri.fromParts("package", context.getPackageName(), null));
    } else if (Build.VERSION.SDK_INT <= 8) {
        localIntent.setAction(Intent.ACTION_VIEW);
        localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
        localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
    }
    launchApp(context, localIntent);
}

  /**
 * 安全的啟動APP
 */
public static boolean launchApp(Context ctx, Intent intent) {
    if (ctx == null)
        throw new NullPointerException("ctx is null");
    try {
        ctx.startActivity(intent);
        return true;
    } catch (ActivityNotFoundException e) {
        Logger.e(e);
        return false;
    }
}
  • 取消支持 Apache HTTP 客戶端

Android 6.0 版移除了對 Apache HTTP 客戶端的支持。如果您的應用使用該客戶端,并以 Android 2.3(API 級別 9)或更高版本為目標平臺,請改用HttpURLConnection 類。此 API 效率更高,因為它可以通過透明壓縮和響應緩存減少網絡使用,并可最大限度降低耗電量。要繼續使用 Apache HTTP API,您必須先在 build.gradle 文件中聲明以下編譯時依賴項:

  android {
      useLibrary 'org.apache.http.legacy'
  }
  • BoringSSL

Android 正在從使用 OpenSSL 庫轉向使用 BoringSSL 庫。如果您要在應用中使用 Android NDK,請勿鏈接到并非 NDK API 組成部分的加密庫,如libcrypto.so和 libssl.so。這些庫并非公共 API,可能會在不同版本和設備上毫無征兆地發生變化或出現故障。此外,您還可能讓自己暴露在安全漏洞的風險之下。請改為修改原生代碼,以通過 JNI 調用 Java 加密 API,或靜態鏈接到您選擇的加密庫。

bugly錯誤
bugly錯誤
  • 通知

此版本移除了 Notification.setLatestEventInfo()方法。請改用 Notification.Builder 類來構建通知。要重復更新通知,請重復使用Notification.Builder 實例。調用 build() 方法可獲取更新后的 Notification 實例。
adb shell dumpsys notification 命令不再打印輸出您的通知文本。請改用 adb shell dumpsys notification --noredact 命令打印輸出 notification 對象中的文本。build()方法在4.1以上(16+)的系統才能用。

notification
例子
gif
  • 音頻管理器變更

不再支持通過 AudioManager 類直接設置音量或將特定音頻流 靜音。[setStreamSolo()](https://developer.android.google.cn/reference/android/media/AudioManager.html#setStreamSolo(int, boolean)) 方法已棄用,您應該改為調用 [requestAudioFocus()](https://developer.android.google.cn/reference/android/media/AudioManager.html#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int)) 方法。類似地,[setStreamMute()](https://developer.android.google.cn/reference/android/media/AudioManager.html#setStreamMute(int, boolean)) 方法也已棄用,請改為調用 [adjustStreamVolume()](https://developer.android.google.cn/reference/android/media/AudioManager.html#adjustStreamVolume(int, int, int)) 方法并傳入方向值 ADJUST_MUTEADJUST_UNMUTE

  • Android 密鑰庫變更

從此版本開始,Android 密鑰庫提供程序不再支持 DSA。但仍支持 ECDSA。
停用或重置安全鎖定屏幕時(例如,由用戶或設備管理員執行此類操作時),系統將不再刪除需要閑時加密的密鑰,但在上述事件期間會刪除需要閑時加密的密鑰。

  • APK 驗證

該平臺現在執行的 APK 驗證更為嚴格。如果在清單中聲明的文件在 APK 中并不存在,該 APK 將被視為已損壞。移除任何內容后必須重新簽署 APK。

http://www.lxweimin.com/p/95790125b7f4
http://blog.csdn.net/lxk_1993/article/details/73784883

Android7.0

  • 系統權限更改

為了提高私有文件的安全性,面向 Android 7.0 或更高版本的應用私有目錄被限制訪問 (0700)。此設置可防止私有文件的元數據泄漏,如它們的大小或存在性。此權限更改有多重副作用:

  • 在應用間共享文件

對于面向 Android 7.0 的應用,Android 框架執行的 StrictModeAPI 政策禁止在您的應用外部公開 file://URI。如果一項包含文件 URI 的 intent 離開您的應用,則應用出現故障,并現 FileUriExposedException。異常。要在應用間共享文件,您應發送一項 content://URI,并授予 URI 臨時訪問權限。進行此授權的最簡單方式是使用FileProvider類。如需了解有關權限和共享文件的詳細信息,請參閱共享文件

  • FileProvider用法

AndroidManiFest.xml添加

  <application>
  ...
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.fileProvider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
  ...
  </application>
配置applicationId

res目錄下新建xml文件夾,創建provider_paths.xml文件

  <?xml version="1.0" encoding="utf-8"?>
  <resources>
    <paths>
    <!--  前面兩個是bugly的 -->
    <!-- /storage/emulated/0/Download/${applicationId}/.beta/apk-->
    <external-path
        name="beta_external_path"
        path="Download/" />
    <!--/storage/emulated/0/Android/data/${applicationId}/files/apk/-->
    <external-path
        name="beta_external_files_path"
        path="Android/data/" />

    <external-path
        name="sdcard_files"
        path="" />
    <!--相機相冊裁剪-->
    <external-files-path
        name="camera_has_sdcard"
        path="" />
    <files-path
        name="camera_no_sdcard"
        path="" />
    </paths>
    <!--<paths>-->
    <!-- xml文件是唯一設置分享的目錄 ,不能用代碼設置-->
     <!--1.<files-path>        getFilesDir()  /data/data//files目錄-->
     <!--2.<cache-path>        getCacheDir()  /data/data//cache目錄-->
     <!--3.<external-path>       Environment.getExternalStorageDirectory()  -->
     <!--4.<external-files-path>    
       Context.getExternalFilesDir(String)  Context.getExternalFilesDir(null)  
       == SDCard/Android/data/你的應用的包名/files/ 目錄-->
     <!--5.<external-cache-path>      Context.getExternalCacheDir().-->

     <!--  path :代表設置的目錄下一級目錄 eg:<external-path path="images/"-->
     <!--整個目錄為Environment.getExternalStorageDirectory()+"/images/"-->
     <!--name: 代表定義在Content中的字段 eg:name = "myimages" ,并且請求的內容的文件名為default_image.jpg-->
     <!--則 返回一個URI   content://com.example.myapp.fileprovider/myimages/default_image.jpg-->

   <!--</paths>-->
  </resources>

確認下路徑名

路徑

路徑

FileProvider 頭部設置的對應標簽

FileProvider

FileProvider 獲取對應路徑邏輯 解析xml文件 對比對應的標簽 獲取對應的路徑

FileProvider

**修改所有用到Uri的地方 圖中的 BuildConfig.APPLICATION_ID 最好還是改成 context.getPackageName() **


Uri修改

官方鏈接:FileProvider

  • APK Signature Scheme v2

Android 7.0引入了全新的 APK Signature Scheme v2。這是加強對包的校驗,啟動了新的簽名后,像美團的多渠道打包方案在7.0機器上就會報錯了。
解決的辦法也很簡單,官方提供了關閉v2簽名的方法,只需要在gradle上配置一下即可:
signingConfigs {
release {
.......
v2SigningEnabled false
}
}
參考:Android7.0適配
鏈接: APK Signature Scheme v2詳細介紹

其他

Android6.0

  • USB 連接

默認情況下,現在通過 USB 端口進行的設備連接設置為僅充電模式。要通過 USB 連接訪問設備及其內容,用戶必須明確地為此類交互授予權限。如果您的應用支持用戶通過 USB 端口與設備進行交互,請將必須顯式啟用交互考慮在內。

  • 瀏覽器書簽變更

此版本移除了對全局書簽的支持。android.provider.Browser.getAllBookmarks() 和 android.provider.Browser.saveBookmark() 方法現已移除。同樣,READ_HISTORY_BOOKMARKS 權限和 WRITE_HISTORY_BOOKMARKS 權限也已移除。如果您的應用以 Android 6.0(API 級別 23)或更高版本為目標平臺,請勿從全局提供程序訪問書簽或使用書簽權限。您的應用應改為在內部存儲書簽數據。

  • 硬件標識符訪問權

為給用戶提供更嚴格的數據保護,從此版本開始,對于使用 WLAN API 和 Bluetooth API 的應用,Android 移除了對設備本地硬件標識符的編程訪問權。WifiInfo.getMacAddress()方法和 BluetoothAdapter.getAddress()方法現在會返回常量值 02:00:00:00:00:00。
現在,要通過藍牙和 WLAN 掃描訪問附近外部設備的硬件標識符,您的應用必須擁有 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 權限。

:當運行 Android 6.0(API 級別 23)的設備發起后臺 WLAN 或藍牙掃描時,在外部設備看來,該操作的發起來源是一個隨機化 MAC 地址。

  • WLAN 和網絡連接變更

此版本對 WLAN API 和 Networking API 引入了以下行為變更。

在此版本中,相機服務中共享資源的訪問模式已從之前的“先到先得”訪問模式更改為高優先級進程優先的訪問模式。對服務行為的變更包括:

  • 根據客戶端應用進程的“優先級”授予對相機子系統資源的訪問權,包括打開和配置相機設備。帶有對用戶可見 Activity 或前臺 Activity 的應用進程一般會被授予較高的優先級,從而使相機資源的獲取和使用更加可靠;

  • 當高優先級的應用嘗試使用相機時,系統可能會“驅逐”正在使用相機客戶端的低優先級應用。在已棄用的 Camera API 中,這會導致系統為被驅逐的客戶端調用 [onError()](https://developer.android.google.cn/reference/android/hardware/Camera.ErrorCallback.html#onError(int, android.hardware.Camera))。在 Camera2 API 中,這會導致系統為被驅逐的客戶端調用 onDisconnected()

  • 在配備相應相機硬件的設備上,不同的應用進程可同時獨立打開和使用不同的相機設備。但現在,如果在多進程用例中同時訪問相機會造成任何打開的相機設備的性能或能力嚴重下降,相機服務會檢測到這種情況并禁止同時訪問。即使并沒有其他應用直接嘗試訪問同一相機設備,此變更也可能導致低優先級客戶端被“驅逐”。

  • 更改當前用戶會導致之前用戶帳戶擁有的應用內活動相機客戶端被驅逐。對相機的訪問僅限于訪問當前設備用戶擁有的用戶個人資料。舉例來說,這意味著,當用戶切換到其他帳戶后,“來賓”帳戶實際上無法讓使用相機子系統的進程保持運行狀態。

  • 運行時

ART 運行時環境現在可正確實現 newInstance() 方法的訪問規則。此變更修正了之前版本中 Dalvik 無法正確檢查訪問規則的問題。如果您的應用使用newInstance() 方法,并且您想重寫訪問檢查,請調用 setAccessible() 方法(將輸入參數設置為 true)。如果您的應用使用 v7 appcompat 庫v7 recyclerview 庫,則您必須更新應用以使用這些庫的最新版本。否則,請務必更新從 XML 引用的任何自定義類,以便能夠訪問它們的類構造函數。此版本更新了動態鏈接程序的行為。動態鏈接程序現在可以識別庫的 soname 與其路徑之間的差異(公開錯誤 6670),并且現在已實現了按 soname 搜索。之前包含錯誤的 DT_NEEDED 條目(通常是開發計算機文件系統上的絕對路徑)卻仍工作正常的應用,如今可能會出現加載失敗。現已正確實現 dlopen(3) RTLD_LOCAL 標記。請注意,RTLD_LOCAL 是默認值,因此不顯式使用 RTLD_LOCAL 的 dlopen(3) 調用將受到影響(除非您的應用顯式使用 RTLD_GLOBAL)。使用 RTLD_LOCAL 時,在隨后通過調用 dlopen(3) 加載的庫中并不能使用這些符號(這與由 DT_NEEDED 條目引用的情況截然不同)。

在之前版本的 Android 上,如果您的應用請求系統加載包含文本重定位信息的共享庫,系統會顯示警告,但仍允許加載共享庫。從此版本開始,如果您的應用的目標 SDK 版本為 23 或更高,則系統會拒絕加載該庫。為幫助您檢測庫是否加載失敗,您的應用應該記錄 dlopen(3) 失敗日志,并在日志中加入dlerror(3) 調用返回的問題描述文本。要詳細了解如何處理文本重定位,請參閱此指南

  • 低電耗模式和應用待機模式

此版本引入了針對空閑設備和應用的最新節能優化技術。這些功能會影響所有應用,因此請務必在這些新模式下測試您的應用。

  • 低電耗模式:如果用戶拔下設備的電源插頭,并在屏幕關閉后的一段時間內使其保持不活動狀態,設備會進入低電耗模式,在該模式下設備會嘗試讓系統保持休眠狀態。在該模式下,設備會定期短時間恢復正常工作,以便進行應用同步,還可讓系統執行任何掛起的操作。
  • 應用待機模式:應用待機模式允許系統判定應用在用戶未主動使用它時處于空閑狀態。當用戶有一段時間未觸摸應用時,系統便會作出此判定。如果拔下了設備電源插頭,系統會為其視為空閑的應用停用網絡訪問以及暫停同步和作業。

要詳細了解這些節能變更,請參閱對低電耗模式和應用待機模式進行針對性優化

  • 文本選擇

現在,當用戶在您的應用中選擇文本時,您可以在一個浮動工具欄中顯示“剪切”“復制”“粘貼”等文本選擇操作。其在用戶交互實現上與為單個視圖啟用上下文操作模式中所述的上下文操作欄類似。
要實現可用于文本選擇的浮動工具欄,請在您的現有應用中做出以下更改:

請注意,如果您使用 Android 支持庫 22.2 修訂版,浮動工具欄不向后兼容,默認情況下 appcompat 會獲得對 ActionMode 對象的控制權。這會禁止顯示浮動工具欄。要在ActionMode 中啟用 AppCompatActivity 支持,請調用 getDelegate(),然后對返回的setHandleNativeActionModesEnabled() 對象調用 AppCompatDelegate,并將輸入參數設置為 false。此調用會將 ActionMode 對象的控制權交還給框架。在運行 Android 6.0(API 級別 23)的設備上,框架可以支持 ActionBar 模式或浮動工具欄模式;而在運行 Android 5.1(API 級別 22)或之前版本的設備上,框架僅支持 ActionBar 模式。

  • Android for Work 變更

此版本包含下列針對 Android for Work 的行為變更:

Android7.0

  • 無障礙改進

為提高平臺對于視力不佳或視力受損用戶的易用性,Android 7.0 做出了一些更改。這些更改一般并不要求更改您的應用代碼,不過您應仔細檢查并使用您的應用測試這些功能,以評估它們對用戶體驗的潛在影響。

  • 電池和內存

Android 7.0 包括旨在延長設備電池壽命和減少 RAM 使用的系統行為變更。這些變更可能會影響您的應用訪問系統資源,以及您的應用通過特定隱式 intent 與其他應用交互的方式。

  • 屏幕縮放

Android 7.0 支持用戶設置顯示尺寸,以放大或縮小屏幕上的所有元素,從而提升設備對視力不佳用戶的可訪問性。用戶無法將屏幕縮放至低于最小屏幕寬度 sw320dp,該寬度是 Nexus 4 的寬度,也是常規中等大小手機的寬度。

正常效果

運行 Android 7.0 系統映像的設備增大顯示尺寸后的效果。

當設備密度發生更改時,系統會以如下方式通知正在運行的應用:

  • 如果是面向 API 級別 23 或更低版本系統的應用,系統會自動終止其所有后臺進程。這意味著如果用戶切換離開此類應用,轉而打開 Settings 屏幕并更改 Display size 設置,則系統會像處理內存不足的情況一樣終止該應用。如果應用具有任何前臺進程,則系統會如處理運行時更改中所述將配置變更通知給這些進程,就像對待設備屏幕方向變更一樣。
  • 如果是面向 Android 7.0 的應用,則其所有進程(前臺和后臺)都會收到有關配置變更的通知,如處理運行時更改中所述。

大多數應用并不需要進行任何更改即可支持此功能,不過前提是這些應用遵循 Android 最佳做法。具體要檢查的事項:

  • 在屏幕寬度為 sw320dp 的設備上測試您的應用,并確保其充分運行。
  • 當設備配置發生變更時,更新任何與密度相關的緩存信息,例如緩存位圖或從網絡加載的資源。當應用從暫停狀態恢復運行時,檢查配置變更。
    注:如果您要緩存與配置相關的數據,則最好也包括相關元數據,例如該數據對應的屏幕尺寸或像素密度。保存這些元數據便于您在配置變更后決定是否需要刷新緩存數據。
  • 避免用像素單位指定尺寸,因為像素不會隨屏幕密度縮放。應改為使用與密度無關像素 (dp) 單位指定尺寸。
  • NDK 應用鏈接至平臺庫

從 Android 7.0 開始,系統將阻止應用動態鏈接非公開 NDK 庫,這種庫可能會導致您的應用崩潰。此行為變更旨在為跨平臺更新和不同設備提供統一的應用體驗。即使您的代碼可能不會鏈接私有庫,但您的應用中的第三方靜態庫可能會這么做。因此,所有開發者都應進行相應檢查,確保他們的應用不會在運行 Android 7.0 的設備上崩潰。如果您的應用使用原生代碼,則只能使用公開 NDK API

  • 低電耗模式

Android 6.0(API 級別 23)引入了低電耗模式,當用戶設備未插接電源、處于靜止狀態且屏幕關閉時,該模式會推遲 CPU 和網絡活動,從而延長電池壽命。而 Android 7.0 則通過在設備未插接電源且屏幕關閉狀態下、但不一定要處于靜止狀態(例如用戶外出時把手持式設備裝在口袋里)時應用部分 CPU 和網絡限制,進一步增強了低電耗模式。

圖 1. 低電耗模式如何應用第一級系統活動限制以延長電池壽命的圖示。

當設備處于充電狀態且屏幕已關閉一定時間后,設備會進入低電耗模式并應用第一部分限制:關閉應用網絡訪問、推遲作業和同步。如果進入低電耗模式后設備處于靜止狀態達到一定時間,系統則會對PowerManager.WakeLock
AlarmManager 鬧鈴、GPS 和 WLAN 掃描應用余下的低電耗模式限制。無論是應用部分還是全部低電耗模式限制,系統都會喚醒設備以提供簡短的維護時間窗口,在此窗口期間,應用程序可以訪問網絡并執行任何被推遲的作業/同步。
圖 2. 低電耗模式如何在設備處于靜止狀態達到一定時間后應用第二級系統活動限制的圖示。

請注意,激活屏幕或插接設備電源時,系統將退出低電耗模式并移除這些處理限制。此項新增的行為不會影響有關使您的應用適應 Android 6.0(API 級別 23)中所推出的舊版本低電耗模式的建議和最佳做法,如對低電耗模式和應用待機模式進行針對性優化中所討論。您仍應遵循這些建議(例如使用 Google 云消息傳遞 (GCM) 發送和接收消息)并開始安排更新計劃以適應新增的低電耗模式行為。

  • Project Svelte:后臺優化

Android 7.0 移除了三項隱式廣播,以幫助優化內存使用和電量消耗。此項變更很有必要,因為隱式廣播會在后臺頻繁啟動已注冊偵聽這些廣播的應用。刪除這些廣播可以顯著提升設備性能和用戶體驗。
移動設備會經歷頻繁的連接變更,例如在 WLAN 和移動數據之間切換時。目前,可以通過在應用清單中注冊一個接收器來偵聽隱式 CONNECTIVITY_ACTION
廣播,讓應用能夠監控這些變更。由于很多應用會注冊接收此廣播,因此單次網絡切換即會導致所有應用被喚醒并同時處理此廣播。
同理,在之前版本的 Android 中,應用可以注冊接收來自其他應用(例如相機)的隱式 ACTION_NEW_PICTUREACTION_NEW_VIDEO 廣播。當用戶使用相機應用拍攝照片時,這些應用即會被喚醒以處理廣播。

為緩解這些問題,Android 7.0 應用了以下優化措施:

  • 面向 Android 7.0 開發的應用不會收到 CONNECTIVITY_ACTION 廣播,即使它們已有清單條目來請求接受這些事件的通知。在前臺運行的應用如果使用 BroadcastReceiver 請求接收通知,則仍可以在主線程中偵聽CONNECTIVITY_CHANGE
  • 應用無法發送或接收 ACTION_NEW_PICTUREACTION_NEW_VIDEO 廣播。此項優化會影響所有應用,而不僅僅是面向 Android 7.0 的應用。

如果您的應用使用任何 intent,您仍需要盡快移除它們的依賴關系,以正確適配 Android 7.0 設備。Android 框架提供多個解決方案來緩解對這些隱式廣播的需求。例如,JobScheduler API 提供了一個穩健可靠的機制來安排滿足指定條件(例如連入無限流量網絡)時所執行的網絡操作。您甚至可以使用 JobScheduler 來適應內容提供程序變化。如需了解有關 Android N 中后臺優化以及如何改寫應用的詳細信息,請參閱后臺優化

更多信息請猛戳下面的官方鏈接

Android 6.0 變更
Android 7.0 變更

轉載請以鏈接形式標明出處:
http://www.lxweimin.com/p/95790125b7f4
本文出自:103style
or
csdn
http://blog.csdn.net/lxk_1993/article/details/73784883
本文出自:lxk_1993

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容