这几天在实现一个APK版本更新的功能,发现涉及的东西比较繁杂。本着一劳永逸的想法将相关的内容写成了相对比较独立的类供以后参考同时也与大家共享,欢迎大家批评指正
主要实现了一下几个类:
(1)文件下载:设计自定义类,只需传入一个Handler、下载地址URLStr及保存路径及可实现下载的功能。handler主要用于线程间通信,跟新通知中的进度条。
对于handler发送消息更新UI线程实现进度展示的时候一定注意不要太过频繁,过设置计数器隔一定时间才发送消息,不然容易引起系统奔溃
(2) 通知(Notification):提供系统默认自带形式以及自定义通知栏布局两种形式。
(3) 服务:后台服务,startService启动模式
package com.example.test; import java.io.BufferedInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import android.annotation.SuppressLint; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.StrictMode; import android.util.Log; @SuppressLint("NewApi") public class DownFileThread implements Runnable { public final static int DOWNLOAD_COMPLETE = -2; public final static int DOWNLOAD_FAIL = -1; public final static String TAG = "DownFileThread"; Handler mHandler; //传入的Handler,用于像Activity或service通知下载进度 String urlStr; //下载URL File apkFile; //文件保存路径 boolean isFinished; //下载是否完成 boolean interupted=false; //是否强制停止下载线程 public DownFileThread(Handler handler,String urlStr,String filePath) { Log.i(TAG, urlStr); this.mHandler=handler; this.urlStr=urlStr; apkFile=new File(filePath); isFinished=false; } public File getApkFile() { if(isFinished) return apkFile; else return null; } public boolean isFinished() { return isFinished; } /** * 强行终止文件下载 */ public void interuptThread() { interupted=true; } @Override public void run() { // TODO Auto-generated method stub if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { java.net.URL url = null; HttpURLConnection conn = null; InputStream iStream = null; // if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } try { url = new java.net.URL(urlStr); conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5000); conn.setReadTimeout(20000); iStream = conn.getInputStream(); } catch (MalformedURLException e) { Log.i(TAG, "MalformedURLException"); e.printStackTrace(); } catch (Exception e) { Log.i(TAG, "获得输入流失败"); e.printStackTrace(); } FileOutputStream fos = null; try { fos = new FileOutputStream(apkFile); } catch (FileNotFoundException e) { Log.i(TAG, "获得输出流失败:new FileOutputStream(apkFile);"); e.printStackTrace(); } BufferedInputStream bis = new BufferedInputStream(iStream); byte[] buffer = new byte[1024]; int len; // 获取文件总长度 int length = conn.getContentLength(); double rate=(double)100/length; //最大进度转化为100 int total = 0; int times=0;//设置更新频率,频繁操作UI线程会导致系统奔溃 try { Log.i("threadStatus", "开始下载"); while (false==interupted && ((len = bis.read(buffer)) != -1)) { fos.write(buffer, 0, len); // 获取已经读取长度 total += len; int p=(int)(total*rate); Log.i("num", rate+","+total+","+p); if(times>=512 || p==100) {/* 这是防止频繁地更新通知,而导致系统变慢甚至崩溃。 非常重要。。。。。*/ Log.i("time", "time"); times=0; Message msg = Message.obtain(); msg.what =p ; mHandler.sendMessage(msg); } times++; } fos.close(); bis.close(); iStream.close(); if(total==length) { isFinished=true; mHandler.sendEmptyMessage(DOWNLOAD_COMPLETE); Log.i(TAG, "下载完成结束"); } Log.i(TAG, "强制中途结束"); //mhandler.sendEmptyMessage(4); } catch (IOException e) { Log.i(TAG, "异常中途结束"); mHandler.sendEmptyMessage(DOWNLOAD_FAIL); e.printStackTrace(); } } else { Log.i(TAG, "外部存储卡不存在,下载失败!"); mHandler.sendEmptyMessage(DOWNLOAD_FAIL); } } }
package com.example.test; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.widget.RemoteViews; /** * Notification类,既可用系统默认的通知布局,也可以用自定义的布局 * * @author lz * */ public class MyNotification { public final static int DOWNLOAD_COMPLETE = -2; public final static int DOWNLOAD_FAIL = -1; Context mContext; //Activity或Service上下文 Notification notification; //notification NotificationManager nm; String titleStr; //通知标题 String contentStr; //通知内容 PendingIntent contentIntent; //点击通知后的动作 int notificationID; //通知的唯一标示ID int iconID; //通知栏图标 long when = System.currentTimeMillis(); RemoteViews remoteView=null; //自定义的通知栏视图 /** * * @param context Activity或Service上下文 * @param contentIntent 点击通知后的动作 * @param id 通知的唯一标示ID */ public MyNotification(Context context,PendingIntent contentIntent,int id) { // TODO Auto-generated constructor stub mContext=context; notificationID=id; this.contentIntent=contentIntent; this.nm=(NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); } /** * 显示自定义通知 * @param icoId 自定义视图中的图片ID * @param titleStr 通知栏标题 * @param layoutId 自定义布局文件ID */ public void showCustomizeNotification(int icoId,String titleStr,int layoutId) { this.titleStr=titleStr; notification=new Notification(R.drawable.ic_launcher, titleStr, when); notification.flags = Notification.FLAG_ONLY_ALERT_ONCE; notification.flags |= Notification.FLAG_AUTO_CANCEL; notification.contentIntent=this.contentIntent; // 1、创建一个自定义的消息布局 view.xml // 2、在程序代码中使用RemoteViews的方法来定义image和text。然后把RemoteViews对象传到contentView字段 if(remoteView==null) { remoteView = new RemoteViews(mContext.getPackageName(),layoutId); remoteView.setImageViewResource(R.id.ivNotification,icoId); remoteView.setTextViewText(R.id.tvTitle, titleStr); remoteView.setTextViewText(R.id.tvTip, "开始下载"); remoteView.setProgressBar(R.id.pbNotification, 100, 0, false); notification.contentView = remoteView; } nm.notify(notificationID, notification); } /** * 更改自定义布局文件中的进度条的值 * @param p 进度值(0~100) */ public void changeProgressStatus(int p) { if(notification.contentView!=null) { if(p==DOWNLOAD_FAIL) notification.contentView.setTextViewText(R.id.tvTip , "下载失败! "); else if(p==100) notification.contentView.setTextViewText(R.id.tvTip , "下载完成,请点击安装"); else notification.contentView.setTextViewText(R.id.tvTip , "进度("+p+"%) : "); notification.contentView.setProgressBar(R.id.pbNotification, 100, p, false); } nm.notify(notificationID, notification); } public void changeContentIntent(PendingIntent intent) { this.contentIntent=intent; notification.contentIntent=intent; } /** * 显示系统默认格式通知 * @param iconId 通知栏图标ID * @param titleText 通知栏标题 * @param contentStr 通知栏内容 */ public void showDefaultNotification(int iconId,String titleText,String contentStr) { this.titleStr=titleText; this.contentStr=contentStr; this.iconID=iconId; notification=new Notification(); notification.tickerText=titleStr; notification.icon=iconID; notification.flags = Notification.FLAG_INSISTENT; notification.flags |= Notification.FLAG_AUTO_CANCEL; notification.contentIntent=this.contentIntent; // 添加声音效果 // notification.defaults |= Notification.DEFAULT_SOUND; // 添加震动,后来得知需要添加震动权限 : Virbate Permission // mNotification.defaults |= Notification.DEFAULT_VIBRATE ; //添加状态标志 //FLAG_AUTO_CANCEL 该通知能被状态栏的清除按钮给清除掉 //FLAG_NO_CLEAR 该通知能被状态栏的清除按钮给清除掉 //FLAG_ONGOING_EVENT 通知放置在正在运行 //FLAG_INSISTENT 通知的音乐效果一直播放 notification.flags = Notification.FLAG_ONLY_ALERT_ONCE; changeNotificationText(contentStr); } /** * 改变默认通知栏的通知内容 * @param content */ public void changeNotificationText(String content) { notification.setLatestEventInfo(mContext, titleStr, content,contentIntent); // 设置setLatestEventInfo方法,如果不设置会App报错异常 // NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); //注册此通知 // 如果该NOTIFICATION_ID的通知已存在,会显示最新通知的相关信息 ,比如tickerText 等 nm.notify(notificationID, notification); } /** * 移除通知 */ public void removeNotification() { // 取消的只是当前Context的Notification nm.cancel(notificationID); } }
package com.example.test; import java.io.File; 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.provider.Settings.Global; import android.util.Log; public class DownloadServices extends Service { private final static int DOWNLOAD_COMPLETE = -2; private final static int DOWNLOAD_FAIL = -1; //自定义通知栏类 MyNotification myNotification; String filePathString; //下载文件绝对路径(包括文件名) //通知栏跳转Intent private Intent updateIntent = null; private PendingIntent updatePendingIntent = null; DownFileThread downFileThread; //自定义文件下载线程 private Handler updateHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case DOWNLOAD_COMPLETE: //点击安装PendingIntent Uri uri = Uri.fromFile(downFileThread.getApkFile()); Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(uri, "application/vnd.android.package-archive"); updatePendingIntent = PendingIntent.getActivity(DownloadServices.this, 0, installIntent, 0); myNotification.changeContentIntent(updatePendingIntent); myNotification.notification.defaults=Notification.DEFAULT_SOUND;//铃声提醒 myNotification.changeNotificationText("下载完成,请点击安装!"); //停止服务 // myNotification.removeNotification(); stopSelf(); break; case DOWNLOAD_FAIL: //下载失败 // myNotification.changeProgressStatus(DOWNLOAD_FAIL); myNotification.changeNotificationText("文件下载失败!"); stopSelf(); break; default: //下载中 Log.i("service", "default"+msg.what); // myNotification.changeNotificationText(msg.what+"%"); myNotification.changeProgressStatus(msg.what); } } }; public DownloadServices() { // TODO Auto-generated constructor stub // mcontext=context; Log.i("service","DownloadServices1"); } @Override public void onCreate() { // TODO Auto-generated method stub Log.i("service","onCreate"); super.onCreate(); } @Override public void onDestroy() { // TODO Auto-generated method stub Log.i("service","onDestroy"); if(downFileThread!=null) downFileThread.interuptThread(); stopSelf(); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // TODO Auto-generated method stub Log.i("service","onStartCommand"); updateIntent = new Intent(this, MainActivity.class); PendingIntent updatePendingIntent = PendingIntent.getActivity(this,0,updateIntent,0); myNotification=new MyNotification(this, updatePendingIntent, 1); // myNotification.showDefaultNotification(R.drawable.ic_launcher, "测试", "开始下载"); myNotification.showCustomizeNotification(R.drawable.ic_launcher, "测试下载", R.layout.notification); filePathString=Environment.getExternalStorageDirectory().getAbsolutePath() + "/family.apk"; //开启一个新的线程下载,如果使用Service同步下载,会导致ANR问题,Service本身也会阻塞 downFileThread=new DownFileThread(updateHandler,"http://10.103.241.247:8013/update/download",filePathString); new Thread(downFileThread).start(); return super.onStartCommand(intent, flags, startId); } @Override @Deprecated public void onStart(Intent intent, int startId) { // TODO Auto-generated method stub Log.i("service","onStart"); super.onStart(intent, startId); } @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub Log.i("service","onBind"); return null; } }