今天花了不少功夫,终于把软件自动更新功能实现了,还是做一下笔记吧。自己把代码整理了一下,做了一个很简单的Demo。


首先是效果图:

点击第一个按钮会调用系统浏览器默认的下载功能,这个用起来很方便,但是不太友好。

点击第二个安妮会开启服务下载文件,这个功能需要在AndroidManifest.xml提前添加权限:

<!-- 允许访问网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 读写外部存储卡 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

主界面效果。

正在下载package_esp8266com 正在下载软件更新_android

点击下载按钮,状态栏弹出状态栏下载提示,开始下载文件。

正在下载package_esp8266com 正在下载软件更新_状态栏_02

软件下载过程,状态栏一直显示下载进度。


软件下载成功,会自动跳出安装界面。





正在下载package_esp8266com 正在下载软件更新_状态栏_03





好了,效果图完毕,接下来直接上代码:


MainActivity代码内容:


/**
 * 开启服务下载文件并在状态栏显示下载进度
 * @author SHI
 * 2016年3月17日 13:47:41
 */
public class MainActivity extends Activity{

	/**文件下载地址**/
	private String addressOfApkDownload = "http://115.28.9.25//DaiNiFei.apk";
	/**使用系统浏览器下载**/
	private Button btn_downloadBySys;
	/**开启服务下载**/
	private Button btn_downloadBySelf;
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        btn_downloadBySys = (Button) findViewById(R.id.btn_downloadBySys);
        btn_downloadBySelf = (Button) findViewById(R.id.btn_downloadBySelf);
        
        btn_downloadBySys.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				//调用系统自带浏览器下载
				Uri uri = Uri.parse(addressOfApkDownload);  
				Intent downloadIntent = new Intent(Intent.ACTION_VIEW, uri);  
				startActivity(downloadIntent); 				
			}
		});
        
        btn_downloadBySelf.setOnClickListener(new View.OnClickListener() {
			
			@Override
			public void onClick(View v) {
				//开启服务下载
				Intent intent = new Intent(MainActivity.this, UpdateAppService.class);
				intent.putExtra("addressOfApkDownload", addressOfApkDownload);
				startService(intent);
			}
		});


    }
    
    
}


activity_main.xml布局文件内容:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_downloadBySys"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="使用系统浏览器下载文件" />

    <Button
        android:id="@+id/btn_downloadBySelf"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开启服务下载文件" />

</LinearLayout>



/***
 * 更新下載服务
 * @author SHI
 * 2016-3-16 19:57:09
 */
public class UpdateAppService extends Service {
	//和下载过程状态栏的显示有关类               主要是在手机状态栏上显示和更新当前下载进度  
	//这几个类具体什么功能,可以自己详细在网上搜一下。
	/**远程View 显示在状态栏的内容**/
	private RemoteViews contentView;
	/**状态栏内容的管理类**/
	private NotificationManager notiManage;
	/**状态栏类**/
	private Notification note;
	/**设备上下文**/
	private Context mContext;
	
    private int Notification_ID = 110;
	private PendingIntent pd;
	
	UpdateAppServiceController updateAppServiceControllerImp;
    
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		if(mContext == null){
			mContext = this;
			updateAppServiceControllerImp = new UpdateAppServiceController(this);
			updateAppServiceControllerImp.init(intent);
		}
		return super.onStartCommand(intent, flags, startId);
	}

	
	public void initView(Intent intent) {
		
			notiManage=(NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
			
			note=new Notification();
	
			note.icon=R.drawable.ic_launcher;
			
			note.tickerText = "软件更新包下载";
	
			note.flags=Notification.FLAG_AUTO_CANCEL;
	
			contentView = new RemoteViews(getPackageName(), R.layout.layout_progress); 
	
			contentView.setProgressBar(R.id.notificationProgress, 100, 0, false); 
	
			note.contentView = contentView;
			
			notiManage.notify(Notification_ID, note);
	}
	
	/***
	 * 显示下载进度
	 * @param progress
	 */
	public void showLoading(long total, long current) {
		int progress = (int) (current*100/total);
		note.contentView.setProgressBar(R.id.notificationProgress, 100, progress, false);
		note.contentView.setTextViewText(R.id.notificationPercent, "已下载"+progress+"%"); 				
		note.contentIntent = pd;
		notiManage.notify(Notification_ID, note);
	}
	
	/***
	 * 显示错误信息
	 * @param msg
	 */
	public void showFialedMsg(String msg){
		ToastUtil.show(mContext, msg);
	}
	
	/***
	 * 下载成功
	 */
	public void finishRefrushView(String locationForApkDown){
		note.contentView.setProgressBar(R.id.notificationProgress, 100, 100, false); 					
		note.contentView.setTextViewText(R.id.notificationPercent, "已下载完成"); 
		note.contentIntent=pd;		
		//点击安装
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.setDataAndType(Uri.parse("file://" + locationForApkDown),"application/vnd.android.package-archive");
//		intent.setDataAndType(Uri.fromFile(responseInfo.result), "application/vnd.android.package-archive");

		pd = PendingIntent.getActivity(mContext, 0, intent, 0);//这个非要不可。
		
		note.contentIntent = pd;
		
		notiManage.notify(Notification_ID, note);
		startActivity(intent);	
		stopSelf();
  	}
	
	
	
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	/**
	 * 结束当前服务时  清空手机状态栏内容
	 */
	@Override
	public void onDestroy() {
		notiManage.cancel(Notification_ID);
		super.onDestroy();
	}

}



