前言
新年第一篇總結(jié)與分享~
離19年新年就剩一個(gè)月左右了扔涧,整個(gè)公司的項(xiàng)目也不像以往一樣緊張、忙碌缀台,周末同事和朋友聊得最多的就是搶票和年終獎(jiǎng)了(然而我們公司并沒有??)幔翰,為自己心疼一分鐘京髓。烈和。。
總結(jié)一下18年吧叫惊,主要負(fù)責(zé)了4個(gè)商業(yè)項(xiàng)目帝洪,還參與了公司 Android 新框架的封裝似舵,整體來說算是穩(wěn)步提高吧。
先說一下寫這篇文章的背景吧葱峡,主要就是不太忙了砚哗,再加上客戶要求適配 Android 9.0 (第一個(gè)客戶主動(dòng)提出來的),在看完9.0的適配之后砰奕,也對(duì)之前的進(jìn)行了一些整理蛛芥,廢話不多說提鸟,請(qǐng)看干貨!
Android6.0適配
權(quán)限適配
動(dòng)態(tài)權(quán)限適配是 Android 6.0 最先開始的仅淑,也是 Android 系統(tǒng)對(duì)開發(fā)者影響最大的改動(dòng)之一称勋。
權(quán)限適配三連問
- Q: 是否 Android 6.0 所有權(quán)限都需要?jiǎng)討B(tài)申請(qǐng)?
- A: 不是涯竟。只有屬于危險(xiǎn)權(quán)限的才需要申請(qǐng)赡鲜。
- Q: 危險(xiǎn)權(quán)限有哪些?
- A: 見下面“危險(xiǎn)權(quán)限分組說明”
- Q: 危險(xiǎn)權(quán)限是否需要一個(gè)一個(gè)申請(qǐng)庐船?
- A: 在 Android 6.0 ~ Android 8.0银酬,不需要。如果應(yīng)用在運(yùn)行時(shí)請(qǐng)求權(quán)限并且被授予該權(quán)限醉鳖,系統(tǒng)會(huì)錯(cuò)誤地將屬于同一權(quán)限組并且在清單中注冊(cè)的其他權(quán)限也一起授予應(yīng)用捡硅,即對(duì)于同一組內(nèi)的權(quán)限,只要有一個(gè)被同意盗棵,其他的都會(huì)被同意壮韭。在 Android 8.0 之后,此行為已被糾正纹因。系統(tǒng)只會(huì)授予應(yīng)用明確請(qǐng)求的權(quán)限喷屋。然而一旦用戶為應(yīng)用授予某個(gè)權(quán)限,則所有后續(xù)對(duì)該權(quán)限組中權(quán)限的請(qǐng)求都將被自動(dòng)批準(zhǔn)瞭恰,但是若沒有請(qǐng)求相應(yīng)的權(quán)限而進(jìn)行操作的話就會(huì)出現(xiàn)應(yīng)用 crash 的情況屯曹。
危險(xiǎn)權(quán)限分組說明
權(quán)限組 | 權(quán)限名稱 |
---|---|
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 |
對(duì)應(yīng)在清單文件中的展示如下:
<!--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適配
應(yīng)用間共享文件
在 targetSdkVersion 大于等于的 24 的 app 中,但是我們沒有去適配 Android 7.0惊畏。那么在調(diào)用安裝頁面恶耽,或修改用戶頭像操作時(shí),就會(huì)失敗颜启。那么就需要你去適配 Android 7.0或是將 targetSdkVersion 改為 24 以下(不推薦)偷俭。適配的方法這里就不細(xì)講,大家可以看鴻洋大神的 Android 7.0 行為變更 通過FileProvider在應(yīng)用間共享文件這篇文章缰盏。
APK signature scheme v2
Android 7.0 引入一項(xiàng)新的應(yīng)用簽名方案 APK Signature Scheme v2涌萤,它能提供更快的應(yīng)用安裝時(shí)間和更多針對(duì)未授權(quán) APK 文件更改的保護(hù)。在默認(rèn)情況下口猜,Android Studio 2.2 和 Android Plugin for Gradle 2.2 會(huì)使用 APK Signature Scheme v2 和傳統(tǒng)簽名方案來簽署您的應(yīng)用负溪。
說明:
- 只勾選V1簽名就是傳統(tǒng)方案簽署,但是在 Android 7.0 上不會(huì)使用V2安全的驗(yàn)證方式济炎。
- 只勾選V2簽名7.0以下會(huì)顯示未安裝川抡,Android 7.0 上則會(huì)使用了V2安全的驗(yàn)證方式。
- 同時(shí)勾選V1和V2則所有版本都沒問題冻辩。
org.apache不支持問題
// build.gradle里面加上這句話
defaultConfig {
useLibrary 'org.apache.http.legacy'
}
SharedPreferences閃退
// MODE_WORLD_READABLE:Android 7.0以后不能使用這個(gè)獲取猖腕,會(huì)閃退
// 應(yīng)修改成MODE_PRIVATE
SharedPreferences read = getSharedPreferences(RELEASE_POOL_DATA, MODE_WORLD_READABLE);
Android 8.0適配
Android 8.0中PHONE權(quán)限組新增兩個(gè)權(quán)限
ANSWER_PHONE_CALLS:允許您的應(yīng)用通過編程方式接聽呼入電話拆祈。要在您的應(yīng)用中處理呼入電話,您可以使用 acceptRingingCall() 函數(shù)倘感。
READ_PHONE_NUMBERS:權(quán)限允許您的應(yīng)用讀取設(shè)備中存儲(chǔ)的電話號(hào)碼放坏。
通知適配
Android 8.0中,為了更好的管制通知的提醒老玛,不想一些不重要的通知打擾用戶淤年,新增了通知渠道,用戶可以根據(jù)渠道來屏蔽一些不想要的通知蜡豹。
代碼示例如下:
/**
* 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)
{
// 第三個(gè)參數(shù)設(shè)置通知的優(yōu)先級(jí)別
NotificationChannel channel = new NotificationChannel(channeId, channelName, NotificationManager.IMPORTANCE_DEFAULT);
// 是否可以繞過請(qǐng)勿打擾模式
channel.canBypassDnd();
// 是否可以顯示icon角標(biāo)
channel.canShowBadge();
// 是否顯示通知閃燈
channel.enableLights(true);
// 收到消息時(shí)震動(dòng)提示
channel.enableVibration(true);
// 設(shè)置繞過免打擾
channel.setBypassDnd(true);
channel.setLockscreenVisibility(NotificationCompat.VISIBILITY_SECRET);
// 設(shè)置閃光燈顏色
channel.setLightColor(Color.RED);
// 獲取設(shè)置鈴聲設(shè)置
channel.getAudioAttributes();
// 設(shè)置震動(dòng)模式
channel.setVibrationPattern(new long[]{100, 200, 100});
// 是否會(huì)閃光
channel.shouldShowLights();
if (manager != null)
manager.createNotificationChannel(channel);
if (builder != null)
builder.setChannelId(channeId);//這個(gè)id參數(shù)要與上面channel構(gòu)建的第一個(gè)參數(shù)對(duì)應(yīng)
}
}
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);
// // 如果存在則清除上一個(gè)消息
// manager.cancel(lastNotificationId);
builder = new NotificationCompat.Builder(context, MyNotification.CHANNEL_ID_GL);
MyNotification.setONotifyChannel(manager, builder, MyNotification.CHANNEL_ID_GL, MyNotification.CHANNEL_NAME_GL);
// 設(shè)置標(biāo)題
builder.setContentTitle("Title");
// 狀態(tài)欄的動(dòng)畫提醒語句
builder.setTicker("Ticker");
// 什么時(shí)候提醒
builder.setWhen(System.currentTimeMillis());
// 設(shè)置通知欄的優(yōu)先級(jí)
builder.setPriority(Notification.PRIORITY_DEFAULT);
// 設(shè)置點(diǎn)擊可消失
builder.setAutoCancel(true);
// 設(shè)置是否震動(dòng)等
builder.setDefaults(Notification.DEFAULT_VIBRATE);
// 設(shè)置icon
builder.setSmallIcon(R.mipmap.ic_logo);
// 設(shè)置點(diǎn)擊意圖
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)
{
// 設(shè)置內(nèi)容
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的閃退
問題原因:項(xiàng)目使用了ActiveAndroid麸粮,在 8.0 或 8.1 系統(tǒng)上使用 26 或以上的版本的 SDK 時(shí),調(diào)用 ContentResolver 的 notifyChange 方法通知數(shù)據(jù)更新镜廉,或者調(diào)用 ContentResolver 的 registerContentObserver 方法監(jiān)聽數(shù)據(jù)變化時(shí)弄诲,會(huì)出現(xiàn)上述異常。
解決方案:
- 在清單文件配置:
<provider
android:name="com.activeandroid.content.ContentProvider"
android:authorities="com.jz.androidclient"
android:enabled="true"
android:exported="false"/>
- 去掉這個(gè)監(jiān)聽刷新的方法娇唯,改為廣播刷新
靜態(tài)廣播無法正常接收
問題原因:Android 8.0 引入了新的廣播接收器限制齐遵,因此您應(yīng)該移除所有為隱式廣播 Intent 注冊(cè)的廣播接收器。
解決方案:使用動(dòng)態(tài)廣播代替靜態(tài)廣播塔插。
Only fullscreen opaque activities can request orientation
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
問題原因:Android 8.0 非全屏透明頁面不允許設(shè)置方向(后面8.1系統(tǒng)谷歌就去掉了這個(gè)限制梗摇,可能是真的沒必要)
解決方案:
- android:windowIsTranslucent設(shè)置為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 限制了明文流量的網(wǎng)絡(luò)請(qǐng)求,非加密的流量請(qǐng)求都會(huì)被系統(tǒng)禁止掉
解決方案:
- 在資源文件新建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);