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

下载管理器

关于下载进度的监听,这个比较简单,以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());

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值