实现App自动更新,一个方法搞定_chw12341的博客-程序员信息网_app如何实现自动更新

本问系转载,原文地址http://blog.csdn.net/shan_yao/article/details/51881913

前言

前段时间写了一个篇APP自动更新下载的文章自动更新,一个方法搞定,使用系统的DownloadManager 方法超简洁的实现了apk的下载,不过有好多网友反映有一些机型上面这个方法无法实现下载,经过小编的实验在部分机型上确实会有这个问题,所以其中下载的部分只能通过其它方法搞定了。正好看到网上好多关于使用Retrofit实现下载并且监听进度的文章,并且我一直在看Retrofit的东西但是一直没有机会用到,所以我正好拿这个练练手,最终我使用Retrofit + OkHttp + RxBus + Notification + Service实现了这个自动更新下载apk的功能。Demo已经上传的github了大家可以下载下来自己看看,你不仅可以解决App更新的问题,并且可以通过实践了解到一些比较不错的技术。github地址:https://github.com/shanyao0/DownLoadManager,大家多多star和fork,谢谢。。。

原理

基本原理和使用方法跟自动更新,一个方法搞定一样大家不懂得可以参考这里,同样还是一个方法搞定超级简单,不过只是这次的逼格更高,机型兼容性更好。这次手动实现了下载和系统通知进度功能。

  • Retrofit2和okhttp实现了apk的下载
  • 自定义类实现Retrofit2的Callback类在里面通过IO流写入文件并且使用RxBus订阅下载进度
  • 自定义类实现okhttp3的ResponseBody类并且在里面使用RxBus发布下载进度信息
  • 在Service中使用Retrofit在后台下载文件
  • 发送Notifaction到通知栏前台界面展示进度情况

所以我希望大家可以跟着我下面的实现步骤一步一步的实现这个功能,这样你不仅可以在你的项目中使用高逼格的技术,还可以对这些技术有一个比较初步的认识。

实现步骤

1. 创建UpdateManger管理类

这个类主要写了两个管理更新和弹框的方法,比较简单,跟自动更新,一个方法搞定的差不多除了下载部分

  /**
 - 检测软件更新
   */
  public void checkUpdate(final boolean isToast) {
      /**
       * 在这里请求后台接口,获取更新的内容和最新的版本号
       */
      // 版本的更新信息
      String version_info = "更新内容\n" + "    1. 车位分享异常处理\n" + "    2. 发布车位折扣格式统一\n" + "    ";
      int mVersion_code = DeviceUtils.getVersionCode(mContext);// 当前的版本号
      int nVersion_code = 2;
      if (mVersion_code < nVersion_code) {
          // 显示提示对话
          showNoticeDialog(version_info);
      } else {
          if (isToast) {
              Toast.makeText(mContext, "已经是最新版本", Toast.LENGTH_SHORT).show();
          }
      }
  }

    /**
     * 显示更新对话框
     *
     * @param version_info
     */
    private void showNoticeDialog(String version_info) {
        // 构造对话框
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        builder.setTitle("更新提示");
        builder.setMessage(version_info);
        // 更新
        builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                // 启动后台服务下载apk
                mContext.startService(new Intent(mContext, DownLoadService.class));
            }
        });
        // 稍后更新
        builder.setNegativeButton("以后更新", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        Dialog noticeDialog = builder.create();
        noticeDialog.show();
    }
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

2. 初始化RxBus进行简单封装

RxBus的使用可以参考这里用RxJava实现事件总线

import rx.Observable;
import rx.subjects.PublishSubject;
import rx.subjects.SerializedSubject;
import rx.subjects.Subject;

public class RxBus {
    

    private static volatile RxBus mInstance;
    private final Subject<Object, Object> bus;

    private RxBus() {
        bus = new SerializedSubject<>(PublishSubject.create());
    }

