原貼:https://www.jianshu.com/p/ea42040c7acejava
原貼:https://www.jianshu.com/p/ea42040c7aceandroid
原貼:https://www.jianshu.com/p/ea42040c7aceapi
在線更新分爲如下幾個步驟:併發
1, 經過接口獲取線上版本號,versionCode 2, 比較線上的versionCode 和本地的versionCode,彈出更新窗口 3, 下載APK文件(文件下載) 4,安裝APK
首先建立 UpdateDownloadListener 接口類對下載的不一樣狀態回調:app
/** * @desciption: 下載不一樣狀態接口回調 */ public interface UpdateDownloadListener { /** * 下載請求開始回調 */ void onStart(); /** * 請求成功,下載前的準備回調 * * @param contentLength 文件長度 * @param downloadUrl 下載地址 */ void onPrepared(long contentLength, String downloadUrl); /** * 進度更新回調 * * @param progress 進度 * @param downloadUrl 下載地址 */ void onProgressChanged(int progress, String downloadUrl); /** * 下載暫停回調 * * @param progress 進度 * @param completeSize 已下載的文件大小 * @param downloadUrl 下載地址 */ void onPaused(int progress, int completeSize, String downloadUrl); /** * 下載完成回調 * * @param completeSize 已下載的文件大小 * @param downloadUrl 下載地址 */ void onFinished(int completeSize, String downloadUrl); /** * 下載失敗回調 */ void onFailure(); }
而後建立 UpdateDownloadRequest 類負責處理文件的下載和線程間的通訊:dom
/** * @desciption: 負責處理文件的下載和線程間的通訊 */ public class UpdateDownloadRequest implements Runnable { /** * 開始下載的位置 */ private int startPos = 0; /** * 下載地址 */ private String downloadUrl; /** * 文件保存路徑 */ private String localFilePath; /** * 事件回調 */ private UpdateDownloadListener mDownloadListener; private DownloadResponseHandler mDownloadHandler; /** * 下載標誌位 */ private boolean isDownloading = false; /** * 文件長度 */ private long mContentLength; public UpdateDownloadRequest(String downloadUrl, String localFilePath, UpdateDownloadListener downloadListener) { this.downloadUrl = downloadUrl; this.localFilePath = localFilePath; mDownloadListener = downloadListener; mDownloadHandler = new DownloadResponseHandler(); isDownloading = true; } @Override public void run() { try { makeRequest(); } catch (IOException e) { if (mDownloadHandler != null) { mDownloadHandler.sendFailureMessage(FailureCode.IO); } } catch (InterruptedException e) { if (mDownloadHandler != null) { mDownloadHandler.sendFailureMessage(FailureCode.Interrupted); } } } /** * 創建鏈接 */ private void makeRequest() throws IOException, InterruptedException { if (!Thread.currentThread().isInterrupted()) { try { URL url = new URL(downloadUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(5000); connection.setRequestMethod("GET"); connection.setRequestProperty("Range", "bytes=" + startPos + "-"); connection.setRequestProperty("Connection", "Keep-Alive"); //阻塞當前線程 connection.connect(); mContentLength = connection.getContentLength(); if (!Thread.currentThread().isInterrupted()) { if (mDownloadHandler != null) { //完成文件下載,取得與遠程文件的流 mDownloadHandler.sendResponseMessage(connection.getInputStream()); } } } catch (IOException e) { if (!Thread.currentThread().isInterrupted()) { throw e; } } } } public boolean isDownloading() { return isDownloading; } public void stopDownloading() { isDownloading = false; } /** * 下載過程當中全部可能出現的異常狀況 */ public enum FailureCode { // UnknownHost, Socket, SocketTimeout, ConnectTimeout, IO, HttpResponse, JSON, Interrupted } /** * 下載文件,併發送消息和回調接口 */ public class DownloadResponseHandler { protected static final int SUCCESS_MESSAGE = 0; protected static final int FAILURE_MESSAGE = 1; protected static final int START_MESSAGE = 2; protected static final int FINISH_MESSAGE = 3; protected static final int NETWORK_OFF = 4; protected static final int PROGRESS_CHANGED = 5; protected static final int PAUSED_MESSAGE = 6; private Handler mHandler; private int mCompleteSize = 0; private int progress = 0; public DownloadResponseHandler() { if (Looper.myLooper() != null) { mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); handleSelfMessage(msg); } }; } } /** * 發送暫停消息 */ private void sendPausedMessage() { sendMessage(obtainMessage(PAUSED_MESSAGE, null)); } /** * 發送失敗消息 */ protected void sendFailureMessage(FailureCode failureCode) { sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode})); } private void sendMessage(Message message) { if (mHandler != null) { mHandler.sendMessage(message); } else { handleSelfMessage(message); } } private Message obtainMessage(int responseMessage, Object response) { Message msg; if (mHandler != null) { msg = mHandler.obtainMessage(responseMessage, response); } else { msg = Message.obtain(); msg.what = responseMessage; msg.obj = response; } return msg; } private void handleSelfMessage(Message message) { Object[] response; switch (message.what) { default: break; case FAILURE_MESSAGE: response = (Object[]) message.obj; handleFailureMessage((FailureCode) response[0]); break; case PROGRESS_CHANGED: response = (Object[]) message.obj; handleProgressChangedMessage((Integer) response[0]); break; case PAUSED_MESSAGE: handlePausedMessage(); break; case FINISH_MESSAGE: onFinish(); break; } } /** * 失敗消息的處理邏輯 */ protected void handleFailureMessage(FailureCode failureCode) { onFailure(failureCode); } /** * 進度改變消息的處理邏輯 */ protected void handleProgressChangedMessage(int progress) { mDownloadListener.onProgressChanged(progress, ""); } /** * 暫停消息的處理邏輯 */ protected void handlePausedMessage() { mDownloadListener.onPaused(progress, mCompleteSize, ""); } /** * 外部接口完成的回調 */ public void onFinish() { mDownloadListener.onFinished(mCompleteSize, ""); } /** * 外部接口失敗的回調 */ public void onFailure(FailureCode failureCode) { mDownloadListener.onFailure(); } /** * 文件下載方法,發送各類類型的事件 */ public void sendResponseMessage(InputStream inputStream) { //文件讀寫流 RandomAccessFile randomAccessFile = null; mCompleteSize = 0; try { byte[] buffer = new byte[1024]; int length = -1; int limit = 0; randomAccessFile = new RandomAccessFile(localFilePath, "rwd"); randomAccessFile.seek(startPos); boolean isPaused = false; while ((length = inputStream.read(buffer)) != -1) { if (isDownloading) { randomAccessFile.write(buffer, 0, length); mCompleteSize += length; if ((startPos + mCompleteSize) < (mContentLength + startPos)) { progress = (int) (Float.parseFloat(getToPointFloatStr( (float) (startPos + mCompleteSize) / (mContentLength + startPos))) * 100); //限制notification更新頻率 if (limit % 30 == 0 || progress == 100) { //在子線程中讀取流數據,後轉發到主線程中去。 sendProgressChangedMessage(progress); } } limit++; } else { isPaused = true; sendPausedMessage(); break; } } stopDownloading(); if (!isPaused) { sendFinishMessage(); } } catch (IOException e) { sendPausedMessage(); stopDownloading(); e.printStackTrace(); } finally { try { if (inputStream != null) { inputStream.close(); } if (randomAccessFile != null) { randomAccessFile.close(); } } catch (IOException e) { stopDownloading(); e.printStackTrace(); } } } /** * 數字格式化 */ private String getToPointFloatStr(float value) { DecimalFormat format = new DecimalFormat("0.00"); return format.format(value); } /** * 發送進度改變消息 */ private void sendProgressChangedMessage(int progress) { sendMessage(obtainMessage(PROGRESS_CHANGED, new Object[]{progress})); } /** * 發送完成消息 */ protected void sendFinishMessage() { sendMessage(obtainMessage(FINISH_MESSAGE, null)); } } }
而後建立 UpdateManager 類下載調度管理器,調用UpdateDownloadRequest:ide
/** * @desciption: 下載調度管理器,調用UpdateDownloadRequest */ public class UpdateManager { /** * 線程池 */ private ExecutorService mExecutorService; private UpdateDownloadRequest mDownloadRequest; private UpdateManager() { //建立cache線程池 mExecutorService = Executors.newCachedThreadPool(); } public static UpdateManager getInstance() { return Holder.INSTANCE; } public void startDownload(String downloadUrl, String localFilePath, UpdateDownloadListener listener) { if (mDownloadRequest != null && mDownloadRequest.isDownloading()) { return; } checkLocalFilePath(localFilePath); mDownloadRequest = new UpdateDownloadRequest(downloadUrl, localFilePath, listener); Future<?> future = mExecutorService.submit(mDownloadRequest); new WeakReference<Future<?>>(future); } /** * 檢查文件路徑 */ private void checkLocalFilePath(String localFilePath) { File path = new File(localFilePath.substring(0, localFilePath.lastIndexOf("/") + 1)); File file = new File(localFilePath); if (!path.exists()) { path.mkdirs(); } if (!file.exists()) { try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } } public interface InstallPermissionListener { /** * 權限申請成功 */ void permissionSuccess(); /** * 權限申請失敗 */ void permissionFail(); } private static class Holder { private static final UpdateManager INSTANCE = new UpdateManager(); } }
最後經過 Service 啓動下載,建立 UpdateService 類:oop
/** * @desciption: 應用更新組件入口,用來啓動下載器並更新Notification */ public class UpdateService extends Service { public static final String APK_URL="apk_url"; /** * 文件存放路徑 */ private String filePath; /** * 文件下載地址 */ private String apkUrl; private NotificationUtils mNotificationUtils; @Override public void onCreate() { super.onCreate(); filePath = Environment.getExternalStorageDirectory() + "/videobusiness/videobusiness.apk"; mNotificationUtils = new NotificationUtils(this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { apkUrl = intent.getStringExtra(APK_URL); notifyUser(getString(R.string.update_download_start), 0); startDownload(); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return null; } private void notifyUser(String content, int progress) { mNotificationUtils.sendNotificationProgress(getString(R.string.app_name), content, progress, progress >= 100 ? getContentIntent() : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT)); } private void startDownload() { UpdateManager.getInstance().startDownload(apkUrl, filePath, new UpdateDownloadListener() { @Override public void onStart() { } @Override public void onPrepared(long contentLength, String downloadUrl) { } @Override public void onProgressChanged(int progress, String downloadUrl) { notifyUser(getString(R.string.update_download_processing), progress); } @Override public void onPaused(int progress, int completeSize, String downloadUrl) { notifyUser(getString(R.string.update_download_failed), progress); deleteApkFile(); //中止服務自身 stopSelf(); } @Override public void onFinished(int completeSize, String downloadUrl) { notifyUser(getString(R.string.update_download_finish), 100); //中止服務自身 stopSelf(); startActivity(getInstallApkIntent()); } @Override public void onFailure() { notifyUser(getString(R.string.update_download_failed), 0); deleteApkFile(); //中止服務自身 stopSelf(); } }); } private PendingIntent getContentIntent() { return PendingIntent.getActivity(this, 0, getInstallApkIntent(), PendingIntent.FLAG_UPDATE_CURRENT); } private Intent getInstallApkIntent() { File apkFile = new File(filePath); // 經過Intent安裝APK文件 final Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.addCategory(Intent.CATEGORY_DEFAULT); //兼容7.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive"); //兼容8.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean hasInstallPermission = this.getPackageManager().canRequestPackageInstalls(); if (!hasInstallPermission) { InstallPermissionActivity.sListener = new UpdateManager.InstallPermissionListener() { @Override public void permissionSuccess() { installApk(); } @Override public void permissionFail() { Toast.makeText(UpdateService.this, "受權失敗,沒法安裝應用", Toast.LENGTH_LONG).show(); } }; Intent intent = new Intent(this, InstallPermissionActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } } } else { installIntent.setDataAndType(Uri.parse("file://" + apkFile.getPath()), "application/vnd.android.package-archive"); } return installIntent; } /** * 8.0 權限獲取後的安裝 */ private void installApk() { File apkFile = new File(filePath); // 經過Intent安裝APK文件 Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.addCategory(Intent.CATEGORY_DEFAULT); Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive"); startActivity(installIntent); } /** * 刪除無用apk文件 */ private void deleteApkFile() { File apkFile = new File(filePath); if (apkFile.exists() && apkFile.isFile()) { apkFile.delete(); } } }
咱們是以通知的形式更新下載進度條,下面是封裝的 Notification 類:ui
/** * @desciption: 通知管理 */ public class NotificationUtils extends ContextWrapper { public static final String CHANNEL_ID = "default"; private static final String CHANNEL_NAME = "Default Channel"; private static final String CHANNEL_DESCRIPTION = "this is default channel!"; private NotificationManager mManager; public NotificationUtils(Context base) { super(base); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { createNotificationChannel(); } } @TargetApi(Build.VERSION_CODES.O) private void createNotificationChannel() { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT); //是否繞過請勿打擾模式 channel.canBypassDnd(); //閃光燈 channel.enableLights(true); //鎖屏顯示通知 channel.setLockscreenVisibility(VISIBILITY_SECRET); //閃關燈的燈光顏色 channel.setLightColor(Color.RED); //桌面launcher的消息角標 channel.canShowBadge(); //是否容許震動 channel.enableVibration(true); //獲取系統通知響鈴聲音的配置 channel.getAudioAttributes(); //獲取通知取到組 channel.getGroup(); //設置可繞過 請勿打擾模式 channel.setBypassDnd(true); //設置震動模式 channel.setVibrationPattern(new long[]{100, 100, 200}); //是否會有燈光 channel.shouldShowLights(); getManager().createNotificationChannel(channel); } private NotificationManager getManager() { if (mManager == null) { mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } return mManager; } /** * 發送通知 */ public void sendNotification(String title, String content) { NotificationCompat.Builder builder = getNotification(title, content); getManager().notify(1, builder.build()); } private NotificationCompat.Builder getNotification(String title, String content) { NotificationCompat.Builder builder = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID); } else { builder = new NotificationCompat.Builder(getApplicationContext()); builder.setPriority(PRIORITY_DEFAULT); } //標題 builder.setContentTitle(title); //文本內容 builder.setContentText(content); //小圖標 builder.setSmallIcon(R.mipmap.ic_launcher); //設置點擊信息後自動清除通知 builder.setAutoCancel(true); return builder; } /** * 發送通知 */ public void sendNotification(int notifyId, String title, String content) { NotificationCompat.Builder builder = getNotification(title, content); getManager().notify(notifyId, builder.build()); } /** * 發送帶有進度的通知 */ public void sendNotificationProgress(String title, String content, int progress, PendingIntent intent) { NotificationCompat.Builder builder = getNotificationProgress(title, content, progress, intent); getManager().notify(0, builder.build()); } /** * 獲取帶有進度的Notification */ private NotificationCompat.Builder getNotificationProgress(String title, String content, int progress, PendingIntent intent) { NotificationCompat.Builder builder = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { builder = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID); } else { builder = new NotificationCompat.Builder(getApplicationContext()); builder.setPriority(PRIORITY_DEFAULT); } //標題 builder.setContentTitle(title); //文本內容 builder.setContentText(content); //小圖標 builder.setSmallIcon(R.mipmap.ic_launcher); //設置大圖標,未設置時使用小圖標代替,拉下通知欄顯示的那個圖標 //設置大圖片 BitmpFactory.decodeResource(Resource res,int id) 根據給定的資源Id解析成位圖 builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)); if (progress > 0 && progress < 100) { //一種是有進度刻度的(false),一種是循環流動的(true) //設置爲false,表示刻度,設置爲true,表示流動 builder.setProgress(100, progress, false); } else { //0,0,false,能夠將進度條隱藏 builder.setProgress(0, 0, false); builder.setContentText("下載完成"); } //設置點擊信息後自動清除通知 builder.setAutoCancel(true); //通知的時間 builder.setWhen(System.currentTimeMillis()); //設置點擊信息後的跳轉(意圖) builder.setContentIntent(intent); return builder; } }
使用Service 在manifest中註冊this
<!--服務--> <service android:name=".service.update.UpdateService"/>
Intent intent = new Intent(mContext, UpdateService.class); //傳遞apk下載地址 intent.putExtra(UpdateService.APK_URL, apkurl); mContext.startService(intent);
在Android 7.0上,對文件的訪問權限做出了修改,不能在使用file://格式的Uri 訪問文件 ,Android 7.0提供 FileProvider,應該使用這個來獲取apk地址,而後安裝apk。以下進行簡單的適配:
(1) 在res 目錄下,新建一個xml 文件夾,在xml 下面建立一個文件provider_paths文件:
<?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external" path="" /> <external-files-path name="Download" path="" /> </paths>
(2) 在AndroidManifest.xml清單文件中申明Provider:
<!-- Android 7.0 照片、APK下載保存路徑--> <provider android:name="android.support.v4.content.FileProvider" android:authorities="packgeName.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider>
(3) Android 7.0上的文件地址獲取:
Uri uri = FileProvider.getUriForFile(context, "packageName.fileProvider", new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), apkFile));
注意:把上面的 packageName 換成你本身的包名,把 apkFile 換成你本身的 apk 所在的文件。 File apkFile = new File(filePath); filePath 是apk存放路徑。
Android8.0以上,未知來源的應用是不能夠經過代碼來執行安裝的(在sd卡中找找到apk,手動安裝是能夠的),未知應用安裝權限的開關被除掉,取而代之的是未知來源應用的管理列表,須要列表裏面開啓你的應用的未知來源的安裝權限。Google這麼作是爲了防止一開始正經的應用後來開始經過升級來作一些不合法的事情,侵犯用戶權益。
1) 在清單文件中申明權限:REQUEST_INSTALL_PACKAGES
<!--8.0請求安裝包權限--> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
(2) 在代碼中判斷用戶是否已經受過權限了,若是已經受權,能夠直接安裝,若是沒有受權,則跳轉到受權列表,讓用戶開啓未知來源應用安裝權限,開啓後,再安裝應用。
//兼容8.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean hasInstallPermission = this.getPackageManager().canRequestPackageInstalls(); if (!hasInstallPermission) { InstallPermissionActivity.sListener = new UpdateManager.InstallPermissionListener() { @Override public void permissionSuccess() { installApk(); } @Override public void permissionFail() { Toast.makeText(UpdateService.this, "受權失敗,沒法安裝應用", Toast.LENGTH_LONG).show(); } }; Intent intent = new Intent(this, InstallPermissionActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } }
/** * @desciption: 兼容Android 8。0 APP 在線更新,權限申請界面 */ public class InstallPermissionActivity extends BaseActivity { public static final int INSTALL_PACKAGES_REQUEST_CODE = 1; public static UpdateManager.InstallPermissionListener sListener; private AlertDialog mAlertDialog; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 彈窗 if (Build.VERSION.SDK_INT >= 26) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.REQUEST_INSTALL_PACKAGES}, INSTALL_PACKAGES_REQUEST_CODE); } else { finish(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { default: break; case INSTALL_PACKAGES_REQUEST_CODE: if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (sListener != null) { sListener.permissionSuccess(); finish(); } } else { //startInstallPermissionSettingActivity(); showDialog(); } break; } } private void showDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.app_name); builder.setMessage("爲了正常升級 xxx APP,請點擊設置按鈕,容許安裝未知來源應用,本功能只限用於 xxx APP版本升級"); builder.setPositiveButton("設置", new DialogInterface.OnClickListener() { @RequiresApi(api = Build.VERSION_CODES.O) @Override public void onClick(DialogInterface dialogInterface, int i) { startInstallPermissionSettingActivity(); mAlertDialog.dismiss(); } }); builder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { if (sListener != null) { sListener.permissionFail(); } mAlertDialog.dismiss(); finish(); } }); mAlertDialog = builder.create(); mAlertDialog.setCancelable(false); mAlertDialog.show(); } @RequiresApi(api = Build.VERSION_CODES.O) private void startInstallPermissionSettingActivity() { //注意這個是8.0新API Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, 1); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == INSTALL_PACKAGES_REQUEST_CODE && resultCode == RESULT_OK) { // 受權成功 if (sListener != null) { sListener.permissionSuccess(); } } else { // 受權失敗 if (sListener != null) { sListener.permissionFail(); } } finish(); } @Override protected void onDestroy() { super.onDestroy(); sListener = null; } }
注意:當經過Intent 跳轉到未知應用受權列表的時候,必定要加上包名,這樣就能直接跳轉到你的app下,否則只能跳轉到列表。
@RequiresApi(api = Build.VERSION_CODES.O) private void startInstallPermissionSettingActivity() { //注意這個是8.0新API Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + getPackageName())); startActivityForResult(intent, 1); }
這裏把 InstallPermissionActivity 設置爲透明:
<activity android:name=".activity.InstallPermissionActivity" android:theme="@style/activity_translucent"/> <style name="activity_translucent" parent="AppTheme"> <item name="android:windowBackground">@color/translucent_background</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item> </style> <color name="translucent_background">#00000000</color>
/** * @desciption: 應用更新組件入口,用來啓動下載器並更新Notification */ public class UpdateService extends Service { public static final String APK_URL="apk_url"; /** * 文件存放路徑 */ private String filePath; /** * 文件下載地址 */ private String apkUrl; private NotificationUtils mNotificationUtils; @Override public void onCreate() { super.onCreate(); filePath = Environment.getExternalStorageDirectory() + "/videobusiness/videobusiness.apk"; mNotificationUtils = new NotificationUtils(this); } @Override public int onStartCommand(Intent intent, int flags, int startId) { apkUrl = intent.getStringExtra(APK_URL); notifyUser(getString(R.string.update_download_start), 0); startDownload(); return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return null; } private void notifyUser(String content, int progress) { mNotificationUtils.sendNotificationProgress(getString(R.string.app_name), content, progress, progress >= 100 ? getContentIntent() : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT)); } private void startDownload() { UpdateManager.getInstance().startDownload(apkUrl, filePath, new UpdateDownloadListener() { @Override public void onStart() { } @Override public void onPrepared(long contentLength, String downloadUrl) { } @Override public void onProgressChanged(int progress, String downloadUrl) { notifyUser(getString(R.string.update_download_processing), progress); } @Override public void onPaused(int progress, int completeSize, String downloadUrl) { notifyUser(getString(R.string.update_download_failed), progress); deleteApkFile(); //中止服務自身 stopSelf(); } @Override public void onFinished(int completeSize, String downloadUrl) { notifyUser(getString(R.string.update_download_finish), 100); //中止服務自身 stopSelf(); startActivity(getInstallApkIntent()); } @Override public void onFailure() { notifyUser(getString(R.string.update_download_failed), 0); deleteApkFile(); //中止服務自身 stopSelf(); } }); } private PendingIntent getContentIntent() { return PendingIntent.getActivity(this, 0, getInstallApkIntent(), PendingIntent.FLAG_UPDATE_CURRENT); } private Intent getInstallApkIntent() { File apkFile = new File(filePath); // 經過Intent安裝APK文件 final Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.addCategory(Intent.CATEGORY_DEFAULT); //兼容7.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive"); //兼容8.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { boolean hasInstallPermission = this.getPackageManager().canRequestPackageInstalls(); if (!hasInstallPermission) { InstallPermissionActivity.sListener = new UpdateManager.InstallPermissionListener() { @Override public void permissionSuccess() { installApk(); } @Override public void permissionFail() { Toast.makeText(UpdateService.this, "受權失敗,沒法安裝應用", Toast.LENGTH_LONG).show(); } }; Intent intent = new Intent(this, InstallPermissionActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; } } } else { installIntent.setDataAndType(Uri.parse("file://" + apkFile.getPath()), "application/vnd.android.package-archive"); } return installIntent; } /** * 8.0 權限獲取後的安裝 */ private void installApk() { File apkFile = new File(filePath); // 經過Intent安裝APK文件 Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.addCategory(Intent.CATEGORY_DEFAULT); Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", apkFile); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); installIntent.setDataAndType(apkUri, "application/vnd.android.package-archive"); startActivity(installIntent); } /** * 刪除無用apk文件 */ private void deleteApkFile() { File apkFile = new File(filePath); if (apkFile.exists() && apkFile.isFile()) { apkFile.delete(); } } }