Android 版本適配6.0到9.0

    不知不覺,Android系統已經發展了10年之久,按照谷歌的命名風格,每個安卓版本都會以英文字母的順序來進行命名,並且它們都有一個好喫的甜品代號,從最初的紙杯蛋糕(cupcake)到現在的奧利奧(pie)以及最新的beta版本的Android Q。但據說從Android Q開始Google將以數字的形式去命名接下來的版本,android甜品時代即將落幕。。。

搞笑

    牛皮吹到這裏,下面進入正題。既然Android版本的迭代如此之快,那麼系統的適配工作也必須同步進行。系統適配對於項目的發展尤爲重要,只有不斷地提升性能與體驗才能得到用戶的認可。但即使這樣,相信還是有一部分的app還沒來得及做系統的適配工作,那麼這次的適配內容就從2015年推出的的6.0的版本開始講起,一共是四個大版本,6.0(Marshmallow)、7.0(Nougat)、8.0(Oreo),9.0(Pie)。先看下這四個版本有哪些主要的變化:

Android6.0

Android 6.0

發佈時間:2015年5月28日
主要變化:

1.運行時權限
2. 增加低電耗模式和應用待機模式
3. 取消支持 Apache HTTP 客戶端
4. 移除硬件標識符訪問權
5. WLAN 和網絡連接變更
6. 相機服務變更

Android 7.0

Android 7.0

發佈時間: 2016年8月22日
主要變化:

  1. 私有文件訪問權限更改
  2. 多窗口支持(分屏顯示)
  3. 通知增強功能
  4. 隨時隨地低電耗模式
  5. 多語言區域支持,更多語言
  6. 新增的表情符號
  7. Chrome 和 WebView 配合使用
  8. APK signature scheme v2

Android 8.0

這裏寫圖片描述

發佈時間:2017年8月22日
主要變化:

  1. 通知渠道
  2. 啓動圖標
  3. 統一的佈局外邊距和內邊距
  4. 自動填充框架
  5. 畫中畫模式
  6. 多顯示器支持
  7. 媒體增強功能

Android9.0

在這裏插入圖片描述

發佈時間:2018年 8 月 7 日
主要變化:

  1. 網絡安全變化
  2. 顯示屏缺口支持
  3. 渠道設置、廣播和請勿打擾
  4. 多攝像頭支持和攝像頭更新
  5. 無障礙功能

可以看到,每個版本都有比較多的變化,但並不是所有內容都需要適配,紅色標註就是最需要進行系統適配的內容,也是文章所講的內容。如果已經準備好去適配某個版本,那麼請將targetSdkVersion改爲對應的版本號,點擊sync Now,開啓填坑之路吧。


6.0—運行時權限

    在6.0之前的版本中,我們在使用某項權限時,只需要在Manifest中聲明就可以使用了,但是在6.0之後,某些權限(例如:通訊錄、位置、相機)不僅需要在Manifest中聲明,還要在app運行的時候動態地申請並被用戶允許才能正常使用,這類權限稱爲危險權限(Dangerous Permission)。與其對應的是正常權限(Normal Permissions),正常權限(例如:藍牙,網絡)只需要在清單文件聲明即可。具體的權限列表可以參考谷歌的官方文檔

適配流程:

適配過程相對比較簡單,下面以相機權限CAMERA 爲例子說明,分爲以下幾個步驟:
1.在Manifest中聲明所需權限:

<uses-permission android:name="android.permission.CAMERA"/>

2.調用*ContextCompat.checkSelfPermission()*檢查權限:

if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
               != PackageManager.PERMISSION_GRANTED) {
                //權限未授予,調用requestPermission()申請權限
 }else{
				 //權限已授予
 }

*checkSelfPermission()*方法接受兩個參數。第一個參數爲Context對象,第二個參數爲需要進行檢查的權限,類型爲String。返回值是一個int常量,返回PackageManager.PERMISSION_GRANTED表示權限已經被授予,返回PackageManager.PERMISSION_DENIED表示權限未被授予。

3.如果權限未被授予,調用*ActivityCompat.requestPermissions()*申請權限:

 ActivityCompat.requestPermissions(this,
                   new String[]{Manifest.permission.CAMERA}, MY_REQUEST_CODE);