    /**
     * 单例RxBus
     *
     * @return
     */
    public static RxBus getDefault() {
        RxBus rxBus = mInstance;
        if (mInstance == null) {
            synchronized (RxBus.class) {
                rxBus = mInstance;
                if (mInstance == null) {
                    rxBus = new RxBus();
                    mInstance = rxBus;
                }
            }
        }
        return rxBus;
    }

    /**
     * 发送一个新事件
     *
     * @param o
     */
    public void post(Object o) {
        bus.onNext(o);
    }

    /**
     * 返回特定类型的被观察者
     *
     * @param eventType
     * @param <T>
     * @return
     */
    public <T> Observable<T> toObservable(Class<T> eventType) {
        return bus.ofType(eventType);
    }

}
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

3. 自己定制ResponseBody类FileResponseBody

public class FileResponseBody extends ResponseBody {
    

    Response originalResponse;

    public FileResponseBody(Response originalResponse) {
        this.originalResponse = originalResponse;
    }

    @Override
    public MediaType contentType() {
        return originalResponse.body().contentType();
    }

    @Override
    public long contentLength() {
   // 返回文件的总长度,也就是进度条的max
        return originalResponse.body().contentLength();
    }

    @Override
    public BufferedSource source() {
        return Okio.buffer(new ForwardingSource(originalResponse.body().source()) {
            long bytesReaded = 0;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                bytesReaded += bytesRead == -1 ? 0 : bytesRead;
                // 通过RxBus发布进度信息
                RxBus.getDefault().post(new FileLoadingBean(contentLength(), bytesReaded));
                return bytesRead;
            }
        });
    }
}
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

这个别问我问啥这样写,是从ophttp3的官方demo里面搞过来的东西,如果大家有精力可以研究下,到时别忘了告诉我一声,谢谢

4. 自己定制Callback类FileCallback

一个抽象类实现了Callback的onResponse方法并且在里面利用IO流把文件保存到了本地 
定义了两个抽象方法让子类实现,onSuccess()当读写完成之后将文件回调给实现类以便安装apk,onLoading()在文件读写的过程中通过订阅下载的进度把进度信息progress和total回调给实现类以便在通知中实时显示进度信息

public abstract class FileCallback implements Callback<ResponseBody>{
    
    /**
     * 订阅下载进度
     */
    private CompositeSubscription rxSubscriptions = new CompositeSubscription();
    /**
     * 目标文件存储的文件夹路径
     */
    private String destFileDir;
    /**
     * 目标文件存储的文件名
     */
    private String destFileName;

    public FileCallback(String destFileDir, String destFileName) {
        this.destFileDir = destFileDir;
        this.destFileName = destFileName;
        subscribeLoadProgress();// 订阅下载进度
    }
    /**
     * 成功后回调
     */
    public abstract void onSuccess(File file);

    /**
     * 下载过程回调
     */
    public abstract void onLoading(long progress, long total);

    /**
     * 请求成功后保存文件
     */
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
            saveFile(response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 通过IO流写入文件
     */
    public File saveFile(Response<ResponseBody> response) throws Exception {
        InputStream in = null;
        FileOutputStream out = null;
        byte[] buf = new byte[2048];
        int len;
        try {
            File dir = new File(destFileDir);
            if (!dir.exists()) {
   // 如果文件不存在新建一个
                dir.mkdirs();
            }
            in = response.body().byteStream();
            File file = new File(dir,destFileName);
            out = new FileOutputStream(file);
            while ((len = in.read(buf)) != -1){
                out.write(buf,0,len);
            }
            // 回调成功的接口
            onSuccess(file);
            unSubscribe();// 取消订阅
            return file;
        }finally {
            in.close();
            out.close();
        }
    }
    /**
     * 订阅文件下载进度
     */
    private void subscribeLoadProgress() {
        rxSubscriptions.add(RxBus.getDefault()
                .toObservable(FileLoadingBean.class)// FileLoadingBean保存了progress和total的实体类
                .onBackpressureBuffer()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<FileLoadingBean>() {
                    @Override
                    public void call(FileLoadingBean fileLoadEvent) {
                        onLoading(fileLoadEvent.getProgress(), fileLoadEvent.getTotal());
                    }
                }));
    }
    /**
     * 取消订阅,防止内存泄漏
     */
    private void unSubscribe() {
        if (!rxSubscriptions.isUnsubscribed()) {
            rxSubscriptions.unsubscribe();
        }
    }
}
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
保存了progress和total的实体类
public class FileLoadingBean {
    
