版權聲明:本文為博主原創文章(部分引用他人博文,已加上引用說明),未經博主允許不得轉載。http://www.lxweimin.com/p/49fa8ebc0105
轉載請標明出處:
http://www.lxweimin.com/p/49fa8ebc0105
本文出自 AWeiLoveAndroid的博客
上一篇文章講了 屏幕適配 http://www.lxweimin.com/p/7aa34434ad4d
這一篇文章講一下 版本適配 http://www.lxweimin.com/p/49fa8ebc0105
下一篇文章講一下 ROM適配 http://www.lxweimin.com/p/f9c67a4b908e
在我們的開發中,會對不同安卓版本做適配,比如我之前做過的項目中最低兼容到4.4,最高兼容是最新的系統7.1,由于不同版本的系統中部分API版本也不同,我就要對這些API做特殊處理。新的平臺有一些API不能使用舊的API,舊的平臺也使用不了新的API。所以這就要考驗我們開發人員的能力了。我這里簡單給出幾點我開發中使用過的一些方式,僅供參考:
一、同一個api在不同版本都存在,只是api的一些接口方法有變更。
這種情況是最好處理的,只要對版本號做判斷,對應的系統版本用相應的api方法就好了。為了好維護,建議做一個簡單的封裝。
舉例說明如下:
比如Notification在不同版本的兼容,舉例如下:
首先打開谷歌官方文檔,看看文檔里面的一些說明:
1.Notification這個類是added in API level 1,一直都有,只是具體某些方法有變更。繼續往下看。
2.這個類有個說明,意思是Notification.Builder是新增的一個內部類,用它創建通知更方便。接著往下看。
A class that represents how a persistent notification is to
be presented to the user using the NotificationManager.
The Notification.Builder has been added to make it easier
to construct Notifications.
3.Public constructors公共的構造方法,其中有3個參數的這個在api 11過時,它被Notification.Builder替代了。
Notification(int icon, CharSequence tickerText, long when)
This constructor was deprecated in API level 11.
Use Notification.Builder instead.
4.常量
EXTRA_LARGE_ICON
This constant was deprecated in API level 26. Use getLargeIcon(), which supports a wider variety of icon sources.(在API級別26中已棄用。使用getLargeIcon(),它支持更多種圖標源。)EXTRA_SMALL_ICON
This constant was deprecated in API level 26. Use getSmallIcon(), which supports a wider variety of icon sources.(在API級別26中已棄用。使用getSmallIcon(),它支持更多種圖標源。)FLAG_HIGH_PRIORITY
This constant was deprecated in API level 16. Use priority with a positive value.(在api16被棄用,請使用正數priority值替代)FLAG_SHOW_LIGHTS
This constant was deprecated in API level 26. use shouldShowLights().(在API級別26中已棄用。請使用shouldShowLights()
替代)PRIORITY_DEFAULT
This constant was deprecated in API level 26. use IMPORTANCE_DEFAULT instead.(在API級別26中已棄用。請使用IMPORTANCE_DEFAULT
替代)PRIORITY_HIGH
This constant was deprecated in API level 26. use IMPORTANCE_HIGH instead.(在API級別26中已棄用。請使用IMPORTANCE_HIGH
替代)PRIORITY_LOW
This constant was deprecated in API level 26. use IMPORTANCE_LOW instead.(在API級別26中已棄用。請使用IMPORTANCE_LOW
替代)PRIORITY_MAX
This constant was deprecated in API level 26. use IMPORTANCE_HIGH instead.(在API級別26中已棄用。請使用IMPORTANCE_HIGH
替代)PRIORITY_MIN
This constant was deprecated in API level 26. use IMPORTANCE_MIN instead.(在API級別26中已棄用。請使用IMPORTANCE_MIN
替代)STREAM_DEFAULT
This constant was deprecated in API level 21. Use getAudioAttributes() instead.(在API級別21中已棄用。請使用getAudioAttributes()
替代)
5.字段Fields
audioAttributes
在api 26棄用. 使用getAudioAttributes()
替代.audioStreamType
在api 21棄用. 使用audioAttributes
替代.defaults
此字段在API 26棄用。使用getSound()
和shouldShowLights()
和shouldVibrate()
。icon
此字段已在API級別26中棄用。使用setSmallIcon(Icon)
替代。largeIcon
This field was deprecated in API level 23. Use `setLargeIcon(Icon) instead.ledARGB
This field was deprecated in API level 26. use `shouldShowLights().ledOffMS
This field was deprecated in API level 26. use `shouldShowLights().ledOnMS
This field was deprecated in API level 26. useshouldShowLights().
priority
This field was deprecated in API level 26. usegetImportance()
instead.sound
This field was deprecated in API level 26. usegetSound()
instead.vibrate
This field was deprecated in API level 26. usegetVibrationPattern()
.
二、Android6.0的動態權限介紹
因為Android6.0(API23)開始需要動態申請權限,需要手動申請的權限有8組(短信、電話、聯系人、存儲、位置、麥克風、日歷、相機
),共24個,如下所示:
所屬權限組 | 權限 |
---|---|
短信 | SEND_SMS |
短信 | RECEIVE_SMS |
短信 | READ_SMS |
短信 | RECEIVE_WAP_PUSH |
短信 | RECEIVE_MMS |
電話 | READ_PHONE_STATE |
電話 | CALL_PHONE |
電話 | READ_CALL_LOG |
電話 | WRITE_CALL_LOG |
電話 | ADD_VOICEMAIL |
電話 | USE_SIP |
電話 | PROCESS_OUTGOING_CALLS |
聯系人 | READ_CONTACTS |
聯系人 | WRITE_CONTACTS |
聯系人 | GET_ACCOUNTS |
存儲 | READ_EXTERNAL_STORAGE |
存儲 | WRITE_EXTERNAL_STORAGE |
位置 | ACCESS_FINE_LOCATION |
位置 | ACCESS_COARSE_LOCATION |
麥克風 | RECORD_AUDIO |
日歷 | READ_CALENDAR |
日歷 | WRITE_CALENDAR |
相機 | CAMERA |
傳感器 | BODY_SENSORS |
注意:如果應用程序請求在AndroidManifest中列出的危險權限,并且應用程序已經在同一權限組中具有另一個危險權限,系統會立即授予權限,而不會與用戶進行任何交互。
例如,如果一個應用程序先前已經請求并被授予READ_CONTACTS權限,然后它請求WRITE_CONTACTS(同屬于聯系人一組),系統會立即授予該權限,不會再彈出權限授予詢問的對話框。
三、Android6.0如何申請動態權限
開發中經常會遇到拍照的權限申請,這里就講一下如何動態設置拍照權限:
//別忘記在清單文件也加上CAMERA權限
//<uses-permission android:name="android.permission.CAMERA" />
// 定義識別碼
public static final int CAMERA_OK = 1;
//動態申請拍照權限
if (Build.VERSION.SDK_INT>22){
if (ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED){
//先判斷有沒有權限 ,沒有就在這里進行權限的申請
requestPermissions(new String[]{Manifest.permission.CAMERA}, CAMERA_OK);
}else {
//說明已經獲取到攝像頭權限了,可以去選擇照片或者拍照了。
toSelectPhotoOrOpenCamera();
}
}else {
//這個說明系統版本在6.0之下,不需要動態獲取權限,直接去選擇照片或者拍照。
toSelectPhotoOrOpenCamera();
}
//在Activity中重寫權限獲取方法:
/**
* 權限操作結果處理
*/
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
case CAMERA_OK:
if (grantResults.length > 0 && grantResults[0]
== PackageManager.PERMISSION_GRANTED) {
//用戶已授權
toSelectPhotoOrOpenCamera();
} else {
//用戶拒絕權限
ToastUtils.show(this,
"缺少相機權限,暫時無法提供掃描功能,請嘗試在設置中打開相機權限!",
Toast.LENGTH_LONG);
}
break;
}
}
}
四、Android7.0對文件權限進一步升級,提出了新的類FileProvider來獲取文件。所以適配的時候一定要注意這一點api的變化。
FileProvider
是ContentProvider
的子類,把原來文件共享的 file://uri
換成了 content://uri
。一個Uri允許你獲取臨時權限去讀寫文件,當使用含有Uri的Intent,可以使用Intent.setFlags來添加臨時權限。
下面來看看調用系統相機拍攝照片有如何變化,大致步驟如下所示:
(一)在manifest中添加Provider
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.lzw.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
...
</provider>
...
</application>
</manifest>
(二)配置你要獲取的文件所在的文件夾 --> 創建一個xml文件,比如file_demo.xml,文件內容如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path name="my_images" path="images/"/>
...
</paths>
路徑說明:
<files-path name="name" path="path/" />
<!--等同于Context.getFilesDir()下面的path文件夾的所有文件-->
<cache-path name="name" path="path/" />
<!--等同于Context.getCacheDir()下面的path文件夾-->
<external-path name="name" path="path/" />
<!--等同于Environment.getExternalStorageDirectory()下面的path文件夾-->
<external-files-path name="name" path="path/" />
<!--等同于 Context#getExternalFilesDir(String)下面子文件path文件夾-->
<external-cache-path name="name" path="path/" />
<!--相當于 Context.getExternalCacheDir()下邊的path文件夾-->
(三)添加路徑信息到provier
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.lzw.demo.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_demo" />
</provider>
(四)現在可以去拍照了。(由于Android6.0開始要動態申請權限,所以別忘了,這里就不寫了,主要講FileProvider的使用)
//適配7.0的fileprovider,imgfile是圖片文件路徑
public void TakePhotoAdaption(File imgFile){
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//適配android7.0 手機拍照取uri的處理
if(Build.VERSION.SDK_INT<24){
//7.0如果用會Uri.fromFile(XXX)會閃退,所以這里要特別做一個判斷。
//imgfile是圖片文件路徑
uri = Uri.fromFile(imgFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
}else{
//7.0+使用FileProvider.getUriForFile這個api
uri=FileProvider.getUriForFile(DemoActivity.this,
"com.lzw.demo.fileprovider",imgFile);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
//添加這一句表示對目標應用臨時授權該Uri所代表的文件
cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION );
}
startActivityForResult(cameraIntent, FLAG_CHOOSE_CAMERA);
}
想看到拍照、選擇照片、裁剪等完整流程的描述,可以參考這篇博客 解決安卓7.0拍照,相冊選擇崩潰的問題(包括壓縮圖片在內)
五、關于Android7.0相機閃退以及相冊獲取不到圖片問題
- 1、沒有動態申請權限,按照上述思路去做就好了。
- 2、華為手機的一些特殊處理方式,詳情參見 ROM適配 http://www.lxweimin.com/p/f9c67a4b908e
六、Android 8.0適配報錯:Only fullscreen opaque activities can request orientation解決方案:
出現的原因:絕大多數都是因為我們為了提高用戶體驗,手動取消App啟動白屏或者黑屏的時候,將Splash界面設為了透明,然后這個時候又設置了方向為垂直,從而導致了這個問題。
解決方案:
-
1.找到你設置透明的Activity,然后在他的theme中將android:windowIsTranslucent改為false
<item name="android:windowIsTranslucent">false</item>
-
2.再加入下面這行代碼就搞定了。
<item name="android:windowDisablePreview">true</item>
這個坑來自于博客: http://www.lxweimin.com/p/d0d907754603
七、Android8.0版本更新相關api適配
- 創建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel mChannel = new NotificationChannel("channel_01",
"消息推送", NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(mChannel);
}
- 創建Notification
Context context = DJApplication.getInstance();
Notification.Builder builder = new Notification.Builder(context);
builder.setTicker("開始下載");
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(DJApplication.getInstance().getResources(),
R.mipmap.ic_launcher));
builder.setAutoCancel(true);
PendingIntent pIntent = PendingIntent.getActivity(context, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentTitle("下載中");
builder.setContentIntent(pIntent);
builder.setContentText(text);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId("channel_01");//設置有效的通知渠道 ID,這個ID要和之前創建時候的Channel_ID相同
}
manager.notify(1, builder.build());
- 安裝apk權限
在 Android 8.0 中,安裝未知應用權限提高了安裝未知來源應用時的安全性。此權限與其他運行時權限一樣,會與應用綁定,在安裝時進行提示,確保用戶授予使用安裝來源的權限后,此權限才會提示用戶安裝應用。在運行 Android 8.0 或更高版本的設備上使用此權限時,惡意下載程序將無法騙取用戶安裝未獲得預先授權的應用,所以我們需要加入安裝apk文件的權限。
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />