Android 软件升级(后台服务 + 通知栏进度条)

打算从今开始写博客,把自己在日常工作中值得分享的技术写出来,也为了自己对知识更好的理解与巩固。


这两天完成了软件升级下载的功能,通过后台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();


贴上AppDownloadService.java代码

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的布局代码:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingLeft="10dip"
    android:paddingTop="5dip"
    android:paddingBottom="5dip"
    android:paddingRight="15dip"
    android:background="@drawable/main_tab_bg" >

    <ImageView
        android:layout_width="40dip"
        android:layout_height="40dip"
        android:src="@drawable/ic_launcher" />

    <LinearLayout
        android:layout_width="0dip"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_marginLeft="15dip"
        android:orientation="vertical" >
        
        
        <TextView 
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="15sp"
            android:textColor="@android:color/white"
            />
        
        <ProgressBar
            android:id="@+id/pb_download" 
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleHorizontal"
            android:max="100"
            />
        
    </LinearLayout>

</LinearLayout>
主要定义了一个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(),停止该服务;


以上就是所有过程,初次写技术博客,有不正确的地方,请指出改正。

















  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值