Android版本更新服務通知下載實現

在日常開發中,我們肯定會有檢查版本更新的需求,那我版本更新的輪子網上也是有的,想自己實現一下代碼如下:

下載管理類:

public class DownLoadManager {private static final String MAIN = "main"; //Tagprivate static DownLoadManager instance = new DownLoadManager(); //單例對象/*** 對外公布的單例對象** @return*/public static DownLoadManager getInstance() {return instance;}/*** 下載** @param uri        url* @param listener   下載DownLoadListener監聽對象* @param targetFile 目標文件*/public void downLoad(final Context context, final String uri, final DownLoadListener listener,final File targetFile) {if (MAIN.equalsIgnoreCase(Thread.currentThread().getName())) {new Thread() {@Overridepublic void run() {downloadNewThread(context, uri, listener, targetFile);};}.start();} else {downloadNewThread(context, uri, listener, targetFile);}}/*** 新開一個線程執行下載操作** @param uri* @param listener* @param targetFile*/private void downloadNewThread(Context context, String uri, DownLoadListener listener,File targetFile) {FileOutputStream fileOutputStream = null;InputStream inputStream = null;//try {URL url = new URL(uri);//獲取連接HttpURLConnection connection = (HttpURLConnection) url.openConnection();connection.setConnectTimeout(60 * 1000);connection.setReadTimeout(60 * 1000);connection.setRequestProperty("Connection", "Keep-Alive");connection.setRequestProperty("Charset", "UTF-8");connection.setDoInput(true);connection.setUseCaches(false);//打開連接connection.connect();//獲取內容長度int contentLength = connection.getContentLength();if (listener != null) {listener.onStartLoading(contentLength);}File parent = targetFile.getParentFile();if (!parent.exists()) {parent.mkdirs();} else if (!parent.isDirectory()) {if (parent.delete()) {parent.mkdirs();}}//輸入流inputStream = connection.getInputStream();//輸出流boolean old = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;fileOutputStream = old ?context.openFileOutput(targetFile.getName(), Context.MODE_WORLD_READABLE): new FileOutputStream(targetFile);byte[] bytes = new byte[1024];long totalReaded = 0;int temp_Len;long currentTime = System.currentTimeMillis();while ((temp_Len = inputStream.read(bytes)) != -1) {totalReaded += temp_Len;
//                Log.i("XXXX", "run: totalReaded:" + totalReaded);
//                long progress = totalReaded * 100 / contentLength;
//                Log.i("XXXX", "run: progress:" + progress);fileOutputStream.write(bytes, 0, temp_Len);if (listener != null) {listener.onLoading(totalReaded, ((float) totalReaded) / contentLength,((float) temp_Len) / System.currentTimeMillis()- currentTime);}currentTime = System.currentTimeMillis();}if (listener != null) {listener.onLoadingFinish(contentLength);}} catch (Exception e) {e.printStackTrace();if (listener != null) {listener.onFailure(e.getMessage());}} finally {try {if (fileOutputStream != null) {fileOutputStream.close();}if (inputStream != null) {inputStream.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 聲明DownLoadListener監聽器*/public interface DownLoadListener {/*** 開始下載** @param totalSize*/void onStartLoading(long totalSize);/*** 下載中** @param currentSize byte* @param percent* @param speed       byte/second*/void onLoading(long currentSize, float percent, float speed);/*** 下載完成** @param totalSize*/void onLoadingFinish(long totalSize);/*** 下載失敗** @param error*/void onFailure(String error);}
}

更新監聽接口

public interface UpdateListener {/*** @description 開始升級* @param totalSize 安裝包體積*/void onStart(long totalSize);/*** @description 正在下載*/void onLoading(long currentSize, float percent, float speed);/*** @description 下載完成* @param totalSize 安裝包體積*/void onLoadingFinish(long totalSize);/*** @description md5校驗成功*/void onMd5Checked(String path);/*** @description 升級失敗* @param error*/void onError(String error);
}

?最后就是服務類了比較重要

