前言
新年第一篇總結與分享~
離19年新年就剩一個月左右了,整個公司的項目也不像以往一樣緊張、忙碌,周末同僚和朋友聊得最多的就是搶票和年終獎了(然而我們公司并沒有?),為自己心疼一分鐘。。。
總結一下18年吧,主要負責了4個商業項目,還參與了公司 Android 新架構的封裝,整體來說算是穩步提高吧。
先說一下寫這篇文章的背景吧,主要就是不太忙了,再加上客戶要求适配 Android 9.0 (第一個客戶主動提出來的),在看完9.0的适配之後,也對之前的進行了一些整理,廢話不多說,請看幹貨!
Android6.0适配
權限适配
動态權限适配是 Android 6.0 最先開始的,也是 Android 系統對開發者影響最大的改動之一。
權限适配三連問
- Q: 是否 Android 6.0 所有權限都需要動态申請?
- A: 不是。隻有屬于危險權限的才需要申請。
- Q: 危險權限有哪些?
- A: 見下面**“危險權限分組說明”**
- Q: 危險權限是否需要一個一個申請?
- A: 在 Android 6.0 ~ Android 8.0,不需要。如果應用在運作時請求權限并且被授予該權限,系統會錯誤地将屬于同一權限組并且在清單中注冊的其他權限也一起授予應用,即對于同一組内的權限,隻要有一個被同意,其他的都會被同意。在 Android 8.0 之後,此行為已被糾正。系統隻會授予應用明确請求的權限。然而一旦使用者為應用授予某個權限,則所有後續對該權限組中權限的請求都将被自動準許,但是若沒有請求相應的權限而進行操作的話就會出現應用 crash 的情況。
危險權限分組說明
權限組 | 權限名稱 |
---|---|
CALENDAR | android.permission.READ_CALENDAR |
android.permission.WRITE_CALENDAR | |
CAMERA | android.permission.CAMERA |
CONTACTS | android.permission.READ_CONTACTS |
android.permission.WRITE_CONTACTS | |
android.permission.GET_ACCOUNTS | |
LOCATION | android.permission.ACCESS_FINE_LOCATION |
android.permission.ACCESS_COARSE_LOCATION | |
MICROPHONE | android.permission.RECORD_AUDIO |
PHONE | android.permission.READ_PHONE_STATE |
android.permission.CALL_PHONE | |
android.permission.READ_CALL_LOG | |
android.permission.ADD_VOICEMAIL | |
android.permission.WRITE_CALL_LOG | |
android.permission.USE_SIP | |
android.permission.PROCESS_OUTGOING_CALLS | |
SENSORS | android.permission.BODY_SENSORS |
SMS | android.permission.SEND_SMS |
android.permission.RECEIVE_SMS | |
android.permission.READ_SMS | |
android.permission.RECEIVE_WAP_PUSH | |
android.permission.RECEIVE_MMS | |
STORAGE | android.permission.READ_EXTERNAL_STORAGE |
android.permission.WRITE_EXTERNAL_STORAGE |
對應在清單檔案中的展示如下:
<!--CALENDAR-->
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
<!--CAMERA-->
<uses-permission android:name="android.permission.CAMERA"/>
<!--CONTACTS-->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!--LOCATION-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!--MICROPHONE-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<!--PHONE-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<!--SENSORS-->
<uses-permission android:name="android.permission.BODY_SENSORS"/>
<!--SMS-->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!--STORAGE-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Android 7.0适配
應用間共享檔案
在 targetSdkVersion 大于等于的 24 的 app 中,但是我們沒有去适配 Android 7.0。那麼在調用安裝頁面,或修改使用者頭像操作時,就會失敗。那麼就需要你去适配 Android 7.0或是将 targetSdkVersion 改為 24 以下(不推薦)。适配的方法這裡就不細講,大家可以看鴻洋大神的 Android 7.0 行為變更 通過FileProvider在應用間共享檔案這篇文章。
APK signature scheme v2
Android 7.0 引入一項新的應用簽名方案 APK Signature Scheme v2,它能提供更快的應用安裝時間和更多針對未授權 APK 檔案更改的保護。在預設情況下,Android Studio 2.2 和 Android Plugin for Gradle 2.2 會使用 APK Signature Scheme v2 和傳統簽名方案來簽署您的應用。
說明:

