Android中使用Notification实现应用更新显示下载进度
2016-05-17 16:43
1041 查看
公司项目马上要进入内测阶段了,检查的时候发现还差一个更新的功能没有做,IOS那边儿有AppStore,Android可没有,虽然说各大应用市场也会推送吧,但是毕竟还要装个XX应用市场的软件不是,要是没装的话应用不就没法更新了么,考虑到这方面的原因还是决定写一下了。
然后就开始找资料吧,各种找,发现都不尽人意吧,在下资质愚钝,很多东西都是看个一只半解,希望自己写的清晰些,也能帮助一些人吧。
通知栏不会截图,,所以没办法看效果图了,直接上代码吧,最后会放出源码的。
首先是MainActivity类。其实就是个白页面,啥都没有。
然后是FileUtil,工具类直接从以前项目中拖过来的。其实也就用了其中一个方法。
最后就是最关键的service服务类了,DownUpdateApk
自定义Notification的布局文件也贴出来吧
就是这么多了,关键的地方我应该都写了注释的。
最后附上下载地址 :https://github.com/color9169/DownloadUpdateOnNotification/tree/master
然后就开始找资料吧,各种找,发现都不尽人意吧,在下资质愚钝,很多东西都是看个一只半解,希望自己写的清晰些,也能帮助一些人吧。
通知栏不会截图,,所以没办法看效果图了,直接上代码吧,最后会放出源码的。
首先是MainActivity类。其实就是个白页面,啥都没有。
package com.demo_download_notification.demo; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { Context mContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = this; showNoticeDialog("http://wsdownload.hdslb.net/app/BiliPlayer3.apk"); } /** * 显示软件更新对话框 */ public void showNoticeDialog(final String apkUrl) { // 构造对话框 AlertDialog.Builder builder = new AlertDialog.Builder(mContext); builder.setTitle("软件更新"); builder.setMessage("有新版本,建议更新!"); // 更新 builder.setPositiveButton("更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // 显示下载对话框 Intent serviceIntent = new Intent(mContext, DownUpdateApk.class); serviceIntent.putExtra("url", apkUrl); startService(serviceIntent); } }); // 稍后更新 builder.setNegativeButton("稍后更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); Dialog noticeDialog = builder.create(); noticeDialog.show(); } }
然后是FileUtil,工具类直接从以前项目中拖过来的。其实也就用了其中一个方法。
package com.demo_download_notification.demo.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.os.Environment; import android.os.Handler; import android.os.Message; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class FileUtil { /** * 将Bitmap 图片保存到本地路径,并返回路径 * * @param c * @param mType 资源类型,参照 MultimediaContentType 枚举,根据此类型,保存时可自动归类 * @param fileName 文件名称 * @param bitmap 图片 * @return */ public static String saveFile(Context c, String fileName, Bitmap bitmap) { return saveFile(c, "", fileName, bitmap); } public static String saveFile(Context c, String filePath, String fileName, Bitmap bitmap) { byte[] bytes = bitmapToBytes(bitmap); return saveFile(c, filePath, fileName, bytes); } public static boolean saveFile(InputStream inputStream, String path, String fileName, Handler mHandler, long fileLength) throws IOException { File newFile = new File(path); if (!newFile.exists()) { newFile.mkdir(); } newFile = new File(path + "/" + fileName); OutputStream outs = new FileOutputStream(newFile); int byteWritten = 0; int byteCount = 0; byte[] bytes = new byte[1024]; int percentage = 0; //保存百分比信息 int count = 0; int nowCount = 1; while ((byteCount = inputStream.read(bytes)) != -1) { count += byteCount; percentage = (int) (((float) count / fileLength) * 100); if (percentage > 0 && percentage == nowCount) { //每完成百分之一 通知Handler一次 nowCount = percentage + 1; Message msg = mHandler.obtainMessage(); msg.what = 1; msg.obj = percentage + ""; mHandler.sendMessage(msg); } outs.write(bytes, 0, byteCount); byteWritten += byteCount; } //写入完成,通知Handler 可以进行安装,改变通知栏ui Message msg = mHandler.obtainMessage(); msg.what = 2; msg.obj = percentage + ""; mHandler.sendMessage(msg); inputStream.close(); outs.close(); if (byteWritten > 0) { return true; } else { return false; } } public static byte[] bitmapToBytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(CompressFormat.JPEG, 100, baos); return baos.toByteArray(); } public static String saveFile(Context c, String filePath, String fileName, byte[] bytes) { String fileFullName = ""; FileOutputStream fos = null; String dateFolder = new SimpleDateFormat("yyyyMMdd", Locale.CHINA) .format(new Date()); try { String suffix = ""; File file = new File(getPath(filePath)); if (!file.exists()) { file.mkdirs(); } File fullFile = new File(filePath, fileName + suffix); fileFullName = fullFile.getPath(); fos = new FileOutputStream(new File(filePath, fileName + suffix)); fos.write(bytes); } catch (Exception e) { fileFullName = ""; } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { fileFullName = ""; } } } return fileFullName; } public static String getPath(String filePath) { if (filePath == null || filePath.trim().length() == 0) { filePath = Environment.getExternalStorageDirectory() + "/JiaXT/"; } else { filePath = ""; } return filePath; } public static String getPath() { return Environment.getExternalStorageDirectory() + "/TsingpuAkp/"; } /** * 删除单个文件 * * @param filePath 被删除文件的文件名 * @return 文件删除成功返回true,否则返回false */ public static boolean deleteFile(String filePath) { File file = new File(filePath); if (file.isFile() && file.exists()) { return file.delete(); } return false; } /** * 删除文件夹以及目录下的文件 * * @param filePath 被删除目录的文件路径 * @return 目录删除成功返回true,否则返回false */ public static boolean deleteDirectory(String filePath) { boolean flag = false; //如果filePath不以文件分隔符结尾,自动添加文件分隔符 if (!filePath.endsWith(File.separator)) { filePath = filePath + File.separator; } File dirFile = new File(filePath); if (!dirFile.exists() || !dirFile.isDirectory()) { return false; } flag = true; File[] files = dirFile.listFiles(); //遍历删除文件夹下的所有文件(包括子目录) for (int i = 0; i < files.length; i++) { if (files[i].isFile()) { //删除子文件 flag = deleteFile(files[i].getAbsolutePath()); if (!flag) break; } else { //删除子目录 flag = deleteDirectory(files[i].getAbsolutePath()); if (!flag) break; } } if (!flag) return false; //删除当前空目录 return dirFile.delete(); } /** * 根据路径删除指定的目录或文件,无论存在与否 * * @param filePath 要删除的目录或文件 * @return 删除成功返回 true,否则返回 false。 */ public static boolean DeleteFolder(String filePath) { File file = new File(filePath); if (!file.exists()) { return false; } else { if (file.isFile()) { // 为文件时调用删除文件方法 return deleteFile(filePath); } else { // 为目录时调用删除目录方法 return deleteDirectory(filePath); } } } /** * 保存文件 * * @param bm * @param fileName * @throws IOException */ public static File getFile(Bitmap bm, String fileName, String filePath) { try { String path = getPath(filePath); File dirFile = new File(path); if (!dirFile.exists()) { dirFile.mkdir(); } File myCaptureFile = new File(path + fileName); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile)); bm.compress(CompressFormat.JPEG, 80, bos); bos.flush(); bos.close(); return myCaptureFile; } catch (IOException e) { } return null; } }
最后就是最关键的service服务类了,DownUpdateApk
package com.demo_download_notification.demo; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.support.annotation.Nullable; import android.widget.RemoteViews; import com.demo_download_notification.demo.utils.FileUtil; import java.io.File; import java.io.IOException; import java.io.InputStream; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; /** * Created by color on 16/5/16. */ public class DownUpdateApk extends Service { NotificationManager notificationManager; Notification myNotify; private String url; private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message message) { int progress = Integer.parseInt(message.obj.toString()); RemoteViews rv; switch (message.what) { case 1: //更改通知栏UI布局 rv = new RemoteViews(getPackageName(), R.layout.my_notification); rv.setTextViewText(R.id.text_title, "正在下载中"); rv.setProgressBar(R.id.progress, 100, progress, false); rv.setTextViewText(R.id.text_content, progress + "%"); myNotify.contentView = rv; notificationManager.notify(0, myNotify); break; case 2: rv = new RemoteViews(getPackageName(), R.layout.my_notification); rv.setTextViewText(R.id.text_title, "下载完成,点击安装"); rv.setProgressBar(R.id.progress, 100, progress, false); rv.setTextViewText(R.id.text_content, progress + "%"); myNotify.contentView = rv; //下载完成,点击可以去安装文件 Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW);// android.intent.action.VIEW intent.setDataAndType(Uri.fromFile(new File(FileUtil.getPath() + "/Tsingpu.apk")), "application/vnd.android.package-archive"); myNotify.flags = Notification.FLAG_AUTO_CANCEL; myNotify.contentIntent = PendingIntent.getActivity(DownUpdateApk.this, 1, intent, 0); notificationManager.notify(0, myNotify); break; } return false; } }); @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); myNotify = new Notification(); myNotify.icon = R.mipmap.ic_launcher; myNotify.tickerText = "准备下载..."; myNotify.when = System.currentTimeMillis(); myNotify.flags = Notification.FLAG_NO_CLEAR;// 不能够自动清除 RemoteViews rv = new RemoteViews(getPackageName(), R.layout.my_notification); rv.setProgressBar(R.id.progress, 100, 0, false); rv.setTextViewText(R.id.text_content, "开始下载"); //这里就是使用自定义布局了 初始化的时候不设置Intent,点击的时候就不会有反应了,亏得我还找了好久 T-T myNotify.contentView = rv; notificationManager.notify(0, myNotify); } @Override public void onDestroy() { super.onDestroy(); url = null; myNotify = null; notificationManager = null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { url = intent.getStringExtra("url"); init(); return super.onStartCommand(intent, flags, startId); } public void init() { new Thread(new Runnable() { @Override public void run() { OkHttpClient client = new OkHttpClient(); //使用okhttp下载文件 Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { String s = e.getMessage(); } @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); InputStream is = response.body().byteStream(); //成功的回调中拿到字节流 String path = FileUtil.getPath(); long fileLength = response.body().contentLength(); //获取文件长度 FileUtil.saveFile(is, path, "test.apk", mHandler, fileLength); //保存下载的apk文件 } }); } }).start(); } }
自定义Notification的布局文件也贴出来吧
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@mipmap/ic_launcher" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/text_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在下载中" android:textSize="12sp" android:visibility="visible" /> <TextView android:id="@+id/text_finish" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/text_title" android:text="下载完成,点击安装" android:textSize="12sp" android:visibility="gone" /> <TextView android:id="@+id/text_content" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:text="下载进度" android:textSize="12sp" android:visibility="visible" /> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="40dp" android:layout_alignParentBottom="true" android:layout_below="@id/text_content" android:layout_weight="1" android:max="100" android:visibility="visible" /> </RelativeLayout> </LinearLayout>
就是这么多了,关键的地方我应该都写了注释的。
最后附上下载地址 :https://github.com/color9169/DownloadUpdateOnNotification/tree/master
相关文章推荐
- Android Studio 快捷键
- Android双击退出
- Android Service完全解析,关于服务你所需知道的一切
- android camera中antibanding是什么功能?
- android ViewPager用作广告轮播
- 玩转Android studio 插件开发、测试、JCenter库发布(3)
- Android开发笔记之属性动画解析
- android 高德地图 java.lang.UnsatisfiedLinkError: Native method not found: com.autonavi.amap.mapcore.MapC
- Retrofit2.0 android.os.NetworkOnMainThreadException解决方法
- Android PDF阅读
- Android onActivityResult获取返回值的用法
- 饿了么开源项目:便捷高效的Android数据持久化存储框架
- Fiddler手机抓包-Android手机
- Android之Intent全面解析及用法
- 浅谈android中图片处理之图形变换特效Matrix(四)
- Android的MVP框架
- Android ImageView高度自适应
- android 输入框EditText禁止输入Emoji表情符
- 一张图了解Android开源框架
- Android基础之十四数据存储 之 SQLite数据库详解