Android Service下载文件并自定义通知提示下载

时间:2023-01-27 09:34:03
最近要做一个更新sdk,里面用到了service后台下载,自定义通知提示下载进度,下面直接贴上代码.

下面是UpdateUtils.java ,告诉你如何使用

package com.cnziz.updatelib;

import com.cnziz.updatelib.download.DownloadServices;
import com.cnziz.updatelib.utils.Listener.onUpdateListener;

import android.content.Context;
import android.content.Intent;

public class UpdateUtils {

    public static UpdateUtils mUpdateUtils;

    public static UpdateUtils getInstanse(){
        if (null == mUpdateUtils) {
            mUpdateUtils = new UpdateUtils();
        }
        return mUpdateUtils;
    }

    public void update(Context context, String appInfo, onUpdateListener mUpdateListener){
        String url = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
        String filePath = "/sdcard/download";
        download(context, url, filePath, mUpdateListener);
    }
    /** * 下载更新 * @param context * @param url 下载地址 * @param filePath 保存目录 * @param mUpdateListener */
    public void download(Context context, String url, String filePath, onUpdateListener mUpdateListener){
        Intent intent = new Intent(context, DownloadServices.class);
        intent.putExtra("url", url);
        intent.putExtra("file_path", filePath);
        context.startService(intent);
    }
}

DownloadService.java

package com.cnziz.updatelib.download;

import java.io.File;
import java.io.IOException;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;

import com.cnziz.updatelib.R;
import com.cnziz.updatelib.utils.Listener.onUpdateListener;
import com.cnziz.updatelib.utils.LogUtils;

public class DownloadServices extends Service {
    private final static int DOWNLOAD_COMPLETE = -2;
    private final static int DOWNLOAD_FAIL = -1;

    // 自定义通知栏类
    MyNotification myNotification;

    String filePathString; // 下载文件绝对路径(包括文件名)

    // 通知栏跳转Intent
    private Intent updateIntent = null;

    private PendingIntent updatePendingIntent = null;

    DownFileThread downFileThread; // 自定义文件下载线程

    private onUpdateListener mUpdateListener;

    private Handler updateHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case DOWNLOAD_COMPLETE:
                // 点击安装PendingIntent
                Uri uri = Uri.fromFile(downFileThread.getApkFile());
                Intent installIntent = new Intent(Intent.ACTION_VIEW);
                installIntent.setDataAndType(uri,
                        "application/vnd.android.package-archive");
                installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(installIntent);
// updatePendingIntent = PendingIntent.getActivity(
// DownloadServices.this, 0, installIntent, 0);
// myNotification.changeContentIntent(updatePendingIntent);
// myNotification.notification.defaults = Notification.DEFAULT_SOUND;// 铃声提醒
// myNotification.changeNotificationText("下载完成,请点击安装!");

                // 停止服务
                // myNotification.removeNotification();
                stopSelf();
                break;
            case DOWNLOAD_FAIL:
                // 下载失败
                // myNotification.changeProgressStatus(DOWNLOAD_FAIL);
                myNotification.changeNotificationText("文件下载失败!");
                mUpdateListener.downloadFail();
                stopSelf();
                break;
            default: // 下载中
                LogUtils.e("service", "index" + msg.what);
                // myNotification.changeNotificationText(msg.what+"%");
                myNotification.changeProgressStatus(msg.what);
            }
        }
    };

    public DownloadServices() {
        // TODO Auto-generated constructor stub
        // mcontext=context;
        LogUtils.e("service", "DownloadServices1");

    }

    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        LogUtils.e("service", "onCreate");
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        LogUtils.e("service", "onDestroy");
        if (downFileThread != null)
            downFileThread.interuptThread();
// if (null != myNotification) {
// myNotification.removeNotification();
// }
        stopSelf();
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        LogUtils.e("service", "onStartCommand");
        String url = intent.getStringExtra("url");
        String filePath = intent.getStringExtra("file_path");
        mUpdateListener = (onUpdateListener) intent.getParcelableExtra("listener");
        // updateIntent = new Intent(this, MainActivity.class);
        // PendingIntent updatePendingIntent = PendingIntent.getActivity(this,
        // 0,
        // updateIntent, 0);
        myNotification = new MyNotification(this, updatePendingIntent, 1);

        // myNotification.showDefaultNotification(R.drawable.ic_launcher, "测试",
        // "开始下载");
        myNotification.showCustomizeNotification(R.drawable.ic_launcher,
                "测试下载", R.layout.notification);
        if (!filePath.endsWith("/")) {
            filePath = filePath +"/";
        }
        File path = new File(filePath);
        if (!path.exists()) {
            path.mkdir();
        }
        String[] s = url.split("/");
        filePathString = filePath + s[s.length-1];
        File file = new File(filePathString);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        // 开启一个新的线程下载,如果使用Service同步下载,会导致ANR问题,Service本身也会阻塞
        downFileThread = new DownFileThread(
                updateHandler,
                url,
                filePathString);
        new Thread(downFileThread).start();

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    @Deprecated
    public void onStart(Intent intent, int startId) {
        // TODO Auto-generated method stub
        LogUtils.e("service", "onStart");
        super.onStart(intent, startId);
    }

    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        LogUtils.e("service", "onBind");
        return null;
    }

}

