RxJava的订阅关系
Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Throwable { emitter.onNext(1); emitter.onComplete(); } }).subscribe(new Observer<Integer>() { @Override public void onNext(Integer integer) { Log.d(TAG, "onNext: " + integer); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show(); } });
代码中主要有三个角色:
被订阅者Observable
,是整个事件的来源,可以发射数据给订阅者。订阅者Observer
,通过subscribe方法和被订阅者产生关系,也就是开始订阅,同时可以接受被订阅者发送的消息。发射器Subscriber/Emitter
,在Rxjava2之后,发射器改为了Emitter,他的作用主要是用来发射一系列事件的,比如next事件,complete事件等等。
有了这三个角色,一个完整的订阅关系也就生成了。
Observer处理完onComplete后会还能onNext吗?
要弄清楚这个问题,得去看看onComplete,onNext方法到底做了什么。
@Override public void onComplete() { if (!isDisposed()) { try { observer.onComplete(); } finally { dispose(); } } } @Override public void onNext(T t) { if (t == null) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); return; } if (!isDisposed()) { observer.onNext(t); } } public static boolean isDisposed(Disposable d) { return d == DISPOSED; } public static boolean dispose(AtomicReference<Disposable> field) { Disposable current = field.get(); Disposable d = DISPOSED; if (current != d) { current = field.getAndSet(d); if (current != d) { if (current != null) { current.dispose(); } return true; } } return false; }
源码还是比较清晰明了,无论是onComplete
还是onNext
,都会判断当前订阅是否被取消,也就是Disposable类型的变量的引用是否等于DISPOSED
,如果等于则代表该订阅已经被取消,起点和终点已经断开联系。而在onComplete方法的结尾调用了dispose
方法,将原子引用类中的 Disposable
对象设置为 DisposableHelper 内的 DISPOSED
枚举实例,即断开订阅关系,所以在这之后所有的onNext,onComplete,onError
方法中的isDisposed判断都不会通过,也就不会执行后续的数据发送等处理了。
RxJava中的操作符
- concatMap
- flatMap
这两个操作符的功能是一样的,都是将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据放进一个单独的Observable。区别在于concatMap
是有序的,flatMap
是无序的,concatMap最终输出的顺序与原序列保持一致,而flatMap则不一定,有可能出现交错。
举个例子,发送数字01234,通过操作符对他们进行+1处理,发送2的时候进行一个延时:
Observable.fromArray(1,2,3,4,5) .flatMap(new Function<Integer, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(@NonNull Integer integer) throws Exception { int delay = 0; if(integer == 2){ delay = 500;//延迟发射 } return Observable.just(integer*10).delay(delay, TimeUnit.MILLISECONDS); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Consumer<Integer>() { @Override public void accept(@NonNull Integer integer) throws Exception { Log.e("jimu","accept:"+integer); } });
如上述操作,最终打印结果为:10,20,40,50,30。因为发送数字2的时候,进行了延时。
但是如果flatMap
操作符改成concatMap
,打印结果就是10,20,30,40,50了,这是因为concatMap是有序的,会按照原序列的顺序进行变换输出。
- merge、concat、zip,合并
这几个操作符是用作合并发射物的,可以将多个Obserable
和并成一个Obserable
:
Observable<Integer> odds=Observable.just(1,2,3,4); Observable<Integer> events=Observable.just(5,6,7,8); Observable.merge(odds,events).subscribe(i->Log.d("TAG","merge->"+i));
区别在于concat
操作符是在合并后按顺序串行执行,merge
操作符是在合并后按时间线并行执行,如果出现某个数据进行延时发射,那么结果序列就会发生变化。
而zip
操作符的特点是合并之后并行执行,发射事件和最少的一个相同,什么意思呢?比如一个发送两个数据的Obserable
和一个发射4条数据的Obserable
进行zip合并,那么最终只会有两条数据被发射出来,看个例子:
Observable .zip(Observable.just(1,2),Observable.just(3,4,5,6),new BiFunction<Integer, Integer, Integer>() { @Override public Integer apply(@NonNull Integer response, @NonNull Integer response2) throws Exception { //将两个发射器的结果相加 return response+response2; } }) .subscribe(new Consumer<Integer>() { @Override public void accept(@NonNull Integer s) throws Exception { Log.e("lz","accept+"+s); } });
结果只会有两条数据:4,6。第二个发射器发射的后面两条数据会被抛弃。
- interval,周期执行
这个操作符主要用作定时周期任务,比如我需要每100ms发送一次数据:
Observable.interval(100, TimeUnit.MILLISECONDS) .subscribe(new Observer<Long>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Long aLong) { } });
- timer,delay延迟发送数据
这两个操作符都是用作延时发送数据,不同在于timer
是创建型操作符,而delay
是辅助型操作符。意思就是timer
操作符是可以直接创建一个Observable
,然后在订阅之后延时发送数据项,看例子:
Observable .timer(1000,TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .subscribe(disposableObserver);
而delay
是当原始的Observable
发送数据后,启动一个定时器,然后延时将这个数据发送,所以它相当于是处在上游与下游之间的一个辅助项,用作延时发送,它的作用对象必须是个创建好的Observable
:
Observable .just(0L) .doOnNext(new Consumer<Long>() { @Override public void accept(Long aLong) throws Exception { } } .timer(1000,TimeUnit.MILLISECONDS) .subscribeOn(Schedulers.io()) .subscribe(disposableObserver);
Drawable、Canvas、Bitmap
Drawable
表示的是一种可以在Canvas
上进行绘制的抽象的概念,种类很多,最常见的颜色和图片都可以是一个Drawable。
所以他是一种抽象的概念,是表示一种图像,但是又不全是图片,也可以表示颜色,一般被用作View的背景或者填充图。
到这里有的小伙伴可能又要问了,Canvas
又是什么呢?
Canvas一个矩形区域, 没有边界, 我们可以在上面调用任意drawXXX开头的方法绘制到引用的Bitmap上. 同时提供对图形的处理, 比如裁剪, 缩放, 旋转(需要Matrix对象配合使用). 所以Canvas与其说是画布, 不如说是一个绘制工具,它实际操作和存储的像素数据都在它的私有成员 Bitmap 对象中,所谓的画到Canvas画布上,其实就是画到其Bitmap,存储到其Bitmap变量中。
说到这,又要提下Bitmap
了:
Bitmap是一个存储格式, 存储了一个矩形区域内各像素点的信息. 这种格式适合显示, 但是存储效率低.可以理解为int[] buffer,用来保存每个像素的信息。
所以这三者的关系简单点就是:
Drawable
表示一个可以被绘制的图像,是一个抽象概念,需要通过Canvas
绘制到Bitmap上,作为一个图像的存储。所以Bitmap
是Drawable存在的一种实体。
Drawable分类
Drawable的种类很多,这里列举几个比较常用的,具体代码使用方式可以看往期文章这次来把Drawable翻了个遍:
- BitmapDrawablw 最常用的Drawable,表示的就是一张图片,一张带规则的图片,可以设置一些规则,比较适用于对图片有限制的情况,比如背景图。
- NinePatchDrawable 和BitmapDrawablw类似,使用时src传.9图片即可。实际上BitmapDrawablw也可以直接用.9图片,所以这个NinePatchDrawable没有太大的实际作用。
- ShapeDrawable 这个很常见,一般纯颜色的图形就用这个画出来。
- LayerDrawable
这是一种层次化的Drawable,相当于一种Drawable的布局嵌套,或者说集合,通过不同的Drawable放置到不同的层上达到一种叠加后的效果。
- StateListDrawble
一般用作点击效果,对应标签是selector,可以设置按下,点击后,不点击时候的各种状态。
- LevelListDrawable
和LayerDrawable类似,也是一个Drawable的集合,但是他是有等级的概念的,也就是可以通过设置不同的等级来展示对应的drawable,有点像一个有多种样式的Drawable,可以通过代码来展示哪一个样式。可以通过ImageView的setImageLevel方法来切换Drawable。
- TransitionDrawable
该Drawable可以实现两个Drawable之间的淡入淡出,对应的标签是transition。
- InsetDrawable
可以将其他的Drawable内嵌到自己当中,当一个View希望自己的背景比自己的实际区域小的时候,就可以采用这个,通过LayerDrawable也可以实现。一般用作加大点击区域。
- ScaleDrawable
用于缩放,并且和它的等级Level有关,等级为0则不可见。
- ClipDrawable
用作裁剪,会根据自己的等级来裁剪一个Drawable,等级0表示完全裁剪,即整个Drawable都不可见,等级10000表示不裁剪,所以主要是通过控制drawable的等级来完成裁剪功能
Window是什么?在Android中都用到了哪些地方
- 首先,它是一个窗口,是
Android
中唯一的展示视图的中介,所有的视图都是通过Window来呈现的,无论是Activity,Dialog或Toast,他们的视图都是附加到WIndow上的,所以Window是View的直接管理者。 Window
是一个抽象类,他的具体实现就是PhoneWindow。Window
的具体实现在WindowManagerService中,但是创建Window或者访问Window的操作都需要WindowManager。所以这就需要WindowManager和WindowManagerService进行交互,交互的方式就是通过IPC,具体涉及的参数就是token。- 每一个
Window
都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,所以Window并不是实际存在的,而是以View的形式存在。
涉及到Window的地方:
事件分发机制
。界面上事件分发机制的开始都是这样一个过程:DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup
。之前看过一个比较有趣的问题:事件到底是先到DecorView还是先到Window的?,其实是先到DecorView
的,具体逻辑可以自己翻下源码,有机会也可以出篇文章讲讲~各种视图的显示
。比如Activity的setContentView
,Dialog,Toast的显示视图等等都是通过Window完成的。
Window的分层和类别?
- 由于界面上有不止一个的Window,所以就有了分层的概念。每个Window都有自己对应的Window层级—z-ordered,层级大的会覆盖到层级小的上面,类似HTML中的z-index。
Window主要分为三个类别:
应用Window
。对应着一个Activity,Window层级为1-99,在视图最下层。子Window
。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。系统Window
。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000~2999,处在视图最上层。
Window的内部机制—添加、删除、更新。
- 之前说过,Window的操作都是通过
WindowManager
来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl
,并且全部交给WindowManagerGlobal
来处理。下面具体说下addView,updateViewLayout,和removeView。
1) addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { //... ViewRootImpl root; View panelParentView = null; root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; } } }
- 首先,通过add方法修改了
WindowManagerGlobal
中的一些参数,比如mViews—存储了所有Window所对应的View,mRoots——所有Window所对应的ViewRootImpl,mParams—所有Window对应的布局参数。 - 其次,
setView
方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过WindowSession进行一次IPC调用,交给到WMS来实现Window的添加。
2)updateViewLayout
public void updateViewLayout(View view, ViewGroup.LayoutParams params) { //... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; view.setLayoutParams(wparams); synchronized (mLock) { int index = findViewLocked(view, true); ViewRootImpl root = mRoots.get(index); mParams.remove(index); mParams.add(index, wparams); root.setLayoutParams(wparams, false); } }
这里更新了WindowManager.LayoutParams
和ViewRootImpl.LayoutParams
,然后在ViewRootImpl内部同样会重新对View进行绘制,最后通过IPC通信,调用到WMS的relayoutWindow完成更新。
3)removeView
public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); removeViewLocked(index, immediate); if (curView == view) { return; } throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) { InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }
该方法中,通过view找到mRoots
中的对应索引,然后同样走到ViewRootImpl
中进行View删除工作,通过die
方法,最终走到dispatchDetachedFromWindow()
方法中,主要做了以下几件事:
- 回调onDetachedFromeWindow。
- 垃圾回收相关操作;
- 通过Session的remove()在WMS中删除Window;
- 通过Choreographer移除监听器
Window中的Token是什么?
public abstract class Window { private IBinder mAppToken; }
是Window类中的一个变量,是一个Binder对象
。在Window中主要是实现WindowManagerService和应用所在的进程通信,也就是上文说到的WindowManager和WindowManagerService进行交互。是一个添加view的权限标识
。拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的。所以它存在的意义就是为了保护window的创建,也是为了防止Application或Service来做进行view或者UI相关的一些操作。
Activity,Dialog,Toast的Window创建过程
上篇文章说过Dialog的创建,先来回顾下:
1)Dialog
//构造函数 Dialog(Context context, int theme, boolean createContextThemeWrapper) { //...... //获取了WindowManager对象,mContext一般是个Activity,获取系统服务一般是通过Binder获取 mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //创建新的Window Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; //这里也是上方mWindow.getCallback()为什么是Activity的原因,在创建新Window的时候会设置callback为自己 w.setCallback(this); w.setOnWindowDismissedCallback(this); //关联WindowManager与新Window,token为null w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } //show方法 public void show() { //...... if (!mCreated) { //回调Dialog的onCreate方法 dispatchOnCreate(null); } //回调Dialog的onStart方法 onStart(); //获取当前新Window的DecorView对象 mDecor = mWindow.getDecorView(); WindowManager.LayoutParams l = mWindow.getAttributes(); try { //把一个View添加到Activity共用的windowManager里面去 mWindowManager.addView(mDecor, l); //...... } finally { } }
可以看到一个Dialog从无到有经历了以下几个步骤:
- 首先创建了一个新的Window,类型是PhoneWindow类型,与Activity创建Window过程类似,并设置
setCallback
回调。 - 将这个新Window与从Activity拿到的
WindowManager
对象相关联,也就是dialog与Activity公用了同一个WindowManager
对象。 - show方法展示Dialog,先回调了Dialog的
onCreate,onStart
方法。 - 然后获取Dialog自己的
DecorView
对象,并通过addView方法添加到WindowManager对象中,Dialog出现到屏幕上。
2)Activity
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); //... mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; mWindow.setColorMode(info.colorMode); setAutofillOptions(application.getAutofillOptions()); setContentCaptureOptions(application.getContentCaptureOptions()); } public void setContentView(@LayoutRes int layoutResID) { // 交给Window getWindow().setContentView(layoutResID); // 创建ActionBar initWindowDecorActionBar(); }
关于Activity的启动流程,相比大伙都知道些,流程最后会走到ActivityThread
中的performLauchActivity
方法,然后会创建Activity的实例对象,并调用attach方法,也就是上述贴的源码。
在这个方法中,创建了新的Window对象,设置回调接口。这个回调接口主要就是用作Window在接收到外界状态改变的时候,就会回调给这个callback,比如onAttachedToWindow、dispatchTouchEvent
方法等,这个上篇文章也有说过,事件分发的时候就是通过在DecorView中这个callback进行分发的。
然后view怎么显示到界面上的呢,Activity可没有show方法哦?其实就是通过setContentView方法。该方法主要做了以下几件事:
- 创建
DecorView
,如果不存在的话。 - 然后将xml中解析到的view添加到DecorView的
mContentParent
中,也就是布局为android.R.id.content的ContentView。 - 回调
onContentChanged
方法,通知Activity视图已经发生改变。
贴张图:
到这里,一个有完整view结构的DecorView
就创建出来了,但是它还没有被显示到手机界面上,也就是没有被添加到Window中。最后要调用了WMS的addView
方法才会被用户真正看到:
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
3)Toast
public void show() { if (mNextView == null) { throw new RuntimeException("setView must have been called"); } INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; final int displayId = mContext.getDisplayId(); try { service.enqueueToast(pkg, tn, mDuration, displayId); } catch (RemoteException e) { // Empty } } public void cancel() { mTN.cancel(); } //class TN public void handleShow() { // ...... mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mWM.addView(mView, mParams); } public void handleHide() { if (mView != null) { if (mView.getParent() != null) { mWM.removeView(mView); } mView = null; } }
Toast有点不同的在于,它内部维护了两个IPC通信,一个是NotificationManagerService
,一个是回调TN
接口。最终的实现都是走到TN.class的handleShow
和handleHide
方法,也就是addView和removeView。
Android中创建多进程的方式
1) 第一种,大家熟知的,就是给四大组件再AndroidManifest
中指定android:process
属性。
<activity android:name="com.example.uithread.UIActivity" android:process=":test"/> <activity android:name="com.example.uithread.UIActivity2" android:process="com.example.test"/>
可以看到,android:process
有两种表达方式:
:test
。“:”的含义是指要在当前的进程名前面加上当前的包名,如果当前包名为com.example.jimu。那么这个进程名就应该是com.example.jimu:test。这种冒号开头的进程属于当前应用的私有进程,其他应用的组件不可以和他跑到同一个进程中。com.example.test
。第二种表达方式,是完整的命名方式,它就是新进程的进程名,这种属于全局进程,其他应用可以通过shareUID的方式跑到同一个进程中。
简单说下shareUID
:正常来说,Android中每个app都是一个单独的进程,与之对应的是一个唯一的linux user ID,所以就能保住该应用程序的文件或者组件只对该应用程序可见。但是也有一个办法能让不同的apk进行共享文件,那就是通过shareUID,它可以使不同的apk使用相同的 user ID。贴下用法:
//app1 <manifest package="com.test.app1" android:sharedUserId="com.test.jimu" > //app2 <manifest package="com.test.app2" android:sharedUserId="com.test.jimu" > //app1中获取app2的上下文: Context mContext=this.createPackageContext("com.test.app2", Context.CONTEXT_IGNORE_SECURITY);
2)第二种创建进程的方法,就是通过JNI在native
层中去fork一个进程。
这种就比较复杂了,我在网上找了一些资料,找到一个fork普通进程的:
//主要代码 long add(long x,long y) { //fpid表示fork函数返回的值 pid_t fpid; int count=0; fpid=fork(); } //结果: USER PID PPID VSZ RSS STAT NAME root 152 1 S zygote u0_a66 17247 152 297120 44096 S com.example.jni u0_a66 17520 17247 0 0 Z com.example.jni
最终的结果是可以创建出一个进程,但是没有运行,占用的内存为0,处于僵尸程序状态。
但是它这个是通过普通进程fork
出来的,我们知道Android中所有的进程都是直接通过zygote进程fork出来的(fork可以理解为孵化出来的当前进程的一个副本)。所以不知道直接去操作zygote进程可不可以成功,有了解的小伙伴可以在微信讨论群里给大家说说。
对了,有的小伙伴可能会问,为什么所有进程都必须用zygote
进程fork呢?
- 这是因为
fork
的行为是复制整个用户的空间数据以及所有的系统对象,并且只复制当前所在的线程到新的进程中。也就是说,父进程中的其他进程
在子进程中都消失了,为了防止出现各种问题(比如死锁,状态不一致)呢,就只让zygote
进程,这个单线程的进程,来fork新进程。 - 而且在
zygote
进程中会做好一些初始化工作,比如启动虚拟机,加载系统资源。这样子进程fork的时候也就能直接共享,提高效率,这也是这种机制的优点。
一个应用使用多进程会有什么问题吗?
上面说到创建进程的方法很简单,写个android:process属性即可,那么使用是不是也这么简单呢?很显然不是,一个应用中多进程会导致各种各样的问题,主要有如下几个:
静态成员和单例模式完全失效
。因为每个进程都会分配到一个独立的虚拟机,而不同的虚拟机在内存分配上有不同的地址空间,所以在不同的进程,也就是不同的虚拟机中访问同一个类的对象会产生多个副本。线程同步机制完全失效
。同上面一样,不同的内存是无法保证线程同步的,因为线程锁的对象都不一样了。SharedPreferences不在可靠
。之前有一篇说SharedPreferences的文章中说过这一点,SharedPreferences是不支持多进程的。Application会多次创建
。多进程其实就对应了多应用,所以新进程创建的过程其实就是启动了一个新的应用,自然也会创建新的Application,Application和虚拟机和一个进程中的组件是一一对应的。
Android中的IPC方式
既然多进程有很多问题,自然也就有解决的办法,虽然不能共享内存,但是可以进行数据交互啊,也就是可以进行多进程间通信,简称IPC。
下面就具体说说Android中的八大IPC方式:
- Bundle Android四大组件都是支持在Intent中使用
Bundle
来传递数据,所以四大组件直接的进程间通信就可以使用Bundle。但是Bundle有个大小限制要注意下,bundle
的数据传递限制大小为1M,如果你的数据超过这个大小就要使用其他的通信方式了。 - 文件共享 这种方式就是多个进程通过
读写一个文件
来交换数据,完成进程间通信。但是这种方式有个很大的弊端就是多线程读写容易出问题,也就是并发问题
,如果出现并发读或者并发写都容易出问题,所以这个方法适合对数据同步要求不高的进程直接进行通信。
这里可能有人就奇怪了,SharedPreference
不就是读写xml文件吗?怎么就不支持进程间通信了?
- 这是因为系统对于
SharedPreference
有读写缓存策略,也就是在内存中有一份SharedPreference文件的缓存,涉及到内存了,那肯定在多进程中就不那么可靠了。 - Messenger
Messenger
是用来传递Message对象的,在Message中可以放入我们要传递的数据。它是一种轻量级的IPC方案,底层实现是AIDL。 - AIDL
Messenger虽然可以发送消息和接收消息,但是无法同时处理大量消息,并且无法跨进程方法。但是AIDL则可以做到,这里简单说下AIDL
的使用流程:
服务端首先建立一个Service监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中申明,最后在Service中实现这个AIDL
接口。客户端需要绑定这个服务端的Service,然后将服务端返回的Binder
对象转换成AIDL接口的属性,然后就可以调用AIDL中的方法了。
- ContentProvider
这个大家应很熟悉了,四大组件之一,专门用于不同应用间进行数据共享的。它的底层实现是通过Binder实现的。
- Socket
套接字,在网络通信中用的很多,比如TCP,UDP。关于Socket通信,借用网络上的一张图说明:
- Binder连接池
关于Binder的介绍,之前的文章已经说过了。这里主要讲一个Binder的实际使用的技术——Binder连接池
。由于每个AIDL请求都要开启一个服务,防止太多服务被创建,就引用了Binder连接池
技术。Binder连接池的主要作用就是将每个业务模块的Binder
请求统一 转发到远程Service中去执行,从而避免了重复创建Service
的过程。贴一下Binder连接池的工作原理:
- 每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对应的Binder对象.
- 对于服务端来说,只需要一个 Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来 返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。
- BroadcastReceiver
广播,不用多说了吧~ 像我们可以监听系统的开机广播,网络变动广播等等,都是体现了进程间通信的作用。
Android在版本迭代中,总会进行很多改动,那么你熟知各版本都改动了什么内容?又要怎么适配呢?
这个内容太多,我就不贴了哈,具体可以往期文章 Android版本迭代信息
冷启动、温启动、热启动
首先了解下启动的这三个概念,也是面试常被问到的:
冷启动
。冷启动指的是该应用程序在此之前没有被创建,发生在应用程序首次启动或者自上次被终止后的再次启动。简单的说就是app进程还没有,需要创建app的进程启动app。
比如开机后,点击屏幕的app图标启动应用。
冷启动的过程主要分为两步:
1)系统任务
。加载并启动应用程序;显示应用程序的空白启动窗口;创建APP进程 2)APP进程任务
。启动主线程;创建Activity;加载布局;屏幕布局;绘制屏幕
其实这不就是APP的启动流程嘛?所以冷启动是会完整走完一个启动流程的,从系统到进程。
温启动
。温启动指的是App进程存在,但Activity可能因为内存不足被回收,这时候启动App不需要重新创建进程,只需要执行APP进程中的一些任务,比如创建Activity。
比如返回主页后,又继续使用其他的APP,时间久了或者打开的应用多了,之前应用的Activity有可能被回收了,但是进程还在。
所以温启动相当于执行了冷启动的第二过程,也就是APP进程任务,需要重新启动线程,Activity等。
热启动
。热启动就是App进程存在,并且Activity对象仍然存在内存中没有被回收。
比如app被切到后台,再次启动app的过程。
所以热启动的开销最少,这个过程只会把Activity从后台展示到前台,无需初始化,布局绘制等工作。
启动优化我们可以介入的优化点
所以三种启动方式中,冷启动经历的时间最长,也是走完了最完整的启动流程,所以我们再次分析下冷启动的启动流程,看看有哪些可以优化的点:
- Launcher startActivity
- AMS startActivity
- Zygote fork 进程
- ActivityThread main()
- ActivityThread attach
- handleBindApplication
- attachBaseContext
- Application attach
- installContentProviders
- Application onCreate
- Looper.loop
- Activity onCreate,onResume
纵观整个流程,其实我们能动的地方不多,无非就是Application
的attach,onCreate方法,Activity
的onCreate,onResume方法,这些方法也就是我们的优化点。
启动优化方案总结
最后再和大家回顾下今天说到的启动优化方案:
- 消除启动时的白屏/黑屏。windowBackground。
- 第三方库懒加载/异步加载。线程池,启动器。
- 预创建Activity。对象预创建。
- 预加载数据。
- Multidex预加载优化。5.0以下多dex情况。
- Webview启动优化。预创建,缓存池,静态资源。
- 避免布局嵌套。多层嵌套。
为了方便记忆,我再整理成以下三类,分别是Application、Activity、UI
:
- Application 三方库,Multidex。
- Activity 预创建类,预加载数据。
- UI方面 windowBackground,布局嵌套,webview。
具体说明可以看往期文章 Android启动优化全解析