UpdateAppServiceController代码:



/****
 * 业务逻辑控制层
 * @author SHI
 * 2016年3月16日 19:56:57
 */
public class UpdateAppServiceController{

	UpdateAppService mUpdateAppService;
	private String name_App;
	
	
	public UpdateAppServiceController(UpdateAppService mUpdateAppService) {
		super();
		this.mUpdateAppService = mUpdateAppService;
	}

	public void init(Intent intent) {
		mUpdateAppService.initView(intent);
		beginToUpdateApp(intent);
	}
	
	public void beginToUpdateApp(Intent intent){
		
		//获取安装包下载地址
		final String addressOfApkDownload = intent.getStringExtra("addressOfApkDownload");
		//获取安装包名称    并获取下载SD卡位置
		int position = addressOfApkDownload.lastIndexOf("/");
		name_App = addressOfApkDownload.substring(position+1, addressOfApkDownload.length());
		
		final String locationForApkDown = SystemUtil.getDownloadFilePath(name_App);
		HttpUtils httpUtils = new HttpUtils();
		LogUtils.i("beginToUpdateApp");
		/**
		 * addressOfApkDownload 文件下载地址
		 * locationForApkDown   文件下载到本地的路径
		 * 为false的时候,每次下载都会把之前的文件覆盖掉重新下载,ture的时候,会先检测本地文件,如果存在,则抛出file has downloaded completely异常
		 */
		httpUtils.download(addressOfApkDownload, locationForApkDown, true, new RequestCallBack<File>() {
			
			@Override
			public void onLoading(long total, long current, boolean isUploading) {
				mUpdateAppService.showLoading(total,current);
			}
			
			@Override
			public void onSuccess(ResponseInfo<File> responseInfo) {
				Log.i("下载结果", responseInfo.result.getAbsolutePath());
				mUpdateAppService.finishRefrushView(locationForApkDown);
				
			}
			@Override
			public void onFailure(HttpException error, String msg) {
				if(msg.contains("file has downloaded completely")){
					mUpdateAppService.finishRefrushView(locationForApkDown);
				}else{
					mUpdateAppService.stopSelf();
				}
			}
		});
	}

}





<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:padding="3dp" >

    <ImageView
        android:id="@+id/notificationImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginRight="10dp"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/notificationTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/notificationImage"
        android:text="软件更新包下载"
        android:textSize="18sp" />

    <ProgressBar
        android:id="@+id/notificationProgress"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/notificationTitle"
        android:layout_toRightOf="@+id/notificationImage" />

    <TextView
        android:id="@+id/notificationPercent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/notificationProgress"
        android:layout_toRightOf="@+id/notificationImage"
        android:text="已下载0%" /> 

</RelativeLayout>



/***
 * 系统工具类  获取  包管理器信息,SDcard信息,网络状况信息
 * @author SHI
 * 2016-2-25 16:39:55
 */
public class SystemUtil {