- 隻勾選V1簽名就是傳統方案簽署,但是在 Android 7.0 上不會使用V2安全的驗證方式。
- 隻勾選V2簽名7.0以下會顯示未安裝,Android 7.0 上則會使用了V2安全的驗證方式。
- 同時勾選V1和V2則所有版本都沒問題。
org.apache不支援問題
// build.gradle裡面加上這句話
defaultConfig {
useLibrary 'org.apache.http.legacy'
}
SharedPreferences閃退
// MODE_WORLD_READABLE:Android 7.0以後不能使用這個擷取,會閃退
// 應修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);
Android 8.0适配
Android 8.0中PHONE權限組新增兩個權限
ANSWER_PHONE_CALLS:允許您的應用通過程式設計方式接聽呼入電話。要在您的應用中處理呼入電話,您可以使用 acceptRingingCall() 函數。
READ_PHONE_NUMBERS:權限允許您的應用讀取裝置中存儲的電話号碼。
通知适配
Android 8.0中,為了更好的管制通知的提醒,不想一些不重要的通知打擾使用者,新增了通知管道,使用者可以根據管道來屏蔽一些不想要的通知。
代碼示例如下:
/**
* Description: Android 8.0通知的相容類
* Author: Jack Zhang
* create on: 2019/1/2 3:16 PM
*/
public class MyNotification
{
public static final String CHANNEL_ID_GL = "com.jz.gailun";
public static final String CHANNEL_NAME_GL = "蓋倫";
public static final String CHANNEL_ID_AX = "com.jz.aixi";
public static final String CHANNEL_NAME_AX = "艾希";
public static final String CHANNEL_ID_LL = "com.jz.liulang";
public static final String CHANNEL_NAME_LL = "流浪";
public static void setONotifyChannel(NotificationManager manager, String channeId, String channelName)
{
setONotifyChannel(manager, null, channeId, channelName);
}
public static void setONotifyChannel(NotificationManager manager, NotificationCompat.Builder builder, String channeId, String channelName)
{
if (TextUtils.isEmpty(channeId) || TextUtils.isEmpty(channelName))
Logger.e("Android 8.0 Notification的channeId與channelName不能為空");
if (Build.VERSION.SDK_INT >= 26)
{
// 第三個參數設定通知的優先級别
NotificationChannel channel = new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
// 是否可以繞過請勿打擾模式
channel.canBypassDnd();
// 是否可以顯示icon角标
channel.canShowBadge();
// 是否顯示通知閃燈
channel.enableLights(true);
// 收到消息時震動提示
channel.enableVibration(true);
// 設定繞過免打擾
channel.setBypassDnd(true);
channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_SECRET);
// 設定閃光燈顔色
channel.setLightColor(Color.RED);
// 擷取設定鈴聲設定
channel.getAudioAttributes();
// 設定震動模式
channel.setVibrationPattern(new long[]{100, 200, 100});
// 是否會閃光
channel.shouldShowLights();
if (manager != null)
manager.createNotificationChannel(channel);
if (builder != null)
builder.setChannelId(channeId);//這個id參數要與上面channel建構的第一個參數對應
}
}
public static Notification getNotification(Context context, String channelId)
{
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, channelId);
Notification notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_logo)
.setPriority(NotificationManager.IMPORTANCE_MIN)
.setCategory(Notification.CATEGORY_SERVICE)
.build();
return notification;
}
}
/**
* Description: 通知管理類
* Author: Jack Zhang
* create on: 2019/1/2 3:23 PM
*/
public class NotifyManager
{
private volatile static NotifyManager INSTANCE;
private NotifyManager(Context context)
{
initNotifyManager(context);
}
public static NotifyManager getInstance(Context context)
{
if (INSTANCE == null)
synchronized (NotifyManager.class)
{
if (INSTANCE == null)
INSTANCE = new NotifyManager(context);
}
return INSTANCE;
}
private NotificationManager manager;
// NotificationManagerCompat
private NotificationCompat.Builder builder;
// 初始化通知欄配置
private void initNotifyManager(Context context)
{
context = context.getApplicationContext();
manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// // 如果存在則清除上一個消息
// manager.cancel(lastNotificationId);
builder = new NotificationCompat.Builder(context, MyNotification.CHANNEL_ID_GL);
MyNotification.setONotifyChannel(manager, builder, MyNotification.CHANNEL_ID_GL, MyNotification.CHANNEL_NAME_GL);
// 設定标題
builder.setContentTitle("Title");
// 狀态欄的動畫提醒語句
builder.setTicker("Ticker");
// 什麼時候提醒
builder.setWhen(System.currentTimeMillis());
// 設定通知欄的優先級
builder.setPriority(Notification.PRIORITY_DEFAULT);
// 設定點選可消失
builder.setAutoCancel(true);
// 設定是否震動等
builder.setDefaults(Notification.DEFAULT_VIBRATE);
// 設定icon
builder.setSmallIcon(R.mipmap.ic_logo);
// 設定點選意圖
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
}
/**
* 顯示蓋倫通知欄
*
* @author Jack Zhang
* create at 2019/1/2 3:28 PM
*/
public void showGLNotify(Context context)
{
// 設定内容
builder.setContentText("蓋倫");
manager.notify(1, builder.build());
}
/**
* 顯示艾希通知欄
*
* @author Jack Zhang
* create at 2019/1/2 3:28 PM
*/
public void showAXNotify(Context context)
{
builder.setContentText("艾希");
manager.notify(2, builder.build());
}
/**
* 顯示流浪通知欄
*
* @author Jack Zhang
* create at 2019/1/2 3:28 PM
*/
public void showLLNotify(Context context)
{
builder.setContentText("流浪");
manager.notify(3, builder.build());
}
}
SecurityException的閃退
問題原因:項目使用了ActiveAndroid,在 8.0 或 8.1 系統上使用 26 或以上的版本的 SDK 時,調用 ContentResolver 的 notifyChange 方法通知資料更新,或者調用 ContentResolver 的 registerContentObserver 方法監聽資料變化時,會出現上述異常。
解決方案:
- 在清單檔案配置:
<provider
android:name="com.activeandroid.content.ContentProvider"
android:authorities="com.jz.androidclient"
android:enabled="true"
android:exported="false"/>
- 去掉這個監聽重新整理的方法,改為廣播重新整理
靜态廣播無法正常接收
問題原因:Android 8.0 引入了新的廣播接收器限制,是以您應該移除所有為隐式廣播 Intent 注冊的廣播接收器。
解決方案:使用動态廣播代替靜态廣播。
Only fullscreen opaque activities can request orientation
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
問題原因:Android 8.0 非全屏透明頁面不允許設定方向(後面8.1系統谷歌就去掉了這個限制,可能是真的沒必要)
解決方案:
- android:windowIsTranslucent設定為false。
- 如果還是想用的話,就去掉清單檔案中Activity中的android:screenOrientation=“portrait”。
- 使用透明的dialog或者PopupWindow來代替,也可以用DialogFragment,看自己的需求和喜好。
Android 9.0适配
CLEARTEXT communication to life.115.com not permitted by network security policy
CLEARTEXT communication to life.115.com not permitted by network security polic
問題原因: Android P 限制了明文流量的網絡請求,非加密的流量請求都會被系統禁止掉
解決方案:
- 在資源檔案建立xml目錄,建立檔案network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
- 清單檔案配置:
<application
android:networkSecurityConfig="@xml/network_security_config">
<!--Android 9.0加的-->
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
</application>
其他Api的修改
java.lang.IllegalArgumentException: Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed
if (Build.VERSION.SDK_INT >= 26)
canvas.clipPath(mPath);
else
canvas.clipPath(mPath, Region.Op.REPLACE);