Android文件下载及自定义通知显示下载进度
jopen
10年前
主要实现了一下几个类:
(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); } }
</div> </div>
</div> </div>
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; } }