今天花了不少功夫,终于把软件自动更新功能实现了,还是做一下笔记吧。自己把代码整理了一下,做了一个很简单的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" />
主界面效果。
点击下载按钮,状态栏弹出状态栏下载提示,开始下载文件。
软件下载过程,状态栏一直显示下载进度。
/** * 开启服务下载文件并在状态栏显示下载进度 * @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); } }); } }
<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(); } }
/**** * 业务逻辑控制层 * @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 * @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); } }