public class UpdateService extends Service {private static final String NOTIFY_CHANNEL_ID = "com.jianke.api.UpdateService";public static final String BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK = "BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK";public static final String BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS = "BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS";public static boolean isRunning = false; //是否正在運行public static final String URL = "url"; //Tagpublic static final String ICON = "icon"; //Tagpublic static final String MD5 = "md5"; //Tagprivate NotificationCompat.Builder builder;private Handler handler;//Handler對象private int lastPercent = 0;private NotificationManager notificationManager;//Class to notify the user of events that happen.private AuthInstallApkBroadcastReceiver mAuthInstallApkBroadcastReceiver;private String fileName = String.valueOf(System.currentTimeMillis());private UpdateListener updateListener;private class AuthInstallApkBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {installApk();}}@Overridepublic void onCreate() {super.onCreate();mAuthInstallApkBroadcastReceiver = new AuthInstallApkBroadcastReceiver();IntentFilter intentFilter = new IntentFilter(UpdateService.BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK_SUCCESS);LocalBroadcastManager.getInstance(this).registerReceiver(mAuthInstallApkBroadcastReceiver, intentFilter);}@Overridepublic void onDestroy() {stopForeground(true);LocalBroadcastManager.getInstance(this).unregisterReceiver(mAuthInstallApkBroadcastReceiver);updateListener = null;super.onDestroy();isRunning = false;}@Overridepublic IBinder onBind(Intent intent) {return new UpdateServiceBinder();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {isRunning = true;if (intent == null) {return super.onStartCommand(intent, flags, startId);}String action = intent.getAction();if (!TextUtils.isEmpty(action)) {Toast.makeText(getApplicationContext(), "action is null", Toast.LENGTH_SHORT).show();} else {String url = intent.getStringExtra(URL);final String md5 = intent.getStringExtra(MD5);int icon = intent.getIntExtra(ICON, android.R.drawable.sym_def_app_icon);if (TextUtils.isEmpty(url)) {
//                if (BuildConfig.DEBUG) {throw new RuntimeException("獲取APK更新地址失敗");
//                }
//                return super.onStartCommand(intent, flags, startId);} else {startUpdate(url, icon);}handler = new Handler(getMainLooper()) {@Overridepublic void handleMessage(Message msg) {if (builder != null) {switch (msg.what) {case 1:builder.setProgress(100, (Integer) msg.obj, false);builder.setContentText((Integer) msg.obj + "%");if (notificationManager == null || builder == null) {return;}notificationManager.notify(R.id.update_notification_id, builder.build());break;case 2:notificationManager.cancel(R.id.update_notification_id);// 更新通知為下載完成if (builder != null) {builder.setContentTitle("下載完成");builder.setProgress(0, 0, false);notificationManager.notify(R.id.update_notification_id, builder.build());}getFileMD5(md5);break;case 3:  //校驗md5 結果處理 以及安裝String fileHash = (String) msg.obj;LogUtils.d("md5===" + fileHash);// 校驗hash 忽略 md5大小寫if (msg.arg1 == 1 && (TextUtils.isEmpty(md5) || (!TextUtils.isEmpty(fileHash) && fileHash.equalsIgnoreCase(md5)))) {isRunning = false;if(updateListener != null) updateListener.onMd5Checked(getApkPath());installApk();} else {isRunning = false;if(updateListener != null) updateListener.onError("文件錯誤");ToastUtil.setToast("文件錯誤");stopSelf();}break;case 4:isRunning = false;if(updateListener != null) updateListener.onError(msg.obj + "");ToastUtil.setToast(msg.obj + "");notificationManager.cancel(R.id.update_notification_id);stopSelf();break;default:break;}}}};}return super.onStartCommand(intent, flags, startId);}public void setUpdateListener(UpdateListener updateListener) {this.updateListener = updateListener;}/*** 校驗文件md5** @param md5*/private void getFileMD5(String md5) {if (!TextUtils.isEmpty(md5)) {new Thread(new Runnable() {   //300M 耗時 ≈ 3S@Overridepublic void run() {try {File file = new File(getApkPath());String fileHash = MD5Utils.digestMD5(file);sendCheckFileMsg(true, fileHash);} catch (Exception e) {e.printStackTrace();sendCheckFileMsg(false, "");}}}).start();} else {  //md5 空算成功sendCheckFileMsg(true, "");}}private void sendCheckFileMsg(boolean success, String hash) {Message msg = Message.obtain();msg.what = 3;msg.arg1 = success ? 1 : 0;msg.obj = hash;handler.sendMessage(msg);}private void installApk() {File file = new File(getApkPath());// 檢查是否已授權(Android 8.0+)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !getPackageManager().canRequestPackageInstalls()) {ToastUtil.setToast("請授權安裝應用");notificationManager.notify(R.id.update_notification_id, builder.build());requestAutoInstallApk(); // 跳轉授權頁面return;}Intent intent = new Intent();intent.setAction(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");} else {intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive");intent.addCategory("android.intent.category.DEFAULT");}startActivity(intent);stopSelf();}private void requestAutoInstallApk() {isRunning = false;LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(BROADCAST_UPDATE_VERSION_AUTH_INSTALL_APK));if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {// 跳轉到系統設置頁,讓用戶開啟「安裝未知應用」權限Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);intent.setData(Uri.parse("package:" + getPackageName()));intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);}}/*** 開始下載** @param url apk的url*/private void startUpdate(String url, int icon) {createNotification(icon); //創建通知欄進度startDownLoad(url);}/*** 創建通知欄進度*/private void createNotification(int icon) {notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel(NOTIFY_CHANNEL_ID,"xxx",NotificationManager.IMPORTANCE_HIGH);channel.enableLights(true);channel.setShowBadge(true);channel.setDescription("update version");notificationManager.createNotificationChannel(channel);}notificationManager.cancel(R.id.update_notification_id);builder = new NotificationCompat.Builder(getApplicationContext(), NOTIFY_CHANNEL_ID);builder.setSmallIcon(icon).setContentTitle("正在下載 ...").setContentText("0%");builder.setPriority(NotificationCompat.PRIORITY_HIGH);builder.setProgress(100, 0, false);builder.setOnlyAlertOnce(true);builder.setOngoing(true);// 添加點擊安裝的PendingIntentIntent installIntent = new Intent(this, UpdateService.class);installIntent.setAction("INSTALL_APK");PendingIntent pendingIntent = PendingIntent.getService(this, 0,installIntent, PendingIntent.FLAG_UPDATE_CURRENT| PendingIntent.FLAG_IMMUTABLE);builder.setContentIntent(pendingIntent);if (notificationManager == null) {return;}Notification notification = builder.build();notificationManager.notify(R.id.update_notification_id, notification);startForeground(R.id.update_notification_id, notification);}/*** 開始下載** @param url*/private void startDownLoad(String url) {DownLoadManager.getInstance().downLoad(this, url, new DownLoadManager.DownLoadListener() {@Overridepublic void onStartLoading(long totalSize) {// Do nothing because of auto-generatedif(updateListener != null) updateListener.onStart(totalSize);}@Overridepublic void onLoading(long currentSize, float percent, float speed) {int tempPercent = (int) (percent * 100);if (tempPercent >= 0 && lastPercent != tempPercent) {  //避免頻繁調用通知Message msg = Message.obtain();msg.what = 1;msg.obj = tempPercent;handler.sendMessage(msg);lastPercent = tempPercent;if(updateListener != null) updateListener.onLoading(currentSize, percent, speed);}}@Overridepublic void onLoadingFinish(long totalSize) {Message msg = Message.obtain();msg.what = 2;handler.sendMessage(msg);if(updateListener != null) updateListener.onLoadingFinish(totalSize);}@Overridepublic void onFailure(String error) {Message msg = Message.obtain();msg.what = 4;msg.obj = error;handler.sendMessage(msg);}}, new File(getApkPath()));}/*** 獲取apk下載路徑** @return*/private String getApkPath() {boolean old = Build.VERSION.SDK_INT < Build.VERSION_CODES.N;return old ?getFileStreamPath(fileName + ".apk").getAbsolutePath(): getAppRootDir() + fileName + ".apk";}/*** 獲取sdcard的絕對路徑** @return*/public String getSDcardDir() {String sdcardPath = null;if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {sdcardPath = Environment.getExternalStorageDirectory().getAbsolutePath();} else {sdcardPath = getApplicationContext().getFilesDir().getAbsolutePath();}return sdcardPath;}/*** 獲取應用跟目錄** @return*/public String getAppRootDir() {return getFilesDir().getAbsolutePath() + "/";}public class UpdateServiceBinder extends Binder {public UpdateService getService(){return UpdateService.this;}}
}

