Service应用 IntentService文件下载和Notification前台通知

service做为四大组件之一,一般用来做长时间的耗时操作,像文件下载,音乐播放等。一般应用都会涉及到文件的下载,那怎么做到下载文件的同时,又不影响app使用,这时候就需要用到service组件了。而service比较消耗性能,启动后如果不手动停止,service会一直在后台运行,即使app退出,android为我们提供了一个IntentService服务类,很好的帮我们解决了这个问题,当任务完成后,IntentService会自动销毁,不需要我们手动stopService。
业务逻辑大概是这样:启动IntentService,在onStartCommand回调方法中初始化一个Notification,然后再onHandleIntent(子线程)中开始下载文件,并不断更新Notification的进度,当下载完成时,调用系统安装程序安装。代码如下:

service类:

public class DownloadService extends IntentService {

private NotificationManager notificationManger;
private Notification notification;
private NotificationCompat.Builder mBuilder;
private String title;
private boolean isRunning = false;
private static final int PUSH_NOTIFICATION_ID = (0x001);
private static final String PUSH_CHANNEL_ID = "PUSH_NOTIFY_ID";
private static final String PUSH_CHANNEL_NAME = "PUSH_NOTIFY_NAME";

public AppDownloadService() {
    super(AppDownloadService.class.getSimpleName());
}

public AppDownloadService(String name) {
    super(name);
}

@Override
protected void onHandleIntent(Intent intent) {
    downloadTask(intent);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if ( isRunning) {
        return super.onStartCommand(intent, flags, startId);
    }
    notificationManger = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        NotificationChannel channel = new NotificationChannel(PUSH_CHANNEL_ID, PUSH_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
        if (notificationManger != null) {
            notificationManger.createNotificationChannel(channel);
        }
    }

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

/**
 * 方法: downloadTask <p>
 * 描述: 开启一个下载线程 <p>
 */
protected void downloadTask(Intent intent) {
    if (intent == null && isRunning) {
        return;
    }

    isRunning = true;
    title = intent.getStringExtra(KeyContacts.KEY_TITLE);// 下载链接
    displayNotificationMessage(title);
    url = intent.getStringExtra(KeyContacts.KEY_URL);// 下载链接
    LogUtil.e("downloadTask download url:" + url);
    if (TextUtils.isEmpty(url)) {
        downloadCallBack.onError(DownloadUtil.ERROR);
        return;
    }
    try {
        String filePath = DownloadUtil.getTargetFile(AppDownloadService.this, url);
        DownloadUtil.download(AppDownloadService.this, downloadCallBack, url, filePath);
    } catch (Exception e) {
        downloadCallBack.onError(DownloadUtil.ERROR);
    }
}



@Override
public void success() {
    updateNotification(DownloadUtil.FINISH, 0);
    DownloadUtil.installApk(AppDownloadService.this, DownloadUtil.getTargetFile(AppDownloadService.this, url));
}

public void error(int errorType) {
    if (errorType == DownloadUtil.ERROR) {
        updateNotification(DownloadUtil.ERROR, 0);
    } else if (errorType == DownloadUtil.SDCARDNOUSE) {
        notificationManger.cancel(DownloadUtil.NOTIFY_ID_DOWNLOAD);
        notificationManger.cancel(DownloadUtil.NOTIFY_ID_FINISHED);
        ToastUtil.showCustomToast("SD卡无法使用");
    }
}

public void downloading(int process) {
    updateNotification(DownloadUtil.DOWNLOADING, process);
}

/**
 * 方法: displayNotificationMessage <p>
 * 描述: 下载时候显示一个通知栏 <p>
 */
private void displayNotificationMessage(String title) {
    Intent notificationIntent = new Intent();
    notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
            notificationIntent, 0);

    mBuilder = new NotificationCompat.Builder(this);
    mBuilder.setContentTitle(title)
            .setWhen(System.currentTimeMillis())
            .setContentText(url)
            .setSmallIcon(R.drawable.ic_launcher)
            .setContentIntent(contentIntent)
            .setChannelId(PUSH_CHANNEL_ID)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
            .setTicker(title);

    notification = mBuilder.build();

    notification.flags |= Notification.FLAG_NO_CLEAR;

    notificationManger.notify(DownloadUtil.NOTIFY_ID_DOWNLOAD, notification);

}

/**
 * 方法: displayCancelNotification <p>
 * 描述: 显示一个提示完成 或者出错的通知栏 <p>
 */
private void displayCancelNotification(String title, String content, int id) {
    Intent notificationIntent = new Intent();

    if (id == DownloadUtil.NOTIFY_ID_FINISHED) {
        notificationIntent = DownloadUtil.getInstallIntent(this, DownloadUtil.getTargetFile(AppDownloadService.this, url));
    }
    notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
            notificationIntent, 0);

