Android下载进度监听和通知的处理详解

作者:mChenys 时间:2022-03-27 08:39:02 

本文实例为大家分享了Android下载进度监听和通知的具体代码,供大家参考,具体内容如下

下载管理器

关于下载进度的监听,这个比较简单,以apk文件下载为例,需要处理3个回调函数,分别是:

1.下载中
2.下载成功
3.下载失败

因此对应的回调接口就有了:


public interface DownloadCallback {
 /**
  * 下载成功
  * @param file 目标文件
  */
 void onComplete(File file);

/**
  * 下载失败
  * @param e
  */
 void onError(Exception e);

/**
  * 下载中
  * @param count 总大小
  * @param current 当前下载的进度
  */
 void onLoading(long count, long current);
}

接下来就是线程池的管理了,当然你也可以直接使用Executors工具类中提供的几个静态方法来创建线程池,这里我是手动创建线程池的,代码如下:


public class ThreadManager {

private static ThreadPool mThreadPool;

/**
  * 获取线程池
  *
  * @return
  */
 public static ThreadPool getThreadPool() {
   if (null == mThreadPool) {
     synchronized (ThreadManager.class) {
       if (null == mThreadPool) {
         // cpu个数
         int cpuNum = Runtime.getRuntime().availableProcessors();
         //线程个数
         int count = cpuNum * 2 + 1;
         mThreadPool = new ThreadPool(count, count, 0);
       }
     }
   }
   return mThreadPool;
 }

public static class ThreadPool {
   int corePoolSize;// 核心线程数
   int maximumPoolSize;// 最大线程数
   long keepAliveTime;// 保持活跃时间(休息时间)
   private ThreadPoolExecutor executor;

/**
    * 构造方法初始化
    *
    * @param corePoolSize
    * @param maximumPoolSize
    * @param keepAliveTime
    */
   private ThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime) {
     this.corePoolSize = corePoolSize;
     this.maximumPoolSize = maximumPoolSize;
     this.keepAliveTime = keepAliveTime;
   }
   private static final ThreadFactory sThreadFactory = new ThreadFactory() {
     private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r) {
       return new Thread(r, "ThreadManager #" + mCount.getAndIncrement());
     }
   };
   /**
    * 执行线程任务
    *
    * @param r
    */
   public void execute(Runnable r) {
     //参1:核心线程数;参2:最大线程数;参3:保持活跃时间(休息时间);参4:活跃时间单位;参5:线程队列;参6:线程工厂;参7:异常处理策略
     if (null == executor) {
       executor = new ThreadPoolExecutor(corePoolSize,
           maximumPoolSize,
           keepAliveTime,
           TimeUnit.SECONDS,
           new LinkedBlockingQueue<Runnable>(),
           sThreadFactory/*Executors.defaultThreadFactory()*/,
           new ThreadPoolExecutor.AbortPolicy());
     }
     // 将当前Runnable对象放在线程池中执行
     executor.execute(r);
   }

/**
    * 从线程池的任务队列中移除一个任务
    * 如果当前任务已经是运行状态了,那么就表示不在任务队列中了,也就移除失败.
    */
   public void cancle(Runnable r) {
     if (null != executor && null != r) {
       executor.getQueue().remove(r);
     }
   }

/**
    * 是否关闭了线程池
    * @return
    */
   public boolean isShutdown(){
     return executor.isShutdown();
   }

/**
    * 关闭线程池
    */
   public void shutdown() {
     executor.shutdown();
   }
 }
}

接下来就是一个下载管理器的封装了.