最后配置也比較重要要在manifest里注冊服務

 //android:foregroundServiceType="dataSync" 這個高版本必須要指定服務類型<service android:name="com.ai.library_common.widget.versionmanager.UpdateService"android:foregroundServiceType="dataSync"/>權限要加這些<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /><!-- 添加 FOREGROUND_SERVICE 權限 --><uses-permission android:name="android.permission.FOREGROUND_SERVICE" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/><uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"tools:ignore="ProtectedPermissions" /><uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<!--    通知權限--><uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/74143.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/74143.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/74143.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UE5學習筆記 FPS游戲制作33 換子彈 動畫事件

新建動畫蒙太奇 為Rifle和Launcher各自新建一個動畫蒙太奇&#xff0c;拖入動畫&#xff0c;注意動畫的軌道要和動畫藍圖里的一致 在蒙太奇添加動畫事件 在通知一欄新增一個軌道&#xff0c;右鍵軌道&#xff0c;新增一個 換槍完成 通知&#xff0c;不同動畫的同名通知需要…

uniapp中uploadFile的用法

基本語法 uni.uploadFile(OBJECT)OBJECT 是一個包含上傳相關配置的對象&#xff0c;常見參數如下&#xff1a; 參數類型必填說明urlString是開發者服務器地址。filePathString是要上傳文件資源的本地路徑。nameString是文件對應的 key&#xff0c;開發者在服務端可以通過這個 …

