Service有两种启动方式,分别是startService和binderService。
Service默认运行在UI线程中,所以里面不能做耗时操作,要做耗时操作就必须开启一个子线程操作。
IntenService其实是继承自Service,只不过内部默认开启了一个子线程处理所有的Intent请求。
而多次调用startService只会执行onStartCommand方法,但是启动线程的动作友只在onCreate中,所以并不会再次启动新的线程,每次执行onStartService时会通过Message的sendMessage发送一条消息到消息队列中。也就是说通过唯一的一个子线程来处理各个请求,并按照Looper的队列顺序一个接一个执行。
我们都知道,我们自定义View的时候需要实现三个方法,分别是:onMeasure(测量)、onLayout(布局)、onDraw(绘制)。
此时我们会想到一个问题:View到底是如何测量的,如何布局的,最终是如何绘制到界面上的?
我们需要从底层知道自定义View的时候是如何触发这三个函数的以及是如何管理的,由谁来管理的。
我们知道,我们所有的界面显示都是在Activity上进行的,每个Activity都对应一个窗口Window,这个窗口在Android里就是PhoneWindow
当我们调整imageview的大小时,Picasso会不管imageview大小是什么,总是直接缓存整张图片,而Glide就不一样了,它会为每个不同尺寸的Imageview缓存一张图片,也就是说不管你的这张图片有没有加载过,只要imageview的尺寸不一样,那么Glide就会重新加载一次,这时候,它会在加载的imageview之前从网络上重新下载,然后再缓存。
举个例子,如果一个页面的imageview是300*300像素,而另一个页面中的imageview是100*100像素,这时候想要让两个imageview像是同一张图片,那么Glide需要下载两次图片,并且缓存两张图片。
因为Glide在加载资源的时候,如果是在Activity、Fragment这一类有生命周期的组件上进行的话,会创建一个透明的RequestManagerFragment加入到FragmentManager之中,感知生命周期,当Activity、Fragment等组件进入不可见,或者已经销毁的时候,Glide会停止加载资源。
但是如果,是在非生命周期的组件上进行时,会采用Application的生命周期贯穿整个应用,所以applicationManager只有在应用程序关闭的时候终止加载。
主要是通过RequestManager类和Lifecycle接口来实现的。
通过这种机制,Glide能够感知到关联组件的生命周期变化,并根据生命周期的状态来启动、暂停或取消对应的图片加载请求,从而确保了图片加载操作与组件生命周期的同步,避免了潜在的内存泄漏和资源浪费问题。
Glide感知生命周期的主要目的是确保在图片加载过程中,及时地根据组件的生命周期状态进行加载操作的开始和取消,以提高应用的性能和用户体验,具体目的包括:
综上所述,Glide感知生命周期的目的是为了提高应用的性能和用户体验,确保在图片加载过程中,能够及时地根据组件的生命周期状态进行加载操作的开始和取消,从而避免不必要的资源消耗和性能问题。
Handler很重要,它和Binder进程间通信可以说是组成Android最重要的两部分。为什么说Hanlder很重要,我觉着主要有以下三个方面:
其中MessageQueue被Looper管理,Looper在构造时同步会创建MessageQueue,并利用ThreadLocal这种TLS,将其与当前线程绑定。而App的主线程在启动时,已经构造并准备好主线程的Looper对象,开发者只需要直接使用即可。
IdleHandler说白了,就是Handler机制提供的一种,可以在Looper事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制。
IdleHandler被定义在MessageQueue中,它是一个接口。
//MessageQueue.javapublicstaticinterfaceIdleHandler{booleanqueueIdle();}既然IdleHandler主要是在MessageQueue出现空闲的时候被执行,那么何时出现空闲?
Q:IdleHandler有什么用?
Q:MessageQueue提供了add/removeIdleHandler的方法,是否需要成对使用?
Q:当mIdleHanders一直不为空时,为什么不会进入死循环?
Q:是否可以将一些不重要的启动服务,搬移到IdleHandler中去处理?
Q:IdleHandler的queueIdle()运行在那个线程?
Retrofit并不是网络请求框架,严格说只是对网络请求的一种封装,我们只需要定义一个接口类,在请求方法上加上相应的注解,甚至都不需要实现,就可以实现网络请求
Retrofit最重要的就是内部的动态代理模式
静态代理:Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,但是静态代理代码冗余大,一旦需要修改接口,代理类和委托类都需要修改。这也是静态代理的缺陷。
举个例子:假如我有一套房子需要出售,我(真实对象)需要将房子委托给中介公司(代理对象),中介来帮助我出售房子(抽象对象)
动态代理:-JDK自带的java.lang.reflect.Proxy,只能代理接口类我们的类一般由Java源文件编译出Java字节码.class文件,然后经过类加载器ClassLoader加载使用。
我们知道Rxjava是一种链式调用的方式实现我们的一次网络请求
Observable.create(...)//在io调度器上执行.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(...)//在Android主线程上执行Rxjava通过subscribeOn实现切换到工作线程,创建一个ObservableObserveOn对象。会执行subscribeActual方法
如果在使用Handler时,未正确处理消息队列和对外部类弱引用,可能导致外部类无法被回收。
在Android开发中,Context引用是非常常见的内存泄漏原因。当将一个长生命周期的对象与Context关联时,如果未正确解除引用,将导致Context无法被回收。
关于LeakCanary的原理,官网上已经给出了详细的解释。翻译过来就是:
ANR:全称:applicationnoresponse(应用程序无响应),Android设计ANR的用意主要是系统通过与之交互的组件,比如Activity,Service以及用户的操作(触摸、滑动、点击)对它们进行监控,用于判断我们的应用进程是不是存在卡死或者是响应过慢的一个状态,这其实是很多应用当中一种WatchDog(看门狗)的一种设计,当我们的应用程序发生了ANR的时候,我们的应用程序会为我们采集内存、CPU等一些信息,同时会在我们的/Data/ANR目录下生成对应的Trace文件。而我们要去分析ANR的原因以及解决问题,那我们就需要结合这个Trace文件,分析Trace文件来分析原因。
我们在开发过程中出现了各种各样的问题,我们可以通过异常信息或者日志快速区跟踪问题发生的原因,但是当我们的应用上线后,这些问题就很难监控了,那我们的应用程序线上发生ANR时,我们要如何监控呢?我们可以监听我们Trace文件的改变(Android6.0之前),6.0以后我们业界有两种方式监听ANR。
第二种就是通过信号机制来监听的,信号机制类似于Android的广播,当我们在ANR发生的时候,我们的操作系统会给我们发生一个信号。我们Android系统在接到底层发出这个信号时,就会判断发生了ANR,去采集ANR的信息,那我们也可以通过这种方式来监控,爱奇艺的性能监控工具(xCrash)就是通过这种方式实现的。这块主要是通过C/C++的代码,去进行信号的注册,当我们的ANR的信号发生时,就会执行我们注册的函数,用来监控。
是指从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数个MOVE事件。
将点击事件(MotionEvent)传递到某一个具体的View处理的全过程。
即事件传递的过程=事件分发过程
Activity、ViewGroup、View。Android的UI界面由Activity、ViewGroup、View及其派生类组成
即:1个点击事件发生后,事件先传到`Activity`、再传到`ViewGroup`、最终到`View`4.事件分发过程由哪些方法协作完成?
由三种方式协作完成:dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent()
Android事件分发流程=Activity->ViewGroup->View
即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终到View
即要想充分理解Android分发机制,本质上是要理解:
流程1。Activity对点击事件的分发机制;当一个点击事件发生时,从Activity的事件分发开始(Activity.dispatchTouchEvent()),流程总结如下:
核心方法:dispatchTouchEvent和onTouchEvent总结如下:
流程2。ViewGroup的事件分发机制
从上面Activity的事件分发机制可知,在Activity.dipatchTouchEvent()实现了将事件从Activity->ViewGroup的传递,ViewGroup的事件分发机制从dispatchTouchEvent()开始。
核心方法:dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent()总结如下:
流程3。View的事件分发机制
从上面ViewGroup事件分发机制知道,View的事件分发机制从dispatchTouchEvent()开始。流程总结如下:
这里需要特别注意的是,onTouch()的执行先于onClick()
核心方法:dispatchTouchEvent()、onTouchEvent()总结如下:
Android事件分发工作流程-总结
Android事件分发流程=Activity->ViewGroup->View,即:1个点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到View。
内存过小解决办法:
内存泄漏如何分析?
在Android开发中,内存泄漏是一个常见问题,可能会导致应用程序性能下降,甚至在严重情况下导致应用程序崩溃。以下是一些解决Android内存泄漏问题的步骤:
LeakCanary是一个用于检测Android和Java应用程序内存泄漏的工具。它是由Square公司开发的,是一个开源库,可以帮助开发者在开发测试阶段更容易地发现内存泄漏的情况。
LeakCanary的工作原理是在不影响程序正常运行的情况下,动态收集程序存在的内存泄漏问题。它会在Activity的onDestroy方法执行时,对Activity创建一个带ReferenceQueue的弱引用,并检查这个引用是否被清除。如果没有被清除,就认为存在内存泄漏,然后通过另一个进程分析内存泄漏的信息并展示出来。
使用LeakCanary可以极大地方便Android应用程序的开发,因为开发者不需要每次在开发流程中都抽出专人来进行内存泄漏问题检测。将LeakCanary集成到自己的程序中也非常简单,只需要引入LeakCanary提供的jar包即可。一旦检测到内存泄漏,LeakCanary就会dump内存信息,并通过另一个进程分析内存泄漏的信息并展示出来。
Android开发中卡顿问题一直是个比较棘手又重要的问题,严重影响用户体验;卡顿是人的一种视觉感受,比如我们滑动界面时,如果滑动不流畅我们就会有卡顿的感觉,这种感觉我们需要有一个量化指标,在编程时如果开发的程序超过了这个指标我们认为其是卡顿的
过度绘制会导致界面消耗性能,严重还会出现卡顿
我们在写RecyclerView的时候,如果RecyclerView的父布局、RecyclerView、item三者的背景只要其中一个设置就可以了,没有设置背景就不会渲染,否则就会有过度绘制的情况
●父布局套子布局也是尽量只设置其中一个背景,除非没办法都需要背景
●子view一般绘制后是会覆盖父view,所以一般选择把背景设置在子view
●视图的层级结构能减少就减少,层级越多绘制速度越慢
●尽量少设置view的透明度,如果一个view设置了alpha,那他需要知道下面的view是什么内容,再绘制自己,就是过度绘制。如果是文字有透明度,可以在色号里就设置好
移除没用的布局和控件,假设添加个背景,尽可能在已经布局上放,减少只有背景功能的控件
●减少透明度的使用,假设:#55FFFFFF和#888888颜色类似,建议使用后者,因为前者有Alpha,view需要至少绘制两次
●去掉多余的不可见颜色背景、图片等,只保留最上层用户可见即可
●减少布局层次结构,避免多层嵌套约束布局ConstraintLayout等父类布局
●基本控件LinearLayout性能比RelativeLayout高一些,要提前根据UI想好哪个布局更合适,要有的方式,对症下药
●自定义View尽可能只更新渲染局部区域,杜绝不断全部重绘
●推荐使用IDE自带的Lint或者阿里代码检查插件,对于标黄警告等提示重视起来,能改的就改
●使用AndroidStudio自带的LayoutInspector层级检测工具,可以立体的查看界面的层级布局。
MVP的特点
MVP的优点
MVP的缺点
上面例子只是由于简单的主线程耗时操作造成的ANR,造成ANR的原因还有很多:
解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程queryprovider、不要滥用SharePreferences
解决办法:文件读写或数据库操作放在子线程异步操作。
解决办法:AndroidManifest.xml文件中可以设置android:largeHeap="true",以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。
从桌面点击一个图标后,到界面显示,这个过程发生了什么?
①点击桌面App图标,Launcher进程采用BinderIPC向system_server进程发起startActivity请求;
②system_server进程接收到请求后,向zygote进程发送创建进程的请求;
③Zygote进程fork出新的子进程,即App进程;
④App进程,通过BinderIPC向sytem_server进程发起attachApplication请求;
⑤system_server进程在收到请求后,进行一系列准备工作后,再通过binderIPC向App进程发送scheduleLaunchActivity请求;
⑥App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
⑦主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。
⑧到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
Android应用启动方式主要有两种,冷启动和热启动。
Activity的启动模式,也就是常见的四种启动模式
1.standard
android.util.AndroidRuntimeException:CallingstartActivity``from``outsideofanActivitycontextrequirestheFLAG_ACTIVITY_NEW_TASKflag.Is``this``reallywhatyouwant上面错误的原因是被启动的Activity是标准模式,而发起启动请求的Context不是Activity的Context,而是系统的Context,所以系统的Context需要设置FLAG_ACTIVITY_NEW_TASK属性,来启动一个新的Activity栈。
2.singleTop
栈顶复用模式:在这种模式下,如果新启动的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,只会重新调用onNewIntent方法,这个Activity的onCreate、onStart都不会被系统调用。如果新Activity实例已经存在但不在栈顶,那么重新创建Activity并放入栈顶。
3.singleTask
栈内复用模式:这是一种单实例模式,一个栈中同一个Activity只存在唯一一个实例,无论是否在栈顶,只要存在实例,都不会重新创建,和singleTop一样会重新调用onNewIntent方法。需要注意的是:如果一个Activity被设置为singleTask模式,那么当栈内已经存在该Activity实例时,再启动该Activity,会让该Activity实例之上的Activity被出栈。举个例子:有四个Activity分别是A、B、C和D,A是singleTask模式,当先执行A->B->C->D时,A在栈内已存在实例,此时再调用D->A启动A时,会让A实例之上的B、C、D都出栈。一般项目的MainActivity都设置为此模式,方便放回首页和清空中间Activity。
4.singleInstance
单实例模式:这是一种加强的singleTask模式,它除了具有singleTask模式的所有特性外,还加强了一点,那就是此种模式的Activity只能单独地位于一个任务栈中,不同的应用去打开这个activity共享公用的同一个activity。他会运行在自己单独、独立的任务栈里面,并且任务栈里面只有他一个实例存在。应用场景:呼叫来电界面。这种模式的使用情况比较罕见,在Launcher中可能使用。或者你确定你需要使Activity只有一个实例。
当屏幕旋转或切换系统语言时,Activity生命周期会经历销毁再重建,但是ViewModel里面的变量值时不受影响的。说明ViewModel中的数据在此期间进行了存储,在之后又进行了恢复。
我们一般在Activity的onCreate中获取ViewModel的实例
valviewModel=ViewModelProvider(this).get(MainViewModel::class.java)在ViewModelProvider中重点会调用ensureViewModelStore,如下图:
ViewModelProvider会调用ensureViewModelStore(),该方法中有两个很重要的类:ViewModelStore和NonConfigurationInstances
ViewModelStore是用来存储ViewModel对象的,记得内部应该是一个HashMap,用于缓存ViewModel的实例对象。
NonConfigurationInstances实际是一个JavaBean类,内部存储了viewmodelStore。
具体的执行流程在.get(MainViewModel::class.java)中,根据Key从ViewModelStore获取缓存的ViewMode,如果存在,就返回ViewModel实例,如果不存在当前的Class实例,则用工厂方法创建一个,再将新创建的加入缓存。
屏幕旋转前后,mViewModelStore在屏幕旋转前后都是同一个对象,会在NonConfigurationInstances.onRetainNonConfigurationInstance()中保存数据。
屏幕旋转后,Activity重建后从getLastNonConfigurationInstance()中获取到了屏幕旋转前保存的NonConfigurationInstances实例对象,然后从nc对象中获取存储的mViewModelStore对象。