赞
踩
在android 9.0自动下载更新时 遇到 安装包解析错误
在android 7.0使用的是同一套框架却没有问题?
然后通过adb命令抓取一下异常发现
12-18 19:21:32.665 4804 5604 W InstallStaging: java.lang.SecurityException: Permission Denial: reading android.support.v4.content.FileProvider uri content://com.***.***.update_app.file_provider/download/update/***.apk from pid=4804, uid=1000 requires the provider be exported, or grantUriPermission() 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.os.Parcel.createException(Parcel.java:1950) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.os.Parcel.readException(Parcel.java:1918) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:146) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:698) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1459) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1296) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.content.ContentResolver.openInputStream(ContentResolver.java:1016) 12-18 19:21:32.665 4804 5604 W InstallStaging: at com.android.packageinstaller.InstallStaging$StagingAsyncTask.doInBackground(InstallStaging.java:167) 12-18 19:21:32.665 4804 5604 W InstallStaging: at com.android.packageinstaller.InstallStaging$StagingAsyncTask.doInBackground(InstallStaging.java:160) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.os.AsyncTask$2.call(AsyncTask.java:333) 12-18 19:21:32.665 4804 5604 W InstallStaging: at java.util.concurrent.FutureTask.run(FutureTask.java:266) 12-18 19:21:32.665 4804 5604 W InstallStaging: at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) 12-18 19:21:32.665 4804 5604 W InstallStaging: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 12-18 19:21:32.665 4804 5604 W InstallStaging: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 12-18 19:21:32.665 4804 5604 W InstallStaging: at java.lang.Thread.run(Thread.java:764)
发现Android9.0是需要权限读取fileProvider和uri的
于是我看了一下三方框架的源码,确实没有加权限
private Intent installIntent(Context context, String path) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addCategory(Intent.CATEGORY_DEFAULT);
String fileProviderAuthority = getFileProviderAuthority(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && null!= fileProviderAuthority) {
Uri fileUri = FileProvider.getUriForFile(context, fileProviderAuthority, new File(path));
intent.setDataAndType(fileUri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive");
}
return intent;
}
于是我就扒了一下源码对其进行修改
private Intent installIntent(Context context, String path) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_DEFAULT); String fileProviderAuthority = getFileProviderAuthority(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && null!= fileProviderAuthority) { Uri fileUri = FileProvider.getUriForFile(context, fileProviderAuthority, new File(path)); intent.setDataAndType(fileUri, "application/vnd.android.package-archive"); grantUriPermission(getPackageName(), fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); } else { intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive"); } return intent; }
测试通过,完美安装,没有出现解析失败问题
代码:
<!--AndroidManifest.xml--> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.INTERNET"/> <provider android:name="android.support.v4.content.FileProvider" android:authorities="包名.file_provider" android:exported="false" android:grantUriPermissions="true"> <!-- 元数据 --> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" tools:replace="android:resource" /> </provider> <service android:name=".UpdateService"/>//从源码下载的升级下载安装apk的服务
<!-- file_paths.xml -->
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="." name="download" />
<external-cache-path
name="storage/emulated/0"
path="." />
</paths>
/** * Created by Sun on 2020/12/19. */ public class UpdateService extends Service { public static final String TAG = "UpdateService"; public static final String ACTION = "me.shenfan.UPDATE_APP"; public static final String STATUS = "status"; public static final String PROGRESS = "progress"; public static boolean DEBUG = false; //下载大小通知频率 public static final int UPDATE_NUMBER_SIZE = 1; public static final int DEFAULT_RES_ID = -1; public static final int UPDATE_PROGRESS_STATUS = 0; public static final int UPDATE_ERROR_STATUS = -1; public static final int UPDATE_SUCCESS_STATUS = 1; //params private static final String URL = "downloadUrl"; private static final String ICO_RES_ID = "icoResId"; private static final String ICO_SMALL_RES_ID = "icoSmallResId"; private static final String UPDATE_PROGRESS = "updateProgress"; private static final String STORE_DIR = "storeDir"; private static final String DOWNLOAD_NOTIFICATION_FLAG = "downloadNotificationFlag"; private static final String DOWNLOAD_SUCCESS_NOTIFICATION_FLAG = "downloadSuccessNotificationFlag"; private static final String DOWNLOAD_ERROR_NOTIFICATION_FLAG = "downloadErrorNotificationFlag"; private static final String IS_SEND_BROADCAST = "isSendBroadcast"; private String downloadUrl; private int icoResId; //default app ico private int icoSmallResId; private int updateProgress; //update notification progress when it add number private String storeDir; //default sdcard/Android/package/update private int downloadNotificationFlag; private int downloadSuccessNotificationFlag; private int downloadErrorNotificationFlag; private boolean isSendBroadcast; private UpdateProgressListener updateProgressListener; private LocalBinder localBinder = new LocalBinder(); /** * Class used for the client Binder. */ public class LocalBinder extends Binder { /** * set update progress call back * * @param listener */ public void setUpdateProgressListener(UpdateProgressListener listener) { UpdateService.this.setUpdateProgressListener(listener); } } private boolean startDownload;//开始下载 private int lastProgressNumber; private NotificationCompat.Builder builder; private NotificationManager manager; private int notifyId; private String appName; private LocalBroadcastManager localBroadcastManager; private Intent localIntent; private DownloadApk downloadApkTask; /** * whether debug */ public static void debug() { DEBUG = true; } private Intent installIntent(Context context, String path) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addCategory(Intent.CATEGORY_DEFAULT); String fileProviderAuthority = getFileProviderAuthority(context); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && null!= fileProviderAuthority) { Uri fileUri = FileProvider.getUriForFile(context, fileProviderAuthority, new File(path)); intent.setDataAndType(fileUri, "application/vnd.android.package-archive"); grantUriPermission(getPackageName(), fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); } else { intent.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive"); } return intent; } /** * 获取FileProvider的auth */ private static String getFileProviderAuthority(Context context) { try { for (ProviderInfo provider : context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PROVIDERS).providers) { if (FileProvider.class.getName().equals(provider.name) && provider.authority.endsWith(".update_app.file_provider")) { return provider.authority; } } } catch (PackageManager.NameNotFoundException ignore) { } return null; } private static Intent webLauncher(String downloadUrl) { Uri download = Uri.parse(downloadUrl); Intent intent = new Intent(Intent.ACTION_VIEW, download); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } private static String getSaveFileName(String downloadUrl) { if (downloadUrl == null || TextUtils.isEmpty(downloadUrl)) { return System.currentTimeMillis() + ".apk"; } return downloadUrl.substring(downloadUrl.lastIndexOf("/")); } private static File getDownloadDir(UpdateService service) { File downloadDir = null; if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { if (service.storeDir != null) { downloadDir = new File(Environment.getExternalStorageDirectory(), service.storeDir); } else { downloadDir = new File(service.getExternalCacheDir(), "update"); } } else { downloadDir = new File(service.getCacheDir(), "update"); } if (!downloadDir.exists()) { downloadDir.mkdirs(); } return downloadDir; } @Override public void onCreate() { super.onCreate(); appName = getApplicationName(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (!startDownload && intent != null) { startDownload = true; downloadUrl = intent.getStringExtra(URL); icoResId = intent.getIntExtra(ICO_RES_ID, DEFAULT_RES_ID); icoSmallResId = intent.getIntExtra(ICO_SMALL_RES_ID, DEFAULT_RES_ID); storeDir = intent.getStringExtra(STORE_DIR); updateProgress = intent.getIntExtra(UPDATE_PROGRESS, UPDATE_NUMBER_SIZE); downloadNotificationFlag = intent.getIntExtra(DOWNLOAD_NOTIFICATION_FLAG, 0); downloadErrorNotificationFlag = intent.getIntExtra(DOWNLOAD_ERROR_NOTIFICATION_FLAG, 0); downloadSuccessNotificationFlag = intent.getIntExtra(DOWNLOAD_SUCCESS_NOTIFICATION_FLAG, 0); isSendBroadcast = intent.getBooleanExtra(IS_SEND_BROADCAST, false); if (DEBUG) { Log.d(TAG, "downloadUrl: " + downloadUrl); Log.d(TAG, "icoResId: " + icoResId); Log.d(TAG, "icoSmallResId: " + icoSmallResId); Log.d(TAG, "storeDir: " + storeDir); Log.d(TAG, "updateProgress: " + updateProgress); Log.d(TAG, "downloadNotificationFlag: " + downloadNotificationFlag); Log.d(TAG, "downloadErrorNotificationFlag: " + downloadErrorNotificationFlag); Log.d(TAG, "downloadSuccessNotificationFlag: " + downloadSuccessNotificationFlag); Log.d(TAG, "isSendBroadcast: " + isSendBroadcast); } notifyId = startId; buildNotification(); buildBroadcast(); downloadApkTask = new DownloadApk(this); downloadApkTask.execute(downloadUrl); } return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return localBinder; } @Override public boolean onUnbind(Intent intent) { return true; } public void setUpdateProgressListener(UpdateProgressListener updateProgressListener) { this.updateProgressListener = updateProgressListener; } @Override public void onDestroy() { if (downloadApkTask != null) { downloadApkTask.cancel(true); } if (updateProgressListener != null) { updateProgressListener = null; } localIntent = null; builder = null; super.onDestroy(); } public String getApplicationName() { PackageManager packageManager = null; ApplicationInfo applicationInfo = null; try { packageManager = getApplicationContext().getPackageManager(); applicationInfo = packageManager.getApplicationInfo(getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { applicationInfo = null; } String applicationName = (String) packageManager.getApplicationLabel(applicationInfo); return applicationName; } private void buildBroadcast() { if (!isSendBroadcast) { return; } localBroadcastManager = LocalBroadcastManager.getInstance(this); localIntent = new Intent(ACTION); } private void sendLocalBroadcast(int status, int progress) { if (!isSendBroadcast || localIntent == null) { return; } localIntent.putExtra(STATUS, status); localIntent.putExtra(PROGRESS, progress); localBroadcastManager.sendBroadcast(localIntent); } private void buildNotification() { manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); builder = new NotificationCompat.Builder(this); builder.setContentTitle(getString(R.string.update_app_model_prepare, appName)) .setWhen(System.currentTimeMillis()) .setProgress(100, 1, false) .setSmallIcon(icoSmallResId) .setLargeIcon(BitmapFactory.decodeResource( getResources(), icoResId)) .setDefaults(downloadNotificationFlag); manager.notify(notifyId, builder.build()); } private void start() { builder.setContentTitle(appName); builder.setContentText(getString(R.string.update_app_model_progress, 1, "%")); //setChannelId 必须添加否则不能在通知栏显示(Android 8.0) builder.setChannelId(getPackageName()); manager.notify(notifyId, builder.build()); sendLocalBroadcast(UPDATE_PROGRESS_STATUS, 1); if (updateProgressListener != null) { updateProgressListener.start(); } } /** * @param progress download percent , max 100 */ private void update(int progress) { if (progress - lastProgressNumber > updateProgress) { lastProgressNumber = progress; builder.setProgress(100, progress, false); builder.setContentText(getString(R.string.update_app_model_progress, progress, "%")); manager.notify(notifyId, builder.build()); sendLocalBroadcast(UPDATE_PROGRESS_STATUS, progress); if (updateProgressListener != null) { updateProgressListener.update(progress); } } } private void success(String path) { builder.setProgress(0, 0, false); builder.setContentText(getString(R.string.update_app_model_success)); Intent i = installIntent(this, path); PendingIntent intent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(intent); builder.setDefaults(downloadSuccessNotificationFlag); Notification n = builder.build(); n.contentIntent = intent; manager.notify(notifyId, n); sendLocalBroadcast(UPDATE_SUCCESS_STATUS, 100); if (updateProgressListener != null) { updateProgressListener.success(); } startActivity(i); stopSelf(); } private void error() { Intent i = webLauncher(downloadUrl); PendingIntent intent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentText(getString(R.string.update_app_model_error)); builder.setContentIntent(intent); builder.setProgress(0, 0, false); builder.setDefaults(downloadErrorNotificationFlag); Notification n = builder.build(); n.contentIntent = intent; manager.notify(notifyId, n); sendLocalBroadcast(UPDATE_ERROR_STATUS, -1); if (updateProgressListener != null) { updateProgressListener.error(); } stopSelf(); } private static class DownloadApk extends AsyncTask<String, Integer, String> { private WeakReference<UpdateService> updateServiceWeakReference; public DownloadApk(UpdateService service) { updateServiceWeakReference = new WeakReference<>(service); } @Override protected void onPreExecute() { super.onPreExecute(); UpdateService service = updateServiceWeakReference.get(); if (service != null) { service.start(); } } @Override protected String doInBackground(String... params) { final String downloadUrl = params[0]; final File file = new File(UpdateService.getDownloadDir(updateServiceWeakReference.get()), UpdateService.getSaveFileName(downloadUrl)); if (DEBUG) { Log.d(TAG, "download url is " + downloadUrl); Log.d(TAG, "download apk cache at " + file.getAbsolutePath()); } File dir = file.getParentFile(); if (!dir.exists()) { dir.mkdirs(); } HttpURLConnection httpConnection = null; InputStream is = null; FileOutputStream fos = null; int updateTotalSize = 0; URL url; try { url = new URL(downloadUrl); httpConnection = (HttpURLConnection) url.openConnection(); httpConnection.setConnectTimeout(20000); httpConnection.setReadTimeout(20000); if (DEBUG) { Log.d(TAG, "download status code: " + httpConnection.getResponseCode()); } if (httpConnection.getResponseCode() != 200) { return null; } updateTotalSize = httpConnection.getContentLength(); if (file.exists()) { if (updateTotalSize == file.length()) { // 下载完成 return file.getAbsolutePath(); } else { file.delete(); } } file.createNewFile(); is = httpConnection.getInputStream(); fos = new FileOutputStream(file, false); byte buffer[] = new byte[4096]; int readSize = 0; int currentSize = 0; while ((readSize = is.read(buffer)) > 0) { fos.write(buffer, 0, readSize); currentSize += readSize; publishProgress((currentSize * 100 / updateTotalSize)); } // download success } catch (Exception e) { e.printStackTrace(); return null; } finally { if (httpConnection != null) { httpConnection.disconnect(); } if (is != null) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } return file.getAbsolutePath(); } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (DEBUG) { Log.d(TAG, "current progress is " + values[0]); } UpdateService service = updateServiceWeakReference.get(); if (service != null) { service.update(values[0]); } } @Override protected void onPostExecute(String s) { super.onPostExecute(s); UpdateService service = updateServiceWeakReference.get(); if (service != null) { if (s != null) { service.success(s); } else { service.error(); } } } } /** * a builder class helper use UpdateService */ public static class Builder { private String downloadUrl; private int icoResId = DEFAULT_RES_ID; //default app ico private int icoSmallResId = DEFAULT_RES_ID; private int updateProgress = UPDATE_NUMBER_SIZE; //update notification progress when it add number private String storeDir; //default sdcard/Android/package/update private int downloadNotificationFlag; private int downloadSuccessNotificationFlag; private int downloadErrorNotificationFlag; private boolean isSendBroadcast; protected Builder(String downloadUrl) { this.downloadUrl = downloadUrl; } public static Builder create(String downloadUrl) { if (downloadUrl == null) { throw new NullPointerException("downloadUrl == null"); } return new Builder(downloadUrl); } public String getDownloadUrl() { return downloadUrl; } public int getIcoResId() { return icoResId; } public Builder setIcoResId(int icoResId) { this.icoResId = icoResId; return this; } public int getIcoSmallResId() { return icoSmallResId; } public Builder setIcoSmallResId(int icoSmallResId) { this.icoSmallResId = icoSmallResId; return this; } public int getUpdateProgress() { return updateProgress; } public Builder setUpdateProgress(int updateProgress) { if (updateProgress < 1) { throw new IllegalArgumentException("updateProgress < 1"); } this.updateProgress = updateProgress; return this; } public String getStoreDir() { return storeDir; } public Builder setStoreDir(String storeDir) { this.storeDir = storeDir; return this; } public int getDownloadNotificationFlag() { return downloadNotificationFlag; } public Builder setDownloadNotificationFlag(int downloadNotificationFlag) { this.downloadNotificationFlag = downloadNotificationFlag; return this; } public int getDownloadSuccessNotificationFlag() { return downloadSuccessNotificationFlag; } public Builder setDownloadSuccessNotificationFlag(int downloadSuccessNotificationFlag) { this.downloadSuccessNotificationFlag = downloadSuccessNotificationFlag; return this; } public int getDownloadErrorNotificationFlag() { return downloadErrorNotificationFlag; } public Builder setDownloadErrorNotificationFlag(int downloadErrorNotificationFlag) { this.downloadErrorNotificationFlag = downloadErrorNotificationFlag; return this; } public boolean isSendBroadcast() { return isSendBroadcast; } public Builder setIsSendBroadcast(boolean isSendBroadcast) { this.isSendBroadcast = isSendBroadcast; return this; } public Builder build(Context context) { if (context == null) { throw new NullPointerException("context == null"); } Intent intent = new Intent(); intent.setClass(context, UpdateService.class); intent.putExtra(URL, downloadUrl); if (icoResId == DEFAULT_RES_ID) { icoResId = getIcon(context); } if (icoSmallResId == DEFAULT_RES_ID) { icoSmallResId = icoResId; } intent.putExtra(ICO_RES_ID, icoResId); intent.putExtra(STORE_DIR, storeDir); intent.putExtra(ICO_SMALL_RES_ID, icoSmallResId); intent.putExtra(UPDATE_PROGRESS, updateProgress); intent.putExtra(DOWNLOAD_NOTIFICATION_FLAG, downloadNotificationFlag); intent.putExtra(DOWNLOAD_SUCCESS_NOTIFICATION_FLAG, downloadSuccessNotificationFlag); intent.putExtra(DOWNLOAD_ERROR_NOTIFICATION_FLAG, downloadErrorNotificationFlag); intent.putExtra(IS_SEND_BROADCAST, isSendBroadcast); context.startService(intent); return this; } private int getIcon(Context context) { final PackageManager packageManager = context.getPackageManager(); ApplicationInfo appInfo = null; try { appInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } if (appInfo != null) { return appInfo.icon; } return 0; } } }
/** * 下载回调 */ public interface UpdateProgressListener { /** * download start */ void start(); /** * update download progress * @param progress */ void update(int progress); /** * download success */ void success(); /** * download error */ void error(); }
UpdateService.Builder.create("服务器给的下载路径")
.setStoreDir("下载路径")//default sdcard/Android/package/update
.setIsSendBroadcast(true)
.setIcoResId(android.R.drawable.ic_notification_clear_all)
.setIcoSmallResId(android.R.drawable.ic_notification_overlay)
.build(context);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。