打算从今开始写博客,把自己在日常工作中值得分享的技术写出来,也为了自己对知识更好的理解与巩固。
这两天完成了软件升级下载的功能,通过后台service下载文件,并刷新通知栏,功能简单。
首先发起http请求,把软件的版本号等信息提交给服务端,对比版本号,查看是否有软件更新,有的话则返回升级信息(主要是升级apk的url)
得到下载的url后就可以启动下载Service,这里是AppDownloadService
Intent intent = new Intent(mContext,AppDownloadService.class);
intent.putExtra("url", versionData.getUrl());
mContext.startService(intent);
Toast.makeText(mContext, "正在下载...", Toast.LENGTH_SHORT).show();
package com.runsdata.tower.android.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
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 android.widget.RemoteViews;
import com.runsdata.tower.android.R;
import com.runsdata.tower.android.config.Configuration;
/**
* App更新下载Service
*
* @author Leo.lai
*
*/
public class AppDownloadService extends Service {
private NotificationManager mNotificationManager;
private Notification notification;
private Context mContext;
private boolean isGoing = false; // 正在下载
private static final String TAG = "HttpTools";
private static final String DEFAUL_METHOD = "POST";
private static final int DEFAUL_TIMEOUTMILLIS = 10000;
private static final int DEFAUL_READTIMEOUT = 10000;
private static final String fileName = "Tower_Android.apk";
private File apkFile;
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@SuppressLint("NewApi")
@Override
public void onCreate() {
super.onCreate();
mContext = this;
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notification = new Notification.Builder(mContext).setContentTitle("正在下载").setContentText("下载").setSmallIcon(R.drawable.ic_launcher).build();
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), R.layout.notification_download);
// 指定个性化视图
notification.contentView = contentView;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String url = intent.getStringExtra("url");
if (!isGoing) {
resetNotification(notification);
mNotificationManager.notify(1, notification);
new Thread(new Runnable() {
@Override
public void run() {
isGoing = true;
download(url);
isGoing = false;
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
/**
* 重置通知
* @param notification
*/
private void resetNotification(Notification notification){
notification.flags = Notification.FLAG_ONGOING_EVENT; // 设置为正在进行的事件
notification.contentView.setTextViewText(R.id.tv_title, "正在下载新版本");
notification.contentIntent = null;
}
/**
* 得到安装apk的pendingIntent
* @param file
* @return
*/
private PendingIntent getInstallPendingIntent(File file){
Uri uri = Uri.fromFile(file);
Intent installIntent = new Intent(Intent.ACTION_VIEW);
installIntent.setDataAndType(uri, "application/vnd.android.package-archive");
return PendingIntent.getActivity(mContext, 0, installIntent, 0);
}
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
//下载失败
notification.contentView.setTextViewText(R.id.tv_title, "下载失败,请重试");
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置为自动取消
mNotificationManager.notify(1, notification);
AppDownloadService.this.stopSelf();
break;
case 2:
//下载完成
notification.contentIntent = getInstallPendingIntent(apkFile);
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置为自动取消
notification.contentView.setTextViewText(R.id.tv_title, "下载完成,点击安装");
AppDownloadService.this.stopSelf();
//这里没有break
case 3:
//下载中,更新进度
notification.contentView.setProgressBar(R.id.pb_download, msg.arg1, msg.arg2, false);
mNotificationManager.notify(1, notification);
break;
default:
break;
}
}
};
/**
* 下载文件
* @param requestUrl
*/
public void download(String requestUrl) {
URL url = null;
HttpURLConnection conn = null;
FileOutputStream fos = null;
BufferedReader rd = null;
try {
url = new URL(requestUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(DEFAUL_TIMEOUTMILLIS);
conn.setRequestMethod(DEFAUL_METHOD);
conn.setReadTimeout(DEFAUL_READTIMEOUT); // 读取超时
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
InputStream is = conn.getInputStream();
int fileLength = conn.getContentLength();
apkFile = new File(getDownloadpath(), fileName);
fos = new FileOutputStream(apkFile);
byte[] buffer = new byte[1024];
int rc = 0;
int done = 0; // 已经下载好的长度
int part = 0; // 用来表示通知handler刷新的长度
while ((rc = is.read(buffer)) > 0) {
fos.write(buffer, 0, rc);
done += rc;
part += rc;
if (part > (fileLength * 0.04)) {
// 刷新进度条
part = 0;
Message msg = new Message();
msg.what = 3;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
}
}
Message msg = new Message();
msg.what = 2;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
} catch (Exception e) {
Log.e(TAG, "下载失败");
handler.sendEmptyMessage(1);
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
Log.e(TAG, "请求接口出错:" + e.getMessage());
}
}
if (rd != null) {
try {
rd.close();
} catch (IOException e) {
Log.e(TAG, "关闭接口出错:" + e.getMessage());
}
}
if (conn != null) {
conn.disconnect();
}
}
}
/**
* 文件存放路径
*
* @return
*/
private String getDownloadpath() {
String path = Environment.getExternalStorageDirectory() + "/" + Configuration.APP_IDENTIFIER + "/apk";
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
return path;
}
}
首先是Oncreate()
public void onCreate() {
super.onCreate();
mContext = this;
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notification = new Notification.Builder(mContext).setContentTitle("正在下载").setContentText("下载").setSmallIcon(R.drawable.ic_launcher).build();
RemoteViews contentView = new RemoteViews(mContext.getPackageName(), R.layout.notification_download);
// 指定个性化视图
notification.contentView = contentView;
}
在这里初始化NotificationManager和Notification对象,在以后的通知中都重用这个Notification对象。RemoteView是通知栏的布局,这里为R.layout.notification_download,
最后设置notification.contentView = contentView ,指定好通知栏的布局
R.layout.notification_download的布局代码:
主要定义了一个textView和一个ProgressBar
接着是OnstartCommand()
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String url = intent.getStringExtra("url");
if (!isGoing) {
resetNotification(notification);
mNotificationManager.notify(1, notification);
new Thread(new Runnable() {
@Override
public void run() {
isGoing = true;
download(url);
isGoing = false;
}
}).start();
}
return super.onStartCommand(intent, flags, startId);
}
这里先得到传进来的Intent中的url,用一个全局变量isGoing来防止任务重复启动;首先重置一下Notification()重置通知布局,
notification.flags = Notification.FLAG_ONGOING_EVENT; // 设置为正在进行的事件
上面这行代码讲该通知标记为正在进行的事件,通知栏上讲不能清除该条通知;
最后new 一个新的线程来下载文件。
/**
* 下载文件
* @param requestUrl
*/
public void download(String requestUrl) {
URL url = null;
HttpURLConnection conn = null;
FileOutputStream fos = null;
BufferedReader rd = null;
try {
url = new URL(requestUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(DEFAUL_TIMEOUTMILLIS);
conn.setRequestMethod(DEFAUL_METHOD);
conn.setReadTimeout(DEFAUL_READTIMEOUT); // 读取超时
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
InputStream is = conn.getInputStream();
int fileLength = conn.getContentLength();
apkFile = new File(getDownloadpath(), fileName);
fos = new FileOutputStream(apkFile);
byte[] buffer = new byte[1024];
int rc = 0;
int done = 0; // 已经下载好的长度
int part = 0; // 用来表示通知handler刷新的长度
while ((rc = is.read(buffer)) > 0) {
fos.write(buffer, 0, rc);
done += rc;
part += rc;
if (part > (fileLength * 0.04)) {
// 刷新进度条
part = 0;
Message msg = new Message();
msg.what = 3;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
}
}
Message msg = new Message();
msg.what = 2;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
} catch (Exception e) {
Log.e(TAG, "下载失败");
handler.sendEmptyMessage(1);
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
Log.e(TAG, "请求接口出错:" + e.getMessage());
}
}
if (rd != null) {
try {
rd.close();
} catch (IOException e) {
Log.e(TAG, "关闭接口出错:" + e.getMessage());
}
}
if (conn != null) {
conn.disconnect();
}
}
}
这是一个普通的httpUrlConnection下载文件过程,重点是在下载的过程中通过handler对Notification布局进行更新。
byte[] buffer = new byte[1024];
int rc = 0;
int done = 0; // 已经下载好的长度
int part = 0; // 用来表示通知handler刷新的长度
while ((rc = is.read(buffer)) > 0) {
fos.write(buffer, 0, rc);
done += rc;
part += rc;
if (part > (fileLength * 0.04)) {
// 刷新进度条
part = 0;
Message msg = new Message();
msg.what = 3;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
}
}
Message msg = new Message();
msg.what = 2;
msg.arg1 = fileLength;
msg.arg2 = done;
handler.sendMessage(msg);
part > (fileLength * 0.04)
这里声明了一个int part变量,用来判断什么时候更新通知栏,这里设置为part > (fileLength * 0.04)的时候更新,
这样下载完一个文件只需要更新25次通知栏,每次更新进度为4%,节省了内存的开销。
最后是handler的处理:
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
//下载失败
notification.contentView.setTextViewText(R.id.tv_title, "下载失败,请重试");
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置为自动取消
mNotificationManager.notify(1, notification);
AppDownloadService.this.stopSelf();
break;
case 2:
//下载完成
notification.contentIntent = getInstallPendingIntent(apkFile);
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置为自动取消
notification.contentView.setTextViewText(R.id.tv_title, "下载完成,点击安装");
AppDownloadService.this.stopSelf();
//这里没有break
case 3:
//下载中,更新进度
notification.contentView.setProgressBar(R.id.pb_download, msg.arg1, msg.arg2, false);
mNotificationManager.notify(1, notification);
break;
default:
break;
}
}
};
在下载失败的话更新notification,将flag设置为FLAG_AUTO_CANCEL,这样就可以在通知栏清除该通知,再调用Service.stopSelf(),停止该服务;
以上就是所有过程,初次写技术博客,有不正确的地方,请指出改正。