    /**
     * 文件大小
     */
    long total;
    /**
     * 已下载大小
     */
    long progress;

    public long getProgress() {
        return progress;
    }

    public long getTotal() {
        return total;
    }

    public FileLoadingBean(long total, long progress) {
        this.total = total;
        this.progress = progress;
    }
}
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5. 在后台Service中利用Retrofit2和okhttp下载并安装apk,同时发送通知在前台展示下载进度

Retrofit和okhttp的使用请参考鸿洋大神的Retrofit2 完全解析 探索与okhttp之间的关系 
通知Notification大家直接参考官网Notification就行

public class DownLoadService extends Service {
    

    /**
     * 目标文件存储的文件夹路径
     */
    private String  destFileDir = Environment.getExternalStorageDirectory().getAbsolutePath() + File
            .separator + "M_DEFAULT_DIR";
    /**
     * 目标文件存储的文件名
     */
    private String destFileName = "shan_yao.apk";

    private Context mContext;
    private int preProgress = 0;
    private int NOTIFY_ID = 1000;
    private NotificationCompat.Builder builder;
    private NotificationManager notificationManager;
    private Retrofit.Builder retrofit;
    /**
     * 为什么在这个方法调用下载的逻辑?而不是onCreate?我在下面有解释
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mContext = this;
        loadFile();
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * 下载文件
     */
    private void loadFile() {
        initNotification();
        if (retrofit == null) {
            retrofit = new Retrofit.Builder();
        }
        // 使用Retrofit进行文件的下载
        retrofit.baseUrl("http://112.124.9.133:8080/parking-app-admin-1.0/android/manager/adminVersion/")
                .client(initOkHttpClient())
                .build()
                .create(IFileLoad.class)
                .loadFile()
                .enqueue(new FileCallback(destFileDir, destFileName) {

                    @Override
                    public void onSuccess(File file) {
                        Log.e("zs", "请求成功");
                        // 安装软件
                        cancelNotification();
                        installApk(file);
                    }

                    @Override
                    public void onLoading(long progress, long total) {
                        Log.e("zs", progress + "----" + total);
                        updateNotification(progress * 100 / total);// 更新前台通知
                    }

                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        Log.e("zs", "请求失败");
                        cancelNotification();// 取消通知
                    }
                });
    }

    public interface IFileLoad {
    
        @GET("download")
        Call<ResponseBody> loadFile();
    }

    /**
     * 安装软件
     *
     * @param file
     */
    private void installApk(File file) {
        Uri uri = Uri.fromFile(file);
        Intent install = new Intent(Intent.ACTION_VIEW);
        install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        install.setDataAndType(uri, "application/vnd.android.package-archive");
        // 执行意图进行安装
        mContext.startActivity(install);
    }

    /**
     * 初始化OkHttpClient
     *
     * @return
     */
    private OkHttpClient initOkHttpClient() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(100000, TimeUnit.SECONDS);
        builder.networkInterceptors().add(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Response originalResponse = chain.proceed(chain.request());
                return originalResponse
                        .newBuilder()
                        .body(new FileResponseBody(originalResponse))//将自定义的ResposeBody设置给它
                        .build();
            }
        });
        return builder.build();
    }

    /**
     * 初始化Notification通知
     */
    public void initNotification() {
        builder = new NotificationCompat.Builder(mContext)
                .setSmallIcon(R.mipmap.ic_launcher)// 设置通知的图标
                .setContentText("0%")// 进度Text
                .setContentTitle("QQ更新")// 标题
                .setProgress(100, 0, false);// 设置进度条
        notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);// 获取系统通知管理器
        notificationManager.notify(NOTIFY_ID, builder.build());// 发送通知 
    }

    /**
     * 更新通知
     */
    public void updateNotification(long progress) {
        int currProgress = (int) progress;
        if (preProgress < currProgress) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, (int) progress, false);
            notificationManager.notify(NOTIFY_ID, builder.build());
        }
        preProgress = (int) progress;
    }

    /**
     * 取消通知
     */
    public void cancelNotification() {
        notificationManager.cancel(NOTIFY_ID);
    }
}
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145