Android設計模式之責任鏈模式

一、定義&#xff1a; 使多個對象都有機會處理請求&#xff0c;從而避免了請求的發送者和接收者之間的耦合關系將這些對象連城一條鏈&#xff0c;并沿著這條鏈傳遞該請求&#xff0c;只到有對象處理它為止。 二、模式結構&#xff1a; 抽象處理者&#xff08;Handler&#xff…

Oracle數據庫數據編程SQL<3.3 PL/SQL 游標>

游標(Cursor)是Oracle數據庫中用于處理查詢結果集的重要機制&#xff0c;它允許開發者逐行處理SQL語句返回的數據。 目錄 一、游標基本概念 1. 游標定義 2. 游標分類 二、靜態游標 &#xff08;一&#xff09;顯式游標 【一】不帶參數&#xff0c;普通的顯示游標 1. 顯式…

逗萬DareWorks|創意重構書寫美學,引領新潮無界的文創革命

當傳統文具陷入同質化泥潭時&#xff0c;逗萬DareWorks品牌猶如一顆璀璨的明星&#xff0c;以其獨特的創意理念和卓越的產品品質&#xff0c;迅速贏得了廣大消費者的青睞。 逗萬DareWorks隸屬于東莞司貿文教贈品有限公司&#xff0c;后者深耕制筆行業45年&#xff0c;占地4.6萬…

寫Prompt的技巧和基本原則

一.基本原則 1.一定要描述清晰你需要大模型做的事情&#xff0c;不要模棱兩可 2.告訴大模型需要它做什么&#xff0c;不需要做什么 改寫前: 請幫我推薦一些電影 改寫后: 請幫我推薦2025年新出的10部評分比較高的喜劇電影&#xff0c;不要問我個人喜好等其他問題&#xff…

【React】基于 React+Tailwind 的 EmojiPicker 選擇器組件

1.背景 React 寫一個 EmojiPicker 組件&#xff0c;基于 emoji-mart 組件二次封裝。支持添加自定義背景 、Emoji 圖標選擇&#xff01;并在頁面上展示&#xff01; 2.技術棧 emoji-mart/data 、emoji-mart : emoji 圖標庫、元數據 tailwindcss: 原子化 CSS 樣式庫 antd : 組…

Qt中繪制不規則控件

在Qt中繪制不規則控件可通過設置遮罩&#xff08;Mask&#xff09;實現。以下是詳細步驟: ?繼承目標控件?&#xff1a;如QPushButton或QWidget。?重寫resizeEvent?&#xff1a;當控件大小變化時&#xff0c;更新遮罩形狀。?創建遮罩區域?&#xff1a;使用QRegion或QPain…

Parallel_Scheduling_of_DAGs_under_Memory_Constraints論文閱讀

內存約束下的 DAG 并行調度 點擊閱讀原文語雀鏈接更清晰 摘要 科學工作流通常被建模為任務的有向無環圖&#xff08;DAG&#xff09;&#xff0c;這些任務代表計算模塊及其依賴關系&#xff0c;依賴關系表現為任務生成的數據被其他任務使用。這種形式化方法允許使用運行時系統&…

探索MVC、MVP、MVVM和DDD架構在不同編程語言中的實現差異

MVC與MVP/MVVM/DDD架構對比&#xff0c;不同語言實現 MVC 分層架構設計概述 模型-視圖-控制器&#xff08;Model-View-Controller&#xff0c;簡稱 MVC&#xff09;是一種經典軟件架構設計&#xff0c;通過分層解耦&#xff0c;使得系統結構清晰和易于維護&#xff0c;具有良…

一文讀懂 UML:基礎概念與體系框架