    mBuilder = new NotificationCompat.Builder(this);
    mBuilder.setContentTitle(title)
            .setWhen(System.currentTimeMillis())
            .setContentText(content)
            .setSmallIcon(R.drawable.ic_launcher)
            .setContentIntent(contentIntent)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
            .setTicker(content);
    notification = mBuilder.build();
    notification.defaults |= Notification.DEFAULT_SOUND;

    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    notificationManger.notify(id, notification);

}

/**
 * 方法: updateNotification <p>
 * 描述: 更新通知栏 <p>
 */
protected void updateNotification(int type, int progress) {
    LogUtil.d("type=" + type + "---progress=" + progress);
    switch (type) {
        case DownloadUtil.DOWNLOADING:
            notification.flags = Notification.FLAG_NO_CLEAR;
            mBuilder.setProgress(100, progress, false)
                    .setContentInfo(progress + "%");
            notificationManger.notify(DownloadUtil.NOTIFY_ID_DOWNLOAD, mBuilder.build());
            break;
        case DownloadUtil.FINISH:
            notificationManger.cancel(DownloadUtil.NOTIFY_ID_DOWNLOAD);
            displayCancelNotification(title, "下载完成,点击安装", DownloadUtil.NOTIFY_ID_FINISHED);
            //notificationManger.cancel(NOTIFY_ID_FINISHED);
            return;
        case DownloadUtil.ERROR:
            displayCancelNotification(title, "下载失败", DownloadUtil.NOTIFY_ID_ERROR);
            notificationManger.cancel(DownloadUtil.NOTIFY_ID_DOWNLOAD);
            notificationManger.cancel(DownloadUtil.NOTIFY_ID_FINISHED);
            break;
        default:
            break;
    }
}
}

service中用到的一些方法:

//获取下载路径:
public static String getTargetFile(Context mContext, String url) {

    String fileName = getFileName(url);
    if (TextUtils.isEmpty(fileName)) {
        fileName = UUID.randomUUID().toString();
    }
    String filePath = getDownLoadFilePath(mContext);
    if (TextUtils.isEmpty(filePath)) {
        return "";
    } else {
        return filePath + File.separator + fileName;
    }
}

public static String getDownLoadFilePath(Context context) {
    if (FileUtil.isExternalStorageCanUse()) {
        File tempPath = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +AppConfig.SDCARD_DIR_PATH);
        if (!tempPath.exists()) {
            tempPath.mkdirs();
        }
        return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + AppConfig.SDCARD_DIR_PATH;
    } else if (FileUtil.isRootStorageCanUse()) {
        return context.getCacheDir().getAbsolutePath();
    } else {
        //磁盘空间不足
        return "";
    }
}

//通过url生产文件名
public static String getFileName(String url) {
    String filename = "";
    // 从路径中获取
    if (TextUtils.isEmpty(filename)) {
        filename = url.substring(url.lastIndexOf("/") + 1);
    }
    return filename;
}

下载工具类:

public class DownloadUtil {


public static final int NOTIFY_ID_DOWNLOAD = 10001;
public static final int NOTIFY_ID_FINISHED = 10002;
public static final int NOTIFY_ID_ERROR = 10003;
public static final int FINISH = 1;
public static final int ERROR = 2;
public static final int DOWNLOADING = 3;
//SD卡无法使用
public static final int SDCARDNOUSE = 4;

/**
 * 方法: getTargetFile <p>
 * 描述: 得到下载的目标文件 如果为空则为UUID<p>
 */
public static String getTargetFile(Context mContext, String url) {

    String fileName = getFileName(url);
    fileName = MD5Util.md5(fileName);
    if(!fileName.endsWith(".apk")){
        fileName = fileName + ".apk";
    }

    String filePath = getDownLoadFilePath(mContext);
    if (TextUtils.isEmpty(filePath)) {
        return "";
    } else {
        return filePath + File.separator + fileName;
    }
}

/**
 * 方法: installApk <p>
 * 描述: 发送安装APK指令 <p>
 */
public static void installApk(Context mContext, String fileName) {
    mContext.startActivity(getInstallIntent(mContext, fileName));

}

/**
 * 方法: getInstallIntent <p>
 * 描述: 得到安装apk的intent <p>
 */
public static Intent getInstallIntent(Context mContext, String fileName) {
    File apkFile = new File(fileName);
    if (apkFile.exists()) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".fileProvider", apkFile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
        } else {
            intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }
        return intent;
    }
    return new Intent();
}

