Update 升级项目
Update 流程及问题分析
前言
前段时间做了一个Update 升级的维护项目,有很多波折也发现了自己的很多问题,所以特地写篇博客总结一下。这个项目最开始是一个同事写的,他在老的Update 代码上自己新建了几个类 实现了现在的需求。后来这个升级应用要在一个很紧急的项目上上线,测试妹子发现了一些bug,然后就转到我来维护解决,结果接手后发现远远不止解决表面的几个bug这么简单。刚开始的时候在别人的代码和思路框架上小修小补,解决了一些bug,结果测试测着测着每次都会有不同的新的问题爆出来,隐隐约约的感觉像有大问题要发生。但那个时候急着解决完问题发版本,有想过彻底改一下流程框架,但觉得时间紧迫没有去那样做。最终的结果是那一周我每天都加班到回家都是12点,然而直到第二天上线这个项目的bug 还是没有清完。最终的结果就是这个应用没有上线,而我也被批了一顿 ······
当这个项目陷入胶着的时候,我开始反思为什么会造成今天这样的局面。既然摔了一跤,那就应该好好总结一番,有所收获和进步也不枉跌倒的痛 !
框架流程总结
这个项目有三大功能,分为强制升级、静默升级、在线升级 和 本地升级。
开机后系统后台需要一直检测如果有网络连接就会直接下载,进入界面时会显示下载进度。在强制升级时后台下载完了会直接弹出 Dialog 窗口,且不能取消和退出。
最开始别人写的流程框架是:
这样子写在改代码过程中就发现了这些问题。
- Service 和Activity 交互时采用广播通讯,但是广播往往都会有延时。而且后面我增加Activity重新新检测下载功能时继续采用广播就发现Service里面到处都是sendBroadCast( )方法。
- IntentService 是用完自己关闭的,Activity发送重新检测的广播,IntentService 已经关闭了不会再重新执行检测。
- 在线Activity更新 UI时有太多的状态改变,不同的Button 在不同状态下都有不同功能,当前界面用很多boolean变量去区分会很凌乱,可读性也不强。
后来在接下来的两周时间里我重构代码了代码,做了一些小的改变。
1、用接口回调的方式替代广播通讯,并且Service 和Activity 交互用Binder替代
2、采用Service替代IntentService
3、参考系统源码中Wifi的状态控制,定义一个状态类控制整个在线升级的检测、查询、下载、升级中的所有状态改变对应的UI显示
现在的流程框架是:
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
[email protected]_M61_INT:/ # 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
[email protected]_M61_INT:/ # 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也是没有必要用的。