需要注意的几个地方

  • 为什么在onStartCommand里面下载apk而不是onCreate里面

    这个跟service的生命周期有关系,onStartCommand()在每次调用startService时候都会调用一次,而onCreate()方法只有在服务第一次启动的时候才会掉用。当一个服务已经开启了那么再次调用startService不会在调用onCreate()方法,而onStartCommand()会被再次调用。

  • 发送通知设置进度用的是setProgress,这个方法只有在4.0以上才能用

    如果不用这个方法想要兼容低版本,就只能使用自定义的Notification,然后自己创建一个含有ProgressBar的layout布局,但是自定义的Notification在不同的系统中的适配处理太麻烦了,因为不同的系统的通知栏的背景颜色不一致,我们需要对不同的背景做不同的适配才能保证上面的文字能够正常显示,比方你写死了一个白色的文字,但是系统通知的背景也是白色,这样一来文字就不能正常显示了,当然如果使用系统的通知样式无法满足你的需求,只能使用自定义样式,可以参考这篇文章Android自定义通知样式适配。如果仅仅是为了兼容低版本我个人感觉完全没有必要,大家可以看看友盟指数,所以没有必要去做兼容。这个看不同的需求而定。 
    指数

  • 在更新通知时我做了判断

    在更新通知时我做了判断看下代码,这里我设置只有当进度等于1时并且发生改变时做更新,因为progress的值可能为1.1,1.2,1.3,但是显示的都会是1%,如果我不做判断限制那么每次onLoading的回调都会更新通知内容,也就是当1.1时会更新,1.2时也会执行更新的方法,但是前台展示的都是1%,所以做了好多无用功,并且当你不做限制时整个app会非常的卡,有时会卡死。这个你可以不做限制自己运行下Demo试试,绝壁卡死。

    /**
     * 更新通知
     */
    public void updateNotification(long progress) {
        int currProgress = (int) progress;
        if (preProgress < currProgress) {
            builder.setContentText(progress + "%");
            builder.setProgress(100, (int) progress, false);
            notificationManager.notify(NOTIFY_ID, builder.build());
        }
        preProgress = (int) progress;
    }
 
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chw12341/article/details/72764061

智能推荐

2020年专转本计算机知识点,2020专转本计算机真题(2020年-2021年).pdf_weixin_39537049的博客-程序员信息网

2015 一、判断题(本大题共 20 小题,每小题 1 分,共 20 分。)1.现代计算机采用的是冯·诺依曼提出的“存储程序控制”思想,科学家们正在研究的生物计算机采用非冯·诺依曼结构。2 .通信系统的基本任务是传递信息,至少需由信源、信宿和信息三个要素组成。3 .CPU、内存储、总线等构成了计算机的“主机” , 外存储器、输入/输出设...

硅基光子学回路设计:方法,工具及挑战---(1)_faust_cao的博客-程序员信息网

未来的光集成的一个重要方向是硅基光电子集成,本文探讨了硅基PIC设计的系统设计流程。

Java MVC模式_快乐的小京巴的博客-程序员信息网

