今天花了不少功夫,终于把软件自动更新功能实现了,还是做一下笔记吧。自己把代码整理了一下,做了一个很简单的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" />
主界面效果。
点击下载按钮,状态栏弹出状态栏下载提示,开始下载文件。
软件下载过程,状态栏一直显示下载进度。
软件下载成功,会自动跳出安装界面。
好了,效果图完毕,接下来直接上代码:
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之后的或者使用别的网络框架。原理是不变的。