下载线程 DownFileThread.java

package com.cnziz.updatelib.download;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;

import com.cnziz.updatelib.utils.LogUtils;

import android.annotation.SuppressLint;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.util.Log;

public class DownFileThread implements Runnable {
    public final static int DOWNLOAD_COMPLETE = -2;
    public final static int DOWNLOAD_FAIL = -1;
    public final static String TAG = "DownFileThread";
    Handler mHandler; // 传入的Handler,用于像Activity或service通知下载进度
    String urlStr; // 下载URL
    File apkFile; // 文件保存路径
    boolean isFinished; // 下载是否完成
    boolean interupted = false; // 是否强制停止下载线程

    public DownFileThread(Handler handler, String urlStr, String filePath) {
        LogUtils.i(TAG, urlStr);
        this.mHandler = handler;
        this.urlStr = urlStr;
        apkFile = new File(filePath);
        isFinished = false;
    }

    public File getApkFile() {
        if (isFinished)
            return apkFile;
        else
            return null;
    }

    public boolean isFinished() {
        return isFinished;
    }

    /** * 强行终止文件下载 */
    public void interuptThread() {
        interupted = true;
    }

    @SuppressLint("NewApi")
    @Override
    public void run() {
        // TODO Auto-generated method stub
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            java.net.URL url = null;
            HttpURLConnection conn = null;
            InputStream iStream = null;
            // if (DEVELOPER_MODE)
            {
                StrictMode
                        .setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                                .detectDiskReads().detectDiskWrites()
                                .detectNetwork() // or .detectAll() for all
                                                    // detectable problems
                                .penaltyLog().build());
                StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                        .detectLeakedSqlLiteObjects()
                        .detectLeakedClosableObjects().penaltyLog()
                        .penaltyDeath().build());
            }
            try {
                url = new java.net.URL(urlStr);
                conn = (HttpURLConnection) url.openConnection();
                conn.setConnectTimeout(5 * 1000);
                conn.setReadTimeout(20 * 1000);
                iStream = conn.getInputStream();
            } catch (MalformedURLException e) {
                LogUtils.e(TAG, "MalformedURLException");
                e.printStackTrace();
            } catch (Exception e) {
                LogUtils.e(TAG, "获得输入流失败");
                e.printStackTrace();
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(apkFile);
            } catch (FileNotFoundException e) {
                LogUtils.i(TAG, "获得输出流失败:new FileOutputStream(apkFile);");
                e.printStackTrace();
            }
            BufferedInputStream bis = new BufferedInputStream(iStream);
            byte[] buffer = new byte[1024];
            int len;
            // 获取文件总长度
            int length = conn.getContentLength();
            double rate = (double) 100 / length; // 最大进度转化为100
            int timeLoad = length/100/1024;
            int total = 0;
            int times = 0;// 设置更新频率,频繁操作UI线程会导致系统奔溃
            try {
                LogUtils.e("threadStatus", "开始下载");
                while (false == interupted && ((len = bis.read(buffer)) != -1)) {
                    fos.write(buffer, 0, len);
                    // 获取已经读取长度
                    total += len;
                    int p = (int) (total * rate);
                    // Log.e("num", rate + "," + total + "," + p);
                    if (times >= timeLoad || p == 100) {
                        /* * 这是防止频繁地更新通知,而导致系统变慢甚至崩溃。 非常重要。。。。。 */
                        LogUtils.e("time", String.valueOf(times));
                        times = 0;
                        Message msg = Message.obtain();
                        msg.what = p;
                        mHandler.sendMessage(msg);
                    }
                    times++;
                }
                fos.close();
                bis.close();
                iStream.close();
                if (total == length) {
                    isFinished = true;
                    mHandler.sendEmptyMessage(DOWNLOAD_COMPLETE);
                    LogUtils.e(TAG, "下载完成结束");
                    return;
                }
                LogUtils.e(TAG, "强制中途结束");
                // mhandler.sendEmptyMessage(4);
            } catch (IOException e) {
                LogUtils.e(TAG, "异常中途结束");
                mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
                e.printStackTrace();
            }
        } else {
            LogUtils.e(TAG, "外部存储卡不存在,下载失败!");
            mHandler.sendEmptyMessage(DOWNLOAD_FAIL);
        }
    }
}

自定义通知 MyNotification.java

package com.cnziz.updatelib.download;

import com.cnziz.updatelib.R;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.widget.RemoteViews;