public class DownloadManager {

private DownloadCallback callback;
 private Context context;
 private String url;
 private String fileName;

/**
  * 初始化
  * @param context 上下文
  * @param url 目标文件url
  * @param fileName 下载保存的文件名
  * @param callback 下载回调函数
  */
 public DownloadManager(final Context context, final String url, final String fileName, DownloadCallback callback) {
   this.context = context;
   this.url = url;
   this.fileName = fileName;
   this.callback = callback;
 }

/**
  * 开始下载
  */
 public void startDownload() {
   if (null == callback) return;
   ThreadManager.getThreadPool().execute(new Runnable() {
     @Override
     public void run() {
       HttpURLConnection conn = null;
       try {
         conn = (HttpURLConnection) new URL(url).openConnection();
         conn.setRequestMethod("GET");
         conn.setReadTimeout(5000);
         conn.setConnectTimeout(10000);
         long total = conn.getContentLength();
         long curr = 0;
         File file = new File(PathUtils.getDirectory(context, "apk"), fileName);
         if (!file.exists()) {
           file.createNewFile();
         } else {
           file.delete();
         }
         FileOutputStream fos = new FileOutputStream(file);
         if (200 == conn.getResponseCode()) {
           InputStream in = conn.getInputStream();
           byte[] buff = new byte[1024];
           int len = 0;
           while ((len = in.read(buff)) != -1) {
             fos.write(buff, 0, len);
             curr += len;
             callback.onLoading(total, curr);
           }
           in.close();
           fos.close();
           if (curr == total) {
             callback.onComplete(file);
           } else {
             throw new Exception("curr != total");
           }
         } else {
           throw new Exception("" + conn.getResponseCode());
         }
       } catch (Exception e) {
         e.printStackTrace();
         callback.onError(e);
       } finally {
         if (null != conn) {
           conn.disconnect();
         }
       }
     }
   });
 }
}

涉及的工具类如下:
PathUtils


public class PathUtils {
 /**
  * 获取cache目录下的rootDir目录
  *
  * @param context
  * @param rootDir
  * @return
  */
 public static File getDirectory(Context context, String rootDir) {
   String cachePath = context.getApplicationContext().getCacheDir().getAbsolutePath();
   if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
       || !Environment.isExternalStorageRemovable()) {
     if (Build.VERSION.SDK_INT <= 8) {
       cachePath = Environment.getExternalStorageDirectory().getAbsolutePath();
     } else if (context.getApplicationContext().getExternalCacheDir() != null) {
       cachePath = context.getApplicationContext().getExternalCacheDir().getAbsolutePath();
     }
   }
   File rootF = new File(cachePath + File.separator + rootDir);
   if (!rootF.exists()) {
     rootF.mkdirs();
   }
   //修改目录权限可读可写可执行
   String cmd = "chmod 777 -R " + rootF.getPath();
   CmdUtils.execCmd(cmd);
   return rootF;
 }
}

CmdUtils


public class CmdUtils {
 public static void execCmd(String cmd) {
   try {
     Runtime.getRuntime().exec(cmd);
   } catch (IOException e) {
     e.printStackTrace();
   }
 }
}

下载通知服务

同样以apk下载为例,要实现下载通知服务的话,就用到了Notification和Service,Notification用来通知下载进度并显示给用户看,Service用于后台默默的下载文件,这里我用到了IntentService,它的好处在于任务执行完毕后会自动关闭服务.同时程序用如果其他地方还想监听到下载的进度,那么可以在IntentService下载服务中通过发送广播告知进度.

ok,下面的代码可以直接用户实现apk的升级更新的操作.


