前言
Google Play應(yīng)用市場對于應(yīng)用的targetSdkVersion有了更為嚴格的要求。從 2018 年 8 月 1 日起,所有向 Google Play 首次提交的新應(yīng)用都必須針對 Android 8.0 (API 等級 26) 開發(fā); 2018 年 11 月 1 日起,所有 Google Play 的現(xiàn)有應(yīng)用更新同樣必須針對 Android 8.0。轉(zhuǎn)載請注明來源「Bug總柴」
以下記錄了我們升級targetSdkVersion的坑以及解決辦法,希望對各位開發(fā)者有幫助。
錯誤1. java.lang.IllegalStateException: Not allowed to start service Intent {}: app is in background uid UidRecord{}
原因分析
從Android8.0開始,系統(tǒng)會對后臺執(zhí)行進行限制。初步判斷由于我們應(yīng)用在Application的onCreate過程中使用了IntentService來后臺初始化一些任務(wù),這個時候被系統(tǒng)認為是應(yīng)用還處于后臺,從而報出了java.lang.IllegalStateException錯誤。
解決辦法
解決后臺服務(wù)的限制,首先想到的辦法是將服務(wù)變成前臺服,隨即我們又遇到了另一個問題,見錯誤2
錯誤2. android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{}
原因分析
見Android8.0行為變更。新的 Context.startForegroundService() 函數(shù)將啟動一個前臺服務(wù)?,F(xiàn)在,即使應(yīng)用在后臺運行,系統(tǒng)也允許其調(diào)用 Context.startForegroundService()。不過,應(yīng)用必須在創(chuàng)建服務(wù)后的五秒內(nèi)調(diào)用該服務(wù)的 startForeground() 函數(shù)。
解決辦法
在后臺服務(wù)啟動執(zhí)行執(zhí)行之后,通過Service.startForeground()方法傳入notification變成前臺服務(wù)。需要注意的是從Android8.0開始,Notification必須制定Channel才可以正常彈出通知,如果創(chuàng)建Notification Channels詳見這里。
由于我們的初衷是在啟動程序的過程中后臺進行一些初始化,這種前臺給用戶帶來感知的效果并不是我們所希望的,因此我們考慮可以采用另一個后臺執(zhí)行任務(wù)的方法。這里官方推薦使用JobScheduler。由于我們引入了ktx以及WorkManager,這里我們采用了OneTimeWorkRequest來實現(xiàn)。具體實現(xiàn)如下:
class InitWorker : Worker(){
override fun doWork(): Result {
// 把耗時的啟動任務(wù)放在這里
return Result.SUCCESS
}
}
然后在Applicaiton的onCreate中調(diào)用
val initWork = OneTimeWorkRequestBuilder<InitWorker>().build()
WorkManager.getInstance().enqueue(initWork)
來執(zhí)行后臺初始化工作
錯誤3. java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/http/ProtocolVersion; Caused by: java.lang.ClassNotFoundException: Didn't find class "org.apache.http.ProtocolVersion"
原因分析
Android P Developer Preview的bug
解決辦法
在AndroidManifest.xml文件中<Application>標簽里面加入
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
錯誤4. java.io.IOException: Cleartext HTTP traffic to dict.youdao.com not permitted
原因分析
從Android 6.0開始引入了對Https的推薦支持,與以往不同,Android P的系統(tǒng)上面默認所有Http的請求都被阻止了。
<application android:usesCleartextTraffic=["true" | "false"]>
原本這個屬性的默認值從true改變?yōu)閒alse
解決辦法
解決的辦法簡單來說可以通過在AnroidManifest.xml中的application顯示設(shè)置
<application android:usesCleartextTraffic="true">
更為根本的解決辦法是修改應(yīng)用程序中Http的請求為Https,當然這也需要服務(wù)端的支持。
錯誤5. android.os.FileUriExposedException file exposed beyond app through Intent.getData()
原因分析
主要原因是7.0系統(tǒng)對file uri的暴露做了限制,加強了安全機制。詳見:官方文檔
代碼里出現(xiàn)問題的原因是,在需要安裝應(yīng)用的時候?qū)⑾螺d下來的安裝包地址傳給了application/vnd.android.package-archive的intent
解決辦法
使用FileProvider
具體代碼可參考這篇文章
簡單說明就是要在AndroidManifest里面聲明FileProvider,并且在xml中聲明需要使用的uri路徑
<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>
對應(yīng)的xml/file_paths中指定需要使用的目錄
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="download"
path="yddownload"/>
</paths>
錯誤6. java.lang.SecurityException: Failed to find provider ** for user 0; expected to find a valid ContentProvider for this authority
原因分析
target到android8.0之后對ContentResolver.notifyChange() 以及 registerContentObserver(Uri, boolean, ContentObserver)做了限制,官方解釋在這里
解決辦法
參考文章
簡單來說解決的辦法就是創(chuàng)建一個contentprovider,并在AndroidManifest里面注冊的provider的authority聲明為registerContentObserver中uri的authority就可以了。
<provider
android:name=".download.DownloadUriProvider"
android:authorities="${applicationId}"
android:enabled="true"
android:exported="false"/>
public class DownloadUriProvider extends ContentProvider {
public DownloadUriProvider() {
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public boolean onCreate() {
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}
錯誤7. notification沒有顯示
原因分析
如果targetsdkversion設(shè)定為26或以上,開始要求notification必須知道channel,具體查閱這里。
解決辦法
在notify之前先創(chuàng)建notificationChannel
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "下載提醒";
String description = "顯示下載過程及進度";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(DOWNLOAD_CHANNEL_ID, name, importance);
channel.setDescription(description);
mNotificationManager.createNotificationChannel(channel);
}
}
錯誤8. 在AndroidManifest中注冊的receiver不能收到廣播
原因分析
針對targetsdkversion為26的應(yīng)用,加強對匿名receiver的控制,以至于在manifest中注冊的隱式receiver都失效。具體見官方原文
解決辦法
將廣播從在AndroidManifest中注冊移到在Activity中使用registerReceiver注冊
錯誤9. 無法通過“application/vnd.android.package-archive” action安裝應(yīng)用
原因分析
targetsdkversion大于25必須聲明REQUEST_INSTALL_PACKAGES權(quán)限,見官方說明:
REQUEST_INSTALL_PACKAGES
解決辦法
在AndroidManifest中加入
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
錯誤10. java.lang.RuntimeException: Unable to start activity ComponentInfo{xxxActivity}: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
原因分析
targetsdk26以上,對于透明主題的activity不能夠通過manifest設(shè)定android:screenOrientation。
具體分析見這里
解決辦法
檢查報錯的Activity是否在AndroidManifest中聲明了
,若有需要將其去除。