	/**获取当前应用版本号**/
	public static int getCurrentAppVersionCode(Context mContext){
        // 获得包管理器,注意,整个android手机,共用一个包管理器
         PackageManager packageManager = mContext.getPackageManager();
		 PackageInfo packageInfo = null;
		try {
			packageInfo = packageManager.getPackageInfo(mContext.getPackageName(), 0);
		} catch (NameNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(packageInfo != null){
			return packageInfo.versionCode;
		}else{
			return 0;
		}
	}
	
	/**获取当前应用版包名**/
	public static String getCurrentAppVersionName(Context mContext){
		// 获得包管理器,注意,整个android手机,共用一个包管理器
		PackageManager packageManager = mContext.getPackageManager();
		PackageInfo packageInfo = null;
		try {
			packageInfo = packageManager.getPackageInfo(mContext.getPackageName(), 0);
		} catch (NameNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		if(packageInfo != null){
			return packageInfo.versionName;	
		}else{
			return "";	
		}
	}
	
	/** SD卡是否存在 **/
	public static File whetherExistSDcard() {
		// 判断sd卡是否存在
		if (Environment.getExternalStorageState().equals(
				android.os.Environment.MEDIA_MOUNTED)) {
			return Environment.getExternalStorageDirectory();// 获取跟目录
		}
		return null;
	}

	/*****
	 * 返回指定文件夹和文件名的文件路径
	 * 
	 * @param FolderName
	 * @param fileName
	 * @return
	 */
	public static String getFilePath(String FolderName, String fileName) {
		String downDirectory = null;
		File pathSDcard = whetherExistSDcard();
		if (pathSDcard != null) {
			//判断文件夹是否为空
			if (TextUtils.isEmpty(FolderName)) {
				FolderName = "";
			}
			//创建文件夹
			if(createFolder(FolderName)){
				downDirectory = pathSDcard.toString() + File.separator + FolderName
						+ File.separator + fileName;
			}
		}
		return downDirectory;
	}
	
	/****
	 * 返回系统默认Download文件夹下的File文件路径
	 * @param fileName   文件名称
	 * @return
	 */
	public static String getDownloadFilePath(String fileName) {
		String downDirectory = null;
		String FolderName = "Download";
		File pathSDcard = whetherExistSDcard();
		if (pathSDcard != null) {
			//创建文件夹
			if(createFolder(FolderName)){
				downDirectory = pathSDcard.toString() + File.separator + FolderName
						+ File.separator + fileName;
			}
		}
		return downDirectory;
	}

	/*****
	 * 创建文件夹
	 * @param FolderName 文件夹名称
	 * @return
	 */
	public static boolean createFolder(String FolderName) {
		
		File pathSDcard = whetherExistSDcard();
		if (pathSDcard != null) {
			File file = new File(pathSDcard.toString() + File.separator + FolderName);
			
			if (!file.exists()) {// 目录不存在
				file.mkdirs();
			}
			return true;
		} else {
			return false;
		}
	}

	/****
	 * 删除文件夹或者文件
	 * @param file
	 * @return
	 */
	public static boolean deleteDirectory(File file) {
		if (file.isDirectory()) {
			File[] filelist = file.listFiles();
			for (int i = 0; i < filelist.length; i++) {
				deleteDirectory(filelist[i]);
			}
			if (!file.delete()) {
				return false;
			}
		} else {
			if (!file.delete()) {
				return false;
			}
		}
		return true;
	}
	
	/****
	 * 判断是否有网络连接
	 * @param mContext 设备上下文
	 * @return
	 */
    public boolean isNetworkConnected(Context mContext) {
        if (mContext != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) mContext
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable();
            }
        }
        return false;
    }

    /****
     * 判断WiFi网络是否可用
     * @param mContext
     * @return
     */
    public boolean isWifiConnected(Context mContext) {
        if (mContext != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) mContext
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mWiFiNetworkInfo = mConnectivityManager
                    .getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if (mWiFiNetworkInfo != null) {
                return mWiFiNetworkInfo.isAvailable();
            }
        }
        return false;
    }
    
    /****
     * 判断MOBILE网络是否可用
     * @param mContext
     * @return
     */
    public boolean isMobileConnected(Context mContext) {
        if (mContext != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) mContext
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mMobileNetworkInfo = mConnectivityManager
                    .getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
            if (mMobileNetworkInfo != null) {
                return mMobileNetworkInfo.isAvailable();
            }
        }
        return false;
    }
    
    /****
     * 获取当前网络连接类型
     * @param mContext
     * @return
     * 
     */
    public static int getConnectedType(Context mContext) {
        if (mContext != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) mContext
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
            if (mNetworkInfo != null && mNetworkInfo.isAvailable()) {
                return mNetworkInfo.getType();
            }
        }
        return -1;
    }

    /***
     * 获取手机时间按照fromat格式返回
     * @param format
     * @return
     */
	public static String getCurrentTime(String format) {
		Date date = new Date();
		SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
		String currentTime = sdf.format(date);
		return currentTime;
	}

	/***
	 * 获取手机时间
	 * @return
	 */
	public static String getCurrentTime() {
		return getCurrentTime("yyyy-MM-dd  HH:mm:ss");
	}

}


工具类ToastUtils:

/**
 * ToastUtils
 * @author SHI
 * 2016年3月17日 13:54:46
 */
public class ToastUtil {

    private ToastUtil() {
        throw new AssertionError();
    }

    public static void show(Context context, int resId) {
        show(context, context.getResources().getText(resId), Toast.LENGTH_SHORT);
    }

    public static void show(Context context, int resId, int duration) {
        show(context, context.getResources().getText(resId), duration);
    }

    public static void show(Context context, CharSequence text) {
        show(context, text, Toast.LENGTH_SHORT);
    }

    public static void show(Context context, CharSequence text, int duration) {
        Toast.makeText(context, text, duration).show();
    }

    public static void show(Context context, int resId, Object... args) {
        show(context, String.format(context.getResources().getString(resId), args), Toast.LENGTH_SHORT);
    }

    public static void show(Context context, String format, Object... args) {
        show(context, String.format(format, args), Toast.LENGTH_SHORT);
    }

    public static void show(Context context, int resId, int duration, Object... args) {
        show(context, String.format(context.getResources().getString(resId), args), duration);
    }

    public static void show(Context context, String format, int duration, Object... args) {
        show(context, String.format(format, args), duration);
    }
}



到这里基本就可以实现文件下载按功能了。


有几点需要注意:


1:不要忘记在AndroidManifest.xml添加权限


2:记得在AndroidManifest.xml中为UpdateAppservice添加对应的声明


3:demo使用的xutil2.6.14.对高版本的Android兼容性可能有问题,如果出问题了,大家可以考虑使用Xutis3.0之后的或者使用别的网络框架。原理是不变的。