public class UpdateService extends IntentService {
 private File apkFile;
 private String url;
 private String fileName;
 private NotificationCompat.Builder builderNotification;
 private NotificationManager updateNotificationManager;
 private int appNameID;
 private int iconID;
 private PendingIntent updatePendingIntent;
 private boolean isUpdating;
 public static final String ACTION_UPDATE_PROGRESS = "blog.csdn.net.mchenys.mobilesafe.ACTION_UPDATE_PROGRESS";

private Handler updateHandler = new Handler() {
   public void handleMessage(Message msg) {
     switch (msg.what) {
       case 0:
         UpdateService.this.onFailNotification();
         break;
       case 1:
         UpdateService.this.downComplete();
         break;
     }
     super.handleMessage(msg);
   }
 };

public UpdateService() {
   super("UpdateService");
 }

/**
  * 开始更新
  *
  * @param context
  * @param url   更新的url
  * @param fileName 下载保存apk的文件名称
  */
 public static void startUpdate(Context context, String url, String fileName) {
   Intent intent = new Intent(context, UpdateService.class);
   intent.putExtra("url", url);
   intent.putExtra("fileName", fileName);
   context.startService(intent);
 }

@Override
 protected void onHandleIntent(Intent intent) {
   //WorkerThread
   if (!this.isUpdating && intent != null) {
     initData(intent);
     initNotification();
     downloadFile(true);
   }
 }

/**
  * 初始数据
  *
  * @param intent
  */
 private void initData(Intent intent) {
   this.isUpdating = true;
   this.url = intent.getStringExtra("url");
   this.fileName = intent.getStringExtra("fileName");
   this.apkFile = new File(PathUtils.getDirectory(getApplicationContext(), "mobilesafe"), fileName);
   if (!this.apkFile.exists()) {
     try {
       this.apkFile.createNewFile();
     } catch (IOException e) {
       e.printStackTrace();
     }
   } else {
     this.apkFile.delete();
   }
   this.appNameID = R.string.app_name;
   this.iconID = R.mipmap.ic_logo;
 }

/**
  * 初始化通知
  */
 private void initNotification() {
   Intent updateCompletingIntent = new Intent();
   updateCompletingIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
   updateCompletingIntent.setClass(this.getApplication().getApplicationContext(), UpdateService.class);
   this.updatePendingIntent = PendingIntent.getActivity(this, this.appNameID, updateCompletingIntent, PendingIntent.FLAG_CANCEL_CURRENT);
   this.updateNotificationManager = (NotificationManager) this.getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
   this.builderNotification = new NotificationCompat.Builder(this.getApplicationContext());
   this.builderNotification.setSmallIcon(this.iconID).
       setContentTitle(this.getResources().getString(this.appNameID)).
       setContentIntent(updatePendingIntent).
       setAutoCancel(true).
       setTicker("开始更新").
       setDefaults(1).
       setProgress(100, 0, false).
       setContentText("下载进度").
       build();
   this.updateNotificationManager.notify(this.iconID, this.builderNotification.build());
 }

/**
  * 开始下载apk
  *
  * @param append 是否支持断点续传
  */
 private void downloadFile(final boolean append) {
   final Message message = updateHandler.obtainMessage();
   int currentSize = 0; //上次下载的大小
   long readSize = 0L;//已下载的总大小
   long contentLength = 0;//服务器返回的数据长度
   if (append) {
     FileInputStream fis = null;
     try {
       fis = new FileInputStream(UpdateService.this.apkFile);
       currentSize = fis.available();//获取上次下载的大小,断点续传可用
     } catch (IOException e) {
       e.printStackTrace();
     } finally {
       if (fis != null) {
         try {
           fis.close();
         } catch (IOException e) {
           e.printStackTrace();
         }
       }
     }
   }
   HttpURLConnection conn = null;
   InputStream is = null;
   FileOutputStream fos = null;
   try {
     conn = (HttpURLConnection) new URL(UpdateService.this.url).openConnection();
     conn.setRequestProperty("User-Agent", "Android");
     if (currentSize > 0) {
       conn.setRequestProperty("RANGE", "bytes=" + currentSize + "-");
     }
     conn.setConnectTimeout(10000);
     conn.setReadTimeout(20000);
     contentLength = conn.getContentLength();
     if (conn.getResponseCode() == 404) {
       throw new Exception("Cannot find remote file:" + UpdateService.this.url);
     }

is = conn.getInputStream();
     fos = new FileOutputStream(UpdateService.this.apkFile, append);
     //实时更新通知
     UpdateService.this.builderNotification.setSmallIcon(iconID).
         setContentTitle(UpdateService.this.getResources().getString(UpdateService.this.appNameID)).
         setContentIntent(updatePendingIntent).
         setAutoCancel(true).
         setTicker("正在更新").
         setDefaults(0).
         setContentText("下载进度").
         build();

byte[] buffer = new byte[8*1024];
     int len = 0;
     while ((len = is.read(buffer)) != -1) {
       fos.write(buffer, 0, len);
       readSize += len;//累加已下载的大小
       int progress = (int) (readSize * 100 / contentLength);
       Log.d("UpdateService", (readSize * 100 / contentLength)+"");
       if (progress % 8 == 0) {
         UpdateService.this.builderNotification.setProgress(100, progress, false);
         UpdateService.this.updateNotificationManager.notify(iconID, UpdateService.this.builderNotification.build());
         //发送广播,通知外界下载进度
         sendUpdateProgress(progress);
       }
     }

if (readSize == 0L) {
       message.what = 0;
     } else if (readSize == contentLength) {
       message.what = 1;
       sendUpdateProgress(100);
     }
   } catch (Exception e) {
     e.printStackTrace();
     message.what = 0;
   } finally {
     if (conn != null) {
       conn.disconnect();
     }
     IOUtils.close(is);
     IOUtils.close(fos);
     updateHandler.sendMessage(message);
   }
 }

/**
  * 发送更新进度
  *
  * @param progress
  */
 private void sendUpdateProgress(int progress) {
   Intent intent = new Intent(ACTION_UPDATE_PROGRESS);
   intent.putExtra("progress", progress);
   sendBroadcast(intent);
 }

/**
  * 下载失败通知用户重新下载
  */
 private void onFailNotification() {
   this.builderNotification.setSmallIcon(iconID).
       setContentTitle("更新失败,请重新下载").
       setContentIntent(updatePendingIntent).
       setAutoCancel(true).
       setTicker("更新失败").
       setDefaults(1).
       setProgress(100, 0, false).
       setContentText("下载进度").
       build();
   this.updateNotificationManager.notify(iconID, this.builderNotification.build());
 }

/**
  * 下载完毕,后通知用户点击安装
  */
 private void downComplete() {
   UpdateService.this.isUpdating = false;
   String cmd = "chmod 777 " + this.apkFile.getPath();
   CmdUtils.execCmd(cmd);

Uri uri = Uri.fromFile(this.apkFile);
   Intent updateCompleteIntent = new Intent("android.intent.action.VIEW");
   updateCompleteIntent.addCategory("android.intent.category.DEFAULT");
   updateCompleteIntent.setDataAndType(uri, "application/vnd.android.package-archive");
   this.updatePendingIntent = PendingIntent.getActivity(this, this.appNameID, updateCompleteIntent, PendingIntent.FLAG_UPDATE_CURRENT);
   this.builderNotification.setSmallIcon(this.iconID).
       setContentTitle(this.getResources().getString(this.appNameID)).
       setContentIntent(this.updatePendingIntent).
       setAutoCancel(true).
       setTicker("更新完成").
       setDefaults(1).
       setProgress(0, 0, false).
       setContentText("更新完成,点击安装").
       build();
   this.updateNotificationManager.notify(this.iconID, this.builderNotification.build());

}
}
标签:Android,下载进度,监听,通知
0
投稿