*requestPermissions()*接受三個參數,第一個參數是Context對象,第二個參數是一個String數組,可以同時申請多個權限,第三個參數是請求碼。

4.重寫Activity的*onRequestPermissionsResult()*處理申請回調:

@Override
public void onRequestPermissionsResult(int requestCode,
        String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_REQUEST_CODE: {
 
            if (grantResults.length > 0 &&
            grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                
			//用戶允許該權限
			
            } else {
            
			//用戶拒絕該權限
			
            }
            return;
        }
    }
}

其中requestCode就是請求碼,在這裏就是上面的MY_REQUEST_CODE,permissions就是申請的權限,grantResults是請求的結果,數組大小與permissions對應。由於我們只請求了一個CAMERA權限,所以只需要看grantResults的第一個值就好了,根據結果做出相應的操作。至此,運行時權限申請就完成了。整體看起來,流程比較清晰和簡單,就是有點繁瑣(每個權限都要這麼寫的話),那這裏推薦一個比較好用的封裝庫RxPermission


7.0—多窗口模式(分屏模式)

    多窗口模式是7.0版本的一個新特性,顧名思義,也就是讓一個屏幕分成多個窗口,可以在一個屏幕上顯示多個應用,多窗口模式共分爲三種(分屏模式、畫中畫、自由形狀),在手機上比較多用的是分屏模式,如下圖所示(個人感覺:在屏幕不是特別大的手機上效果一般)。畫中畫模式在Android TV上比較適用,自由模式在TNT上比較適用…這裏不展開討論。提供一個官方文檔的鏈接。

如何進入分屏模式?每個廠家的設置都可能不一樣,有的長按菜單鍵,有的長按返回鍵,具體可以百度下。

多窗口模式

分屏模式具有以下幾個特點:

  1. 分屏模式不會更改 Activity 生命週期
  2. 在分屏模式模式下,只有一個Activity會獲得焦點,其他Activity處於Paused狀態
  3. 在分屏模式下調整窗口大小會回調onConfigurationChanged
  4. 如果根Activity允許多窗口模式,那麼與它在同一個棧種的Activity都被允許多窗口模式(即使沒有設置)。
  5. 某些系統 UI 自定義選項將被禁用;例如,在非全屏模式中,應用無法隱藏狀態欄。
  6. 系統將忽略對android:screenOrientation 屬性所作的更改。

適配流程:

1.如果佈局外層是ScrollView、RecycleView、ListView的界面,那麼在分屏模式下是比較正常,可以嘗試讓這些界面支持分屏模式。

2.如果是固定寬高的界面,設置android:minimalHeightandroid:minimalWidth來設置分屏模式下的最小寬高。

<activity android:name=".MyActivity">
    <layout 
          android:minimalHeight="450dp"
          android:minimalWidth="300dp" />
</activity>

(編譯出錯,問題未知)

3.如果不想適配分屏模式,可以在Manifest的activity標籤下設置屬性android:resizeableActivity="false"


7.0—訪問應用私有文件

    在7.0版本之前,我們可以通過File://這一類Uri訪問其他應用的私有文件或者讓其他應用訪問自己的私有文件。什麼是私有文件?如果不太清楚這個概念的話,可以看這一篇博客,講解的非常好,另外他還有一篇關於7.0的適配也講解的非常詳細。

http://yifeng.studio/2017/04/27/android-app-file-storage-directory/

從7.0版本開始,這麼做就不行了,如果嘗試傳遞 file:// Uri來訪問其他應用的私有文件會觸發 FileUriExposedException異常,分享私有文件內容的推薦方法是使用 FileProvider,FileProvider是v4包下的一個類,繼承自ContentProvider。所以可以猜測通過FileProvider對外公開的uri就是content://開頭的。

說是這麼說,什麼場景下我們會需要訪問其他應用的私有文件或讓其他應用訪問自己的私有文件呢?有幾個場景還是比較常見的,比如,調用系統的安裝程序安裝apk文件(假設這個apk文件存放在私有目錄中),那麼就需要提供此apk的路徑讓安裝程序來訪問(這就是讓其他應用訪問自己的私有文件),又或者調用系統相機拍照時,需要提供一個拍照後的相片的存放路徑等。下面以拍照爲例子講解具體的適配流程。

