Update 升级项目

前言

前段时间做了一个Update 升级的维护项目,有很多波折也发现了自己的很多问题,所以特地写篇博客总结一下。这个项目最开始是一个同事写的,他在老的Update 代码上自己新建了几个类 实现了现在的需求。后来这个升级应用要在一个很紧急的项目上上线,测试妹子发现了一些bug,然后就转到我来维护解决,结果接手后发现远远不止解决表面的几个bug这么简单。刚开始的时候在别人的代码和思路框架上小修小补,解决了一些bug,结果测试测着测着每次都会有不同的新的问题爆出来,隐隐约约的感觉像有大问题要发生。但那个时候急着解决完问题发版本,有想过彻底改一下流程框架,但觉得时间紧迫没有去那样做。最终的结果是那一周我每天都加班到回家都是12点,然而直到第二天上线这个项目的bug 还是没有清完。最终的结果就是这个应用没有上线,而我也被批了一顿 ······

当这个项目陷入胶着的时候,我开始反思为什么会造成今天这样的局面。既然摔了一跤,那就应该好好总结一番,有所收获和进步也不枉跌倒的痛 !

框架流程总结

这个项目有三大功能,分为强制升级静默升级在线升级本地升级
开机后系统后台需要一直检测如果有网络连接就会直接下载,进入界面时会显示下载进度。在强制升级时后台下载完了会直接弹出 Dialog 窗口,且不能取消和退出。
最开始别人写的流程框架是:

开机广播
handler
广播 更新界面
BroadcastReceiver
IntentService
强制升级Dialog
在线Activity
本地升级Activity

这样子写在改代码过程中就发现了这些问题。

  1. Service 和Activity 交互时采用广播通讯,但是广播往往都会有延时。而且后面我增加Activity重新新检测下载功能时继续采用广播就发现Service里面到处都是sendBroadCast( )方法。
  2. IntentService 是用完自己关闭的,Activity发送重新检测的广播,IntentService 已经关闭了不会再重新执行检测。
  3. 在线Activity更新 UI时有太多的状态改变,不同的Button 在不同状态下都有不同功能,当前界面用很多boolean变量去区分会很凌乱,可读性也不强。

后来在接下来的两周时间里我重构代码了代码,做了一些小的改变。

1、用接口回调的方式替代广播通讯,并且Service 和Activity 交互用Binder替代
2、采用Service替代IntentService
3、参考系统源码中Wifi的状态控制,定义一个状态类控制整个在线升级的检测、查询、下载、升级中的所有状态改变对应的UI显示

现在的流程框架是:

开机广播
handler
Binder+接口更新界面
BroadcastReceiver
Service
强制升级Dialog
在线Activity
本地升级Activity

IntentService 和 Service 的区别以及bindService混合启动方式

因为我的项目中Service 需要被开机广播启动startService,然后Activity在前台的时候也要bindService被启动,所以总结一下混合启动方式的生命周期执行顺序。

一、startService 多次启动的方式

   //@param startId A unique integer representing this specific request to 
     // start.  Use with {@link #stopSelfResult(int)}.
@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, " szh onStartCommand-----startId=" + startId);
        return super.onStartCommand(intent, flags, startId);
    }
      
D/MainActivity( 2807): szh  click start Service---
D/MyService( 2807):  szh onCreate-----
D/MyService( 2807):  szh onStartCommand-----startId=1,Thread.currentThread().getId()=1
D/MainActivity( 2807): szh  click start Service---
D/MyService( 2807):  szh onStartCommand-----startId=2,Thread.currentThread().getId()=1
D/MainActivity( 2807): szh  click start Service---
D/MyService( 2807):  szh onStartCommand-----startId=3,Thread.currentThread().getId()=1

我们可以看到多次执行startService方法只有第一次会执行onCreate()方法,后面Service已经启动的情况下只会执行onStartCommand方法。但是这里面每次startService( )都会有一个新的StartId,我们看源码注释说的是这个StartId实际上只是一个标志启动请求的整形数据,并没有特殊含义。重要的是threadId相同,说明后面在服务已经开启的情况下只会执行onStartCommand 方法,并不会新开一个线程。

二、多次执行bindService

root@:/ # logcat |grep szh
D/MainActivity( 4302): szh click bind Service---
D/MyService( 4302):  szh onCreate-----
D/MyService( 4302):  szh on Bind-----,Thread.currentThread().getId()=1
D/MainActivity( 4302): szh onServiceConnected---
D/MainActivity( 4302): szh click bind Service---
D/MainActivity( 4302): szh click bind Service---

通过log我们可以看到多次执行bindService 只有第一次服务没有启动的时候才会执行onCreate–>onBind–>onServiceConnected
需要注意的是它不会执行onStartCommand方法,绑定服务以后再次调用bindService 方法他会不去多次调用onBind方法。

三、先startService再bindService