猜你喜欢

  • OpenCV 颜色追踪的示例代码

    2023-01-19 05:20:31
  • java+SpringBoot设计实现评教系统

    2023-02-05 13:12:18
  • C#集合本质之队列的用法详解

    2023-03-17 06:42:38
  • volatile与happens-before的关系与内存一致性错误

    2021-12-13 20:25:37
  • 详解如何为SpringBoot项目中的自定义配置添加IDE支持

    2021-10-07 05:51:01
  • 浅谈Mybatis分页插件,自定义分页的坑

    2021-12-15 01:28:20
  • C#中线程同步对象的方法分析

    2021-06-06 05:43:46
  • Java 1.8使用数组实现循环队列

    2022-02-11 04:00:10
  • 解决使用RestTemplate时报错RestClientException的问题

    2023-05-27 19:46:36
  • C#Winform窗口移动方法

    2023-04-10 04:46:18
  • Android实现退出界面弹出提示对话框

    2023-03-09 12:19:44
  • Kotlin中的handler如何避免内存泄漏详解

    2023-10-18 22:02:40
  • C# 10个常用特性汇总

    2023-03-22 01:04:13
  • Android需要提升权限的操作方法

    2021-07-17 11:25:47
  • 一篇文章带你搞定JAVA反射

    2023-09-18 02:27:51
  • C#预定义的基础类型转换

    2023-08-13 03:14:16
  • 深入了解JAVA HASHMAP的死循环

    2023-11-28 00:32:12
  • C#获取每个年,月,周的起始日期和结束日期的方法

    2023-11-11 20:53:45
  • 简单仿写Android控件SlidingMenu的实例代码

    2022-01-23 05:11:29
  • 适用于WebForm Mvc的Pager分页组件C#实现

    2022-05-11 22:11:34
  • asp之家 软件编程 m.aspxhome.com