众所周知,我是一个程序猿,随时写博客是很正常的一件事对吧啊 SpringMVC是常见的框架,同时也是一种设计模式,大家应该都使用过ssm,其中就包含了SpringMVC模式,因为这种模式在我看来最大的优点就是将数据层,视图层和控制层分开了 M:数据层,也就是存放数据或者说是用来与数据库交互,我们存储以及获取数据都是通过M层来进行的 V:视图层,视图层是因为之前...

nginx 深入详解多进程模型_??yy的博客-程序员信息网

Nginx深入详解之多进程网络模型2013-11-03 15:17:57分类:Web开发一、进程模型 Nginx之所以为广大码农喜爱,除了其高性能外,还有其优雅的系统架构。与Memcached的经典多线程模型相比,Nginx是经典的多进程模型。Nginx启动后以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程,具体如下图...

iOS开发-常用第三方开源框架介绍(你了解的ios只是冰山一角)_dengxu4495的博客-程序员信息网

图像:1.图片浏览控件MWPhotoBrowser 实现了一个照片浏览器类似iOS自带的相册应用,可显示来自手机的图片或者是网络图片,可自动从网络下载图片并进行缓存。可对图片进行缩放等操作。 下载:https://github.com/mwaterfall/MWPhotoBrowser目前比较活跃的社区仍旧是Github,除此以外也有一些不...

一文带你迅速了解Redis基础篇_刀哥谈Java的博客-程序员信息网

简介Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。它支持诸如字符串、散列、列表、集、带范围查询的排序集、位图、hyperloglog、带半径查询和流的地理空间索引等数据结构。Redis具有内置的复制、Lua脚本、LRU清除、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis集群的自动分区提供高可用性。原理与架构Redis使...

随便推点

tsconfig.json配置_大大大石頭的博客-程序员信息网_tsconfig.json

{ // 用来配置编译选项 "compilerOptions": { "target": "esnext",// 生成js 的版本,下一版本 "module": "esnext", // 生成的module的形式,esm,cmd,amd啥的 "strict": false, // 是否严格模式 "jsx": "preserve", // jsx用于的开发环境,preserve/react/RN "importHelpers": true, // 指定是否引入.

Linux和Windows的redis远程连接_weixin_43351935的博客-程序员信息网

1.本地启动redis,中间有连个词拼错了,就打马赛克了,2.Windows本地的IP地址为192.168.31.104, 我在Linux系统下连接一下,在Linux下我并没有新建数据库数据,因为本地新建的有,可以直接获取。slave端启动 这个为slave端3.打开redis桌面管理工具,可以查看数据库中的值,...

web 全栈开发-实战项目-四 连接mysql数据库--Sequelize_小dobby的博客-程序员信息网

web 全栈开发-实战项目- 连接mysql数据库–SequelizeSequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。Sequelize官网地址:https://sequelize.org/Sequelize中文文档地址:https://www.sequelize.com.cn/

linux恢复 rm -rf 删除的文件:extundelete_zkj_123的博客-程序员信息网

环境 centos1、依赖库下载extundelete依赖e2fsprogs。方法1 使用yum下载yum -y install e2fsprogs  e2fsprogs-devel方法2 编译安装----e2fsprogs下载地址:http://sourceforge.net/projects/e2fsprogs/files/e2fsprogs/1

跨平台低延迟的RTMP/RTSP直播播放器设计实现_daniulive的博客-程序员信息网

开发背景2015年,当我们试图在市面上找一款专供直播播放使用的低延迟播放器,来配合测试我们的RTMP推送模块使用时,居然发现没有一款好用的,市面上的,如VLC或Vitamio,说白了都是基于FFMPEG,在点播这块支持格式很多,也非常优异,但是直播这块,特别是RTMP,延迟要几秒钟,对如纯音频、纯视频播放,快速启播、网络异常状态处理、集成复杂度等各方面,支持非常差,而且因为功能强大,bug很多...

错误,各种错误_weixin_30530339的博客-程序员信息网

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [spring/applicationContext.xml]: Invocation of init method fai...

推荐文章

热门文章

相关标签