適配流程:

1.導入v4包,在主module的build.gradle下的dependencies節點下添加依賴,版本號視情況而定。

 implementation 'com.android.support:support-v4:27.1.1'

2.在 res/xml 目錄下新建一個 xml 文件,用於存放應用需要共享的文件目錄,這裏我命名爲file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path
        name="my_images"
        path="./images/"/>
</paths>

name是自定義的一個別名,path是這個共享的目錄,這裏的path值表示共享外部私有目錄file/images下的文件。如果是輸入的是"."則表示共享外部私有目錄下的file/目錄下的所有文件。
其中<paths>元素有以下幾個子節點:

  • <files-path>:內部存儲空間應用私有目錄下的 files/ 目錄,等同於 Context.getFilesDir() 所獲取的目錄路徑
  • <cache-path>:內部存儲空間應用私有目錄下的 cache/ 目錄,同於 Context.getCacheDir() 所獲取的目錄路徑;
  • <external-path>:外部存儲空間根目錄,等同於 Environment.getExternalStorageDirectory() 所獲取的目錄路徑;
  • <external-files-path>外部存儲空間應用私有目錄下的 files/ 目錄,等同於 Context.getExternalFilesDir(null) 所獲取的目錄路徑;
  • <external-cache-path>:外部存儲空間應用私有目錄下的 cache/ 目錄,等同於 Context.getExternalCacheDir();

3.在AndroidManifest中聲明FileProvide:

  <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/file_paths" />
   </provider>

authorities的名字可自定義,一般爲包名+FileProvide,resource就是剛剛新建的共享文件。

完成以上三步就ok了,下面來嘗試調用系統相機進行拍照(注意CAMERA權限的申請)。
下面是測試代碼:

private void testCapture() {
       String path = getExternalFilesDir(null) + File.separator + "images" + File.separator +
               System.currentTimeMillis() + ".jpg";
       File file = new File(path);
       if (!file.exists()) {
           file.mkdirs();
       }
       if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
           Uri uri = FileProvider.getUriForFile(this,
                   BuildConfig.APPLICATION_ID + ".FileProvider", file);
           Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
           intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
           startActivityForResult(intent, MY_REQUEST_CODE);
       } else {
			//7.0之前的使用
       }
   }

ok,正常打開相機:
7.0打開系統相機測試

點擊✓後照片就會保存在指定的目錄中,我們通過adb找到該文件:

查找照片

完美!!!這樣就可以在7.0以上的設備訪問應用私有文件了。


8.0—自適應圖標

    由於不同的廠商對應用圖標的形狀有了一定的規範(圓角、圓形等),如果不遵循它們的規範,您的應用圖標可能就會被強制改成它們要求的形狀,有時候改過的圖標可能你的期望落差太大 。所以從Android 8.0系統開始,google對應用圖標進行統一的規範。在8.0版本之後,應用程序的圖標被分爲了兩層:前景層和背景層。前景層是一個背景透明的logo,背景層一般是一張純色或帶紋理的圖片。前景層和背景層組合之後,會被蓋上一層mask,這層mask是廠商決定的,這樣一來,不管這一層mask是圓角還是圓形,都可以完整地顯示您的logo了。下面是官方文檔的一張gif演示圖。

8.0應用圖標適配

適配流程
如果您的Android Studio版本在3.0以上,那麼基本在新建工程的時候就會默認創建res/mipmap-anydpi-v26文件夾,如下:
mipmap-anydpi-v26
mipmap-anydpi-v26下有兩個xml文件代表8.0以上版本的啓動圖標,沒錯是xml文件。打開ic_launcher.xml可以看到以下內容:

<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background"/>
    <foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

這是8.0版本的標準寫法,由標籤名即可知道其意思, <background >就是背景
<foreground >就是前景。這是新建工程後Android Studio爲我們創建的,那麼,我們可以根據項目的進行簡單的更改即可。Android Studio3.0以上版本還爲我們提供了一個可視化的工具Image Asset,windows下可用快捷鍵ctrl+shift+a搜索

Image Asset

這裏提供了可以直接操作的可視化界面

Image Asset