public class MyNotification {
    public final static int DOWNLOAD_COMPLETE = -2;
    public final static int DOWNLOAD_FAIL = -1;
    Context mContext; // Activity或Service上下文
    Notification notification; // notification
    NotificationManager nm;
    String titleStr; // 通知标题
    String contentStr; // 通知内容
    PendingIntent contentIntent; // 点击通知后的动作
    int notificationID; // 通知的唯一标示ID
    int iconID; // 通知栏图标
    long when = System.currentTimeMillis();
    RemoteViews remoteView = null; // 自定义的通知栏视图

    /** * * @param context * Activity或Service上下文 * @param contentIntent * 点击通知后的动作 * @param id * 通知的唯一标示ID */
    public MyNotification(Context context, PendingIntent contentIntent, int id) {
        // TODO Auto-generated constructor stub
        mContext = context;
        notificationID = id;
        this.contentIntent = contentIntent;
        this.nm = (NotificationManager) mContext
                .getSystemService(Context.NOTIFICATION_SERVICE);
    }

    /** * 显示自定义通知 * * @param icoId * 自定义视图中的图片ID * @param titleStr * 通知栏标题 * @param layoutId * 自定义布局文件ID */
    public void showCustomizeNotification(int icoId, String titleStr,
            int layoutId) {
        this.titleStr = titleStr;
        notification = new Notification(R.drawable.ic_launcher, titleStr, when);
        notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
        notification.flags |= Notification.FLAG_AUTO_CANCEL;
        notification.contentIntent = this.contentIntent;

        // 1、创建一个自定义的消息布局 view.xml
        // 2、在程序代码中使用RemoteViews的方法来定义image和text。然后把RemoteViews对象传到contentView字段
        if (remoteView == null) {
            remoteView = new RemoteViews(mContext.getPackageName(), layoutId);
            remoteView.setImageViewResource(R.id.ivNotification, icoId);
            remoteView.setTextViewText(R.id.tvTitle, titleStr);
            remoteView.setTextViewText(R.id.tvTip, "开始下载");
            remoteView.setProgressBar(R.id.pbNotification, 100, 0, false);
            notification.contentView = remoteView;
        }
        nm.notify(notificationID, notification);
    }

    /** * 更改自定义布局文件中的进度条的值 * * @param p * 进度值(0~100) */
    public void changeProgressStatus(int p) {
        if (notification.contentView != null) {
            if (p == DOWNLOAD_FAIL)
                notification.contentView.setTextViewText(R.id.tvTip, "下载失败! ");
            else if (p == 100)
                notification.contentView.setTextViewText(R.id.tvTip,
                        "下载完成,请点击安装");
            else
                notification.contentView.setTextViewText(R.id.tvTip, "进度(" + p
                        + "%)");
            notification.contentView.setProgressBar(R.id.pbNotification, 100,
                    p, false);
        }
        nm.notify(notificationID, notification);
    }

    public void changeContentIntent(PendingIntent intent) {
        this.contentIntent = intent;
        notification.contentIntent = intent;
    }

    /** * 显示系统默认格式通知 * * @param iconId * 通知栏图标ID * @param titleText * 通知栏标题 * @param contentStr * 通知栏内容 */
    public void showDefaultNotification(int iconId, String titleText,
            String contentStr) {
        this.titleStr = titleText;
        this.contentStr = contentStr;
        this.iconID = iconId;

        notification = new Notification();
        notification.tickerText = titleStr;
        notification.icon = iconID;
        notification.flags = Notification.FLAG_INSISTENT;
        notification.flags |= Notification.FLAG_AUTO_CANCEL;
        notification.contentIntent = this.contentIntent;

        // 添加声音效果
        // notification.defaults |= Notification.DEFAULT_SOUND;

        // 添加震动,后来得知需要添加震动权限 : Virbate Permission
        // mNotification.defaults |= Notification.DEFAULT_VIBRATE ;

        // 添加状态标志
        // FLAG_AUTO_CANCEL 该通知能被状态栏的清除按钮给清除掉
        // FLAG_NO_CLEAR 该通知能被状态栏的清除按钮给清除掉
        // FLAG_ONGOING_EVENT 通知放置在正在运行
        // FLAG_INSISTENT 通知的音乐效果一直播放
        notification.flags = Notification.FLAG_ONLY_ALERT_ONCE;
        changeNotificationText(contentStr);
    }

    /** * 改变默认通知栏的通知内容 * * @param content */
    public void changeNotificationText(String content) {
        notification.setLatestEventInfo(mContext, titleStr, content,
                contentIntent);

        // 设置setLatestEventInfo方法,如果不设置会App报错异常
        // NotificationManager mNotificationManager = (NotificationManager)
        // getSystemService(Context.NOTIFICATION_SERVICE);
        // 注册此通知
        // 如果该NOTIFICATION_ID的通知已存在,会显示最新通知的相关信息 ,比如tickerText 等
        nm.notify(notificationID, notification);
    }

    /** * 移除通知 */
    public void removeNotification() {
        // 取消的只是当前Context的Notification
        nm.cancel(notificationID);
    }
}

主要代码都在这了,下面是完整的项目链接
https://github.com/yan1348/ServiceDownLoad