public static void download(Context mContext, DownloadCallBack downloadCallBack, String url, String filePath) {
    int errorNumber = 1;
    //下载失败尝试重新下载(只尝试2次)
    while (!downloadInternal(mContext, downloadCallBack, url, filePath) && errorNumber < 2) {
        errorNumber++;
    }
}

/**
 * 方法: download <p>
 * 描述: 下载方法 <p>
 */
protected static boolean downloadInternal(Context mContext, DownloadCallBack downloadCallBack, String url, String filePath) {
    int downLoadFileSize;
    int fileSize = 0;
    FileOutputStream fos = null;
    InputStream is = null;

    boolean success = true;
    try {
        if(url.startsWith("https://")){兼容https下载链接
            SSLContext sslContext = SSLContext.getInstance("SSL");//第一个参数为 返回实现指定安全套接字协议的SSLContext对象。第二个为提供者
            TrustManager[] tm = {new MyX509TrustManager()};
            sslContext.init(null, tm, new SecureRandom());
            HttpsURLConnection localURLConnection = (HttpsURLConnection) new URL(url).openConnection();
            localURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
            localURLConnection.setReadTimeout(100000);
            localURLConnection.setConnectTimeout(100000);
            localURLConnection.setRequestMethod("GET");
            localURLConnection.setRequestProperty("Accept-Language", "zh-CN");
            localURLConnection.setRequestProperty("Charset", "UTF-8");
            localURLConnection.setRequestProperty("Connection", "Keep-Alive");
            localURLConnection.setRequestProperty("Accept-Encoding", "identity");

            localURLConnection.connect();
            is = localURLConnection.getInputStream();
            fileSize = localURLConnection.getContentLength();
        }else {
            HttpURLConnection localURLConnection = (HttpURLConnection) new URL(url).openConnection();
            localURLConnection.setReadTimeout(100000);
            localURLConnection.setConnectTimeout(100000);
            localURLConnection.setRequestMethod("GET");
            localURLConnection.setRequestProperty("Accept-Language", "zh-CN");
            localURLConnection.setRequestProperty("Charset", "UTF-8");
            localURLConnection.setRequestProperty("Connection", "Keep-Alive");
            localURLConnection.setRequestProperty("Accept-Encoding", "identity");

            localURLConnection.connect();
            is = localURLConnection.getInputStream();
            fileSize = localURLConnection.getContentLength();
        }

        if (is == null) {
            downloadCallBack.onError(ERROR);
            return false;
        }

        //filePath = DownloadUtil.getTargetFile(mContext,url);
        if (TextUtils.isEmpty(filePath)) {
            LogUtil.e("downloadTask STORE ERROR  URL:" + url);
            downloadCallBack.onError(SDCARDNOUSE);
            return false;
        }
        File apkFile = new File(filePath);
        File parentFile=apkFile.getParentFile();
        if (!apkFile.exists()) {
            if (!parentFile.exists()){
                parentFile.mkdirs();
            }
            apkFile.createNewFile();
        }

        fos = new FileOutputStream(filePath, false);
        // 把数据存入路径+文件名
        byte buf[] = new byte[1024 * 4];
        downLoadFileSize = 0;
        downloadCallBack.onDownloading(0);

        int readCount = 0;
        int count =0;
        long start = System.currentTimeMillis();
        do {
            // 循环读取
            int numread = 0;
            numread = is.read(buf);
            if (numread == -1) {
                break;
            }
            fos.write(buf, 0, numread);
            downLoadFileSize += numread;

            if(fileSize < 0){//做假效果
                long now = System.currentTimeMillis();
                if((now - start) % 1000 == 0){
                    count += 5 + (now - start) / 1000;
                    if(count > 98)
                        count = 98;
                    downloadCallBack.onDownloading(count);
                }
            }else{
                if (readCount % 10 == 0 ) {
                    downloadCallBack.onDownloading(downLoadFileSize * 100 / fileSize);
                }
            }

            readCount++;
        } while (true);
        downloadCallBack.onSuccess();
    } catch (Exception e) {
        downloadCallBack.onError(ERROR);
        success = false;
    } finally {
        if (fos != null) {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return success;
    }
}
}

回调接口:

public interface DownloadCallBack {
	public void onError(int errorType);
	public void onDownloading(int process);
	public void onSuccess();

}

好了一个完整的前台下载service就完成了,记得需要在清单中注册service,并申请文件的读写权限,检查未知应用安装权限。检查未知应用安装权限比较特殊,不能像动态权限一样去申请,需要跳转到设置页面手动设置,所以我没想好在哪里检查合适,有好的解决方案的小伙伴可以私信告诉我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值