UML 圖是一種標準化的建模語言&#xff0c;在軟件開發和系統設計等領域有著廣泛的應用。以下是對 UML 圖各類圖的詳細介紹&#xff1a; 1.用例圖 定義&#xff1a;用例圖是從用戶角度描述系統功能的模型圖&#xff0c;展現了系統的參與者與用例之間的關系。作用&#xff1a;幫…

Spring 及 Spring Boot 條件化注解(15個)完整列表及示例

Spring 及 Spring Boot 條件化注解完整列表及示例 1. 所有條件化注解列表 Spring 和 Spring Boot 提供了以下條件化注解&#xff08;共 15 個&#xff09;&#xff0c;用于在配置類或方法上實現條件化注冊 Bean 或配置&#xff1a; 注解名稱作用來源框架Conditional自定義條件…

【Kafka】深入探討 Kafka 如何保證一致性

文章目錄 Kafka 基本概念回顧?副本角色? 數據寫入一致性?同步副本&#xff08;ISR&#xff09;集合?數據讀取一致性?故障處理與一致性恢復?總結? 在分布式系統領域&#xff0c;數據一致性是至關重要的一環。作為一款高性能的分布式消息隊列系統&#xff0c;Kafka 在設計…

從入門到精通:SQL注入防御與攻防實戰——紅隊如何突破,藍隊如何應對!

引言&#xff1a;為什么SQL注入攻擊依然如此強大&#xff1f; SQL注入&#xff08;SQL Injection&#xff09;是最古老且最常見的Web應用漏洞之一。盡管很多公司和組織都已經采取了WAF、防火墻、數據庫隔離等防護措施&#xff0c;但SQL注入依然在許多情況下能夠突破防線&#…

【算法day27】有效的數獨——請你判斷一個 9 x 9 的數獨是否有效。只需要 根據以下規則 ,驗證已經填入的數字是否有效即可。

36. 有效的數獨 請你判斷一個 9 x 9 的數獨是否有效。只需要 根據以下規則 &#xff0c;驗證已經填入的數字是否有效即可。 數字 1-9 在每一行只能出現一次。 數字 1-9 在每一列只能出現一次。 數字 1-9 在每一個以粗實線分隔的 3x3 宮內只能出現一次。&#xff08;請參考示例…

leetcode 2360. 圖中的最長環 困難

給你一個 n 個節點的 有向圖 &#xff0c;節點編號為 0 到 n - 1 &#xff0c;其中每個節點 至多 有一條出邊。 圖用一個大小為 n 下標從 0 開始的數組 edges 表示&#xff0c;節點 i 到節點 edges[i] 之間有一條有向邊。如果節點 i 沒有出邊&#xff0c;那么 edges[i] -1 。…

PySpur: AI 智能體可視化開發平臺

GitHub&#xff1a;https://github.com/PySpur-Dev/pyspur 更多AI開源軟件&#xff1a;發現分享好用的AI工具、AI開源軟件、AI模型、AI變現 - 小眾AI PySpur是一個開源的輕量級可視化AI智能體工作流構建器&#xff0c;旨在簡化AI系統的開發流程。通過拖拽式界面&#xff0c;用戶…

vcpkg安裝及使用教程,以安裝matio庫解析mat文件為例

vcpkg安裝及使用教程,以安裝matio庫解析mat文件為例 1. vcpkg安裝2 安裝matio三方庫3 將三方庫集成到VS中3.1 全局集成3.2 集成到特定工程4 結語Vcpkg 是微軟開發的一款開源的 C/C++ 包管理工具,旨在簡化 C/C++ 項目依賴庫的安裝和管理。它支持跨平臺(Windows、Linux、macO…

LLM架構解析:NLP基礎(第一部分)—— 模型、核心技術與發展歷程全解析

本專欄深入探究從循環神經網絡&#xff08;RNN&#xff09;到Transformer等自然語言處理&#xff08;NLP&#xff09;模型的架構&#xff0c;以及基于這些模型構建的應用程序。 本系列文章內容&#xff1a; NLP自然語言處理基礎&#xff08;本文&#xff09;詞嵌入&#xff0…

【Rtklib入門指南】2. 使用RTKLIB GUI進行觀測數據分析

數據準備 下載2025年1月1日的香港CORS站數據和觀測星歷&#xff0c;詳情參照如下博客&#xff1a; 使用GAMP_GOOD進行hk數據下載教程-CSDN博客 分析工具 RTKLIB 2.4.3 demo5&#xff08;也可以選用RTKLIB2.4.2&#xff0c;但不建議使用RTKLIB2.4.3&#xff09; 分析流程 …