這個沒什麼好說的,非常的簡單。唯一要注意的是Preview界面上的黑色圓圈,如果您不想您的logo在mark的時候被覆蓋掉,那麼前景層必須顯示在黑色圓圈內。這裏我將默認的icon的背景層用一個黃色的<color>來替代。下載到手機上看下效果:

這裏寫圖片描述
到這一步就已經完成了8.0的圖標適配了。


8.0—通知渠道

    Android 8.0對通知欄進行了比較大的改動,引入了通知渠道的概念,就好像爲每種不同的消息分類別一樣,例如,某個新聞app,將通知分爲兩種,一種爲新聞類通知,一種爲廣告類通知。這樣,就可以創建兩個不同的渠道,發出通知的時候,可以根據渠道來發送,這樣做的好處是便於用戶去管理每個渠道的通知(選擇他們感興趣的內容)。同時,Android用戶具有管理每個渠道的權限(設置聲音、震動等),並且可以關閉某個渠道的通知,附上官方文檔。我們先來看一下最右app的通知渠道體驗一下。
這裏寫圖片描述
好了,現在我們來進行通知欄的適配。

適配流程
1.創建通知渠道

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel newsChannel = new NotificationChannel(CHANNEL_NEWS, "新聞",
                    NotificationManager.IMPORTANCE_HIGH);
            NotificationChannel adsChannel = new NotificationChannel(CHANNEL_ADS, "廣告",
                    NotificationManager.IMPORTANCE_DEFAULT);
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            manager.createNotificationChannel(newsChannel);
            manager.createNotificationChannel(adsChannel);
    }

NotificationChannel對象就代表着一個通知渠道,構造方法接受三個參數,第一個參數是全局唯一的渠道id,String類型。第二個是渠道名稱,注意這個名稱給用戶看的。第三個是通知的重要等級。這裏我們調用*createNotificationChannel()*創建了兩個渠道,新聞和廣告。注意:通知渠道不會重複創建,創建過程可以放在Application中進行。運行上面的代碼後,渠道就創建好了,如下圖:
通知渠道

2.發送通知
我們定義三個按鈕,一個用來發送新聞類的通知,一個用來發送廣告類的通知,一個用來發送沒有通知渠道的通知:
通知渠道測試

代碼如下:

public void sendNews(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_NEWS)
             .setContentTitle("法國獲得2018世界盃冠軍")
             .setContentText("法國奪冠克羅地亞雖敗猶榮比利時季軍")
             .setAutoCancel(true)
             .build();
     manager.notify(1, notification);
 }

 public void sendAds(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this, CHANNEL_ADS)
             .setContentTitle("收到某新聞來自拼多多的廣告")
             .setContentText("這是它的廣告內容...")
             .setAutoCancel(true)
             .build();
     manager.notify(1, notification);
 }
 
 public void sendNoChannel(View view) {
     NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     Notification notification = new NotificationCompat.Builder(this)
             .setContentTitle("沒有渠道的通知")
             .setContentText("通內容")
             .setAutoCancel(true)
             .setSmallIcon(R.mipmap.ic_launcher)
             .build();
     manager.notify(1, notification);
 }
   

運行效果:
通知渠道運行
可以看到,在8.0之後(targetSdkVersion=26)發送通知如果不指定渠道的話是無法發送出去的,只有指定了通知渠道的通知纔可以正常發送,通知展示的方式取決於用戶的設置。

9.0—網絡安全變化

從9.0開始,google禁止App使用未加密的連接,簡單來說,網絡請求必須使用https開頭的,http的網絡請求被禁止了,如果想在9.0之後使用http的網絡請求,解決方法如下:

  1. 在res下的xml目錄新建一個文件network_security_config(名字隨意),內容如下:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>
  1. 在Androidmanifest.xml中的application節點加入屬性:
 android:networkSecurityConfig="@xml/network_security_config"

ok,這樣就可以繼續使用http了。

總結

    到這裏,6.0到9.0的系統適配內容就講完了,由於一次性要講四個大版本的適配,所以對一些內容敘述的不太完整,見諒。本次適配內容,也是參考了多個博主的文章,下面把鏈接放上來。

1. 關於 Android 7.0 適配中 FileProvider 部分的總結
2 .Android應用圖標微技巧,8.0系統中應用圖標的適配
3 .Android通知欄微技巧,8.0系統中通知欄的適配

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章