root@:/ # logcat | grep szh
D/MainActivity( 4544): szh onStart----
D/MainActivity( 4544): szh onResume----
D/MainActivity( 4544): szh  click start Service---
D/MyService( 4544):  szh onCreate-----
D/MyService( 4544):  szh onStartCommand-----startId=1,Thread.currentThread().getId()=1
D/MainActivity( 4544): szh click bind Service---
D/MyService( 4544):  szh on Bind-----,Thread.currentThread().getId()=1
D/MainActivity( 4544): szh onServiceConnected---
D/MainActivity( 4544): szh click bind Service---
D/MainActivity( 4544): szh click bind Service---
D/MainActivity( 4544): szh  click start Service---
D/MyService( 4544):  szh onStartCommand-----startId=2,Thread.currentThread().getId()=1

执行顺序:onCreate->onStartCommand->onBind->onServiceConnected
这里我们可以看到先startService的话会执行onCreate,然后是onStartCommand,如果再执行bindService 就执行onBind方法,后面同样的再执行多次bindService不会再执行onBind方法,如果再接着执行startService也只是执行onStartCommand

四、先bindService再startService

D/MainActivity( 4893): szh onStart----
D/MainActivity( 4893): szh onResume----
D/MainActivity( 4893): szh click bind Service---
D/MyService( 4893):  szh onCreate-----
D/MyService( 4893):  szh on Bind-----,Thread.currentThread().getId()=1
D/MyService( 4893):  szh onStartCommand-----startId=7,Thread.currentThread().getId()=1
D/MainActivity( 4893): szh onServiceConnected---
D/MainActivity( 4893): szh  click start Service---
D/MyService( 4893):  szh onStartCommand-----startId=8,Thread.currentThread().getId()=1

我们发现bindService后再startService 会执行onCreate–>onBind–>onStartCommand–>onServiceConnected–>onStartCommand

总结:
1、我们发现两种方式混合启动Service过程中Service的onCreate 方法和onBind方法都只执行过一次就不再执行了。
2、混合启动的服务所在的是同一个线程,不会新启动一个新的线程

大家有没有发现上面其实并没有执行Service的onDestroy方法,也就是刚刚进行这些操作的时候服务都没有被销毁。其实只有在bindService启动服务的情况下所有绑定的activity都解绑才会执行unbind–>onDestroy,如果bindService中间再执行startService的话再执行unbindService它是不会执行onDestroy销毁服务的,这个时候startService了除非执行stopSelf( ),否则服务不会销毁。

Service绑定服务的错误写法

---------------Activity中--------------
private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG,"szh onServiceConnected---");
            //myBind=(MyService.MyBind)service;
            //myBind.setOnServiceChangeListener(MainActivity.this);
            myService=((MyService.MyBind) service).getService();
            myService.checkDownload();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG,"szh onServiceDisconnected---");
        }
    };
----------------Service中----------------------
   public void checkDownload(){
        Log.d(TAG,"szh checkDownload---");
    }
    /*内部类*/
 public class MyBind extends Binder {
        private OnServiceChangeListener onServiceChangeListener;

        public MyService getService() {
            return MyService.this;
        }

        public void setOnServiceChangeListener(OnServiceChangeListener listener) {
            this.onServiceChangeListener = listener;
        }
    }

我们看一下这种写法先执行bindService后执行unbindService后的结果:

D/MainActivity( 2695): szh onStart----
D/MainActivity( 2695): szh onResume----
D/MainActivity( 2695): szh click bind Service---
D/MyService( 2695):  szh onCreate-----
D/MyService( 2695):  szh on Bind-----,Thread.currentThread().getId()=1
D/MyService( 2695):  szh onStartCommand-----startId=3,Thread.currentThread().getId()=1
D/MainActivity( 2695): szh onServiceConnected---
D/MyService( 2695): szh checkDownload---
D/MainActivity( 2695): szh click unbind Service---
D/MyService( 2695):  szh onUnbind-----

结果它并不会执行完onUnbind方法后onDestroy方法销毁服务。为什么呢 ?因为他通过getService方法得到了一个Service对象,通过对象去调用它里面的方法这和普通类有什么区别 ?如果服务被销毁了直接就造成内存泄露了,这样子写完全没有通过binder去绑定服务,更可怕的是csdn 上搜的博客十篇里面有5篇以上都是这么写的,真是细思极恐,没有自己去跑过的就copy过来这样子简直就是误人子弟!binder的核心思想是暴露接口给client端调用,client 只需要持有有Service的binder 对象就可以了进行需要的操作就可以了,不能直接去调用Service里面的方法,这样写是很不规范的,希望我踩的坑大家看了这里能以后不要犯这种低级的错误。

关于进程间通信的几种方式的反思

什么是进程什么是线程

当我们的应用程序在被启动的时候系统会分配一个Lniux进程给这个程序,而我们的应用程序中又会有很多线程。线程是进程的有机组成部分,是CPU调度的基础。所以我们可以理解为进程是包含线程的。

进程间通信和线程间通信的方式有哪些。
进程间通信方式:
BroadCast
AIDL
ContentProvider
Messager
Socket

线程间通信方式:
Handler
AsyncTask
runInUIThread

具体方式可以参考这篇博客。
Android 进程间通信和线程间通信的总结
AIDL和广播是常用的进程进程间通信方式,了解了这些我们就知道为什么前面最开始的框架里用广播作为Service 和Activity的交互方式是不可取的,而且在同一个进程中AIDL也是没有必要用的。

参考博客

1、Android Recovery升级原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mrsongs的心情杂货铺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值