赞
踩
Android应用模型是基于组件的应用设计模式,组件的运行要有一个完整的Android工程环境。在这个工程环境下,Activity、Service等系统组件才能够正常工作,而这些组件并不能采用普通的Java对象创建方式,new一下就能创建实例了,而是要有它们各自的上下文环境,也就是Context,Context是维持Android程序中各组件能够正常工作的一个核心功能类。
如何生动形象的理解Context?
一个Android程序可以理解为一部电影,Activity、Service、BroadcastReceiver和ContentProvider这四大组件就好比戏了的四个主角,它们是剧组(系统)一开始定好的,主角并不是大街上随便拉个人(new 一个对象)都能演的。有了演员当然也得有摄像机拍摄啊,它们必须通过镜头(Context)才能将戏传给观众,这也就正对应说四大组件必须工作在Context环境下。那么Button、TextView等等控件就相当于群演,显然没那么重用,随便一个路人甲都能演(可以new一个对象),但是它们也必须在面对镜头(工作在Context环境下),所以Button mButtom = new Button(context) 是可以的。
源码中的Context
public abstract class Context {
}
它是一个纯抽象类,那就看看它的实现类。
它有两个具体实现类:ContextImpl和ContextWrapper。
其中ContextWrapper类,是一个包装类而已,ContextWrapper构造函数中必须包含一个真正的Context引用,同时ContextWrapper中提供了attachBaseContext()用于给ContextWrapper对象指定真正的Context对象,调用ContextWrapper的方法都会被转向其包含的真正的Context对象。ContextThemeWrapper类,其内部包含了与主题Theme相关的接口,这里所说的主题就是指在AndroidManifest,xml中通过android:theme为Application元素或者Activity元素指定的主题。当然,只有Activity才需要主题,Service是不需要主题的,所以Service直接继承与ContextWrapper,Application同理。而ContextImpl类则真正实现了Context中的所有函数,应用程序中所调用的各种Context类的方法,其实现均来源于该类。Context得两个子类分工明确,其中ContextImpl是Context的具体实现类,ContextWrapper是Context的包装类。Activity、Application、Service虽都继承自ContextWrapper(Activity继承自ContextWrapper的子类ContextThemeWrapper),但它们初始化的过程中都会创建ContextImpl对象,由ContextImpl实现Context中的方法。
一个应用程序有几个Context?
在应用程序中Context的具体实现子类就是:Activity、Service和Application。那么Context数量=Activity数量+Service数量+1。那么为什么四大组件中只有Activity和Service持有Context呢?BroadcastReceiver和ContextPrivider并不是Context的子类,它们所持有的Context都是其他地方传过去的,所以并不计入Context总数。
Context能干什么?
Context能实现的功能太多了,弹出Toast、启动Activity、启动Service、发送广播、启动数据库等等都要用到Context。
TextView tv = new TextView(getContext());
ListAdapter adapter = new SimpleCursorAdapter(getApplicationContext(), ...);
AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);getApplicationContext().getSharedPreferences(name, mode);
getApplicationContext().getContentResolver().query(uri, ...);
getContext().getResources().getDisplayMetrics().widthPixels * 5 / 8;
getContext().startActivity(intent);
getContext().startService(intent);
getContext().sendBroadcast(intent);
Context的作用域
虽然Context神通广大,但并不是随便拿到一个Context实例就可以为所欲为,它的使用还是有一些规则限制的。由于Context的具体实例是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会报错。
从上图我们可以发现Activity所持有的Context的作用域最广,无所不能,因此Activity继承至ContextThemeWrapper,而Application和Service继承至ContextWrapper,很显然ContextThemeWrapper在ContextWrapper的基础上又做了一些操作使得Activity变得更强大。着重讲一下不推荐使用的两种情况:
如果我们用ApplicationContext去启动一个LaunchMode为standard的Activity的时候会报错:
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
这是因为非Activity类型的Context并没有所谓的任务栈,所以待启动的Activity就找不到栈了。解决这个问题的方法就是为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就为它创建一个新的任务栈,而此时Activity是以singleTask模式启动的。所有这种用Application启动Activity的方式都不推荐,Service同Application。
在Application和Service中去LayoutInflate也是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用,这种方式也不推荐使用。
一句话总结:凡是跟UI相关的,都应该使用Activity作为Context来处理;其他的一些操作,Service、Activity、Application等实例都可以,当然了注意Context引用的持有,防止内存泄露。
如何获取Context?
有四种方法:
getApplication()和getApplicationContext()的区别?
其内存地址是一样的。Application本身就是一个Context,这里获取getApplicationContext得到的结果就是Application本身的实例。getApplication方法的语义性很强,就是用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application,但是如果在一些其他的场景,比如BroadcastReceiver中也想获取Application实例,这时就可以借助getApplicationContext方法了。
public class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Contextcontext,Intentintent){
Application myApp= (Application)context.getApplicationContext();
}
}
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销。当系统中存在大量的线程时,系统会通过时间片轮转的方式调度每个线程,因此线程不可能做到绝对的并行。
如果在一个进程中频繁的创建和销毁线程,显然不是高效地做法。正确的做法是采用线程池,一个线程池会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。
AsyncTask是一个抽象类,它是由Android封装的一个轻量级异步类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。
AsyncTask的内部封装了两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handle(InternalHandler)。
其中SerialExecutor线程池用于任务的排队,让需要执行的多个耗时任务,按顺序排列,THREAD_POLL_EXECUTOR线程池才真正的执行任务,InternalHandler用于从工作线程切换到主线程。
AsyncTask的类声明如下:
public abstract class AsyncTask<Params,Progress,Result>
AsyncTask是一个抽象泛型类。
Params:开始异步任务时传入的参数类型
Progress:异步任务执行过程中,返回下载进度值的类型
Result:异步任务执行完成后,返回的结果类型
如果AsyncTask确定不需要传递具体参数,那么这三个泛型参数可以用Void来代替。
onPreExecute()
这个方法会在后台任务开始执行之前调用,在主线程执行。用于进行一些界面上的初始化操作,比如显示一个进度条对话框等等。
doInBackground(Params...)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。
任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress ...)方法来完成。
onProgressUpdate(Progress...)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,在主线程中进行,利用参数中的数值就可以对界面元素进行相应的更新。
onPostExecute(Result)
当doInBackground(Params...)执行完毕并通过return语句进行返回时,这个方法就很快被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,在主线程中进行,比如说提醒任务执行的结果,以及关闭掉进度条对话框等等。
上面几个方法的调用顺序为:onPreExecute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExecute()
如果不需要执行更新进度则为:onPreExecute() --> doInBackground() --> onPostExecute()
除了上面四个方法,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled会被调用,这个时候onPostExecute()则不会调用,但是,AsyncTask的cancel()方法并不是真正的去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。
Dalvik是Google公司自己设计用于Android平台的Java虚拟机,它是Android平台的重要组成部分,支持dex格式的Java应用程序的运行。dex格式是专门为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Google对其进行了特定的优化,是的Dalvik具有高效、简洁、节省资源的特点。从Android系统架构图知,Dalvik虚拟机运行在Android的运行时库层。
Dalvik作为面向Linux、为嵌入式操作系统设计的虚拟机,主要负责完成对象生命周期、堆栈管理、线程管理、安全和异常管理,以及垃圾回收等。另外,Dalvik早期并没有JIT编译器,知道Android2.2才加入了对JIT的技术支持。
体积小,占用内存空间小。
专有的DEX可执行文件格式,体积更小,执行速度更快。
常量池采用32位索引值,寻址类方法名、字段名,常量更快。。
基于寄存器架构,并拥有一套完整的指令系统。
提供了对象生命周期管理,堆栈管理,线程管理,安全和异常管理以及垃圾回收等重要功能。
所有的Android程序都运行在Android系统进程里,每个进程对应着一个Dalvik虚拟机实例。
Dalvik虚拟机与传统的Java虚拟机有着许多不同点,两者并不兼容,它们显著的不同点主要表现在以下几个方面:
Java虚拟机运行的是Java字节码,Dalvik虚拟机运行的是Dalvik字节码。
传统的Java程序经过编译,生成Java字节码保存在class文件中,Java虚拟机通过解码class文件中的内容来运行程序。而Dalvik虚拟机运行的是Dalvik字节码,所有的Dalvik字节码由Java字节码转换而来,并被打包到一个DEX可执行文件中。Dalvik虚拟机通过解码DEX文件来执行这些字节码。
Dalvik可执行文件体积小,Android SDK中有一个叫dx的工具负责将Java字节码转换为Dalvik字节码。
消除其中的冗余信息,重新组合形成一个常量池,所有的类文件共享同一个常量池。由于dx工具对常量池的压缩,使得相同的字符串常量在DEX文件中只出现一次,从而减小了文件的体积。
简单来讲,dex格式文件就是将多个class文件中公有的部分统一存放,去除冗余信息。
Java虚拟机与Dalvik虚拟机架构不同,这也是Dalvik与JVM之间最大的区别。
**Java虚拟机基于栈架构。**程序在运行时虚拟机需要频繁的从栈上读取或写入数据,这个过程需要更多的指令分配与内存访问次数,会耗费不少CPU时间,对于像手机设备资源有限来说,这是相当大的一笔开销。
Dalvik虚拟机基于寄存器架构。
数据的访问通过寄存器间直接传递,这样的访问方式比基于栈方式要快很多。
一个应用首先经过DX工具将class文件转换成Dalvik虚拟机可以执行的dex文件,然后由类加载器加载原生类和Java类,接着由解释器根据指令集对Dalvik字节码进行解释、执行。最后,根据dvm_arch参数选择编译的目标机体系结构。
总结:编译 --> DEX --> 打包 --> 签名和对齐
ART代表Android Runtime,其处理应用程序执行的方式完全不同于Dalvik,Dalvik是依靠一个Just-In-Time(JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运行。ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time(AOT)编译。在移除解释代码这一过程后,应用程序执行将更加效率。启动更快。
在Dalvik中,如同其他大多数JVM一样,都采用的是JIT来做及时翻译(动态翻译),将dex或odex中并排的Dalvik code(或者叫smali指令集)运行态翻译成native code去执行。JIT的引入使得Dalvik提升了3-6倍的性能。
而在ART中,完全抛弃了Dalvik的JIT,使用了AOT直接在安装时将其完全翻译成native code。这一技术的引入,使得虚拟机执行指令的速度又一重大提升。
首先介绍下Dalvik的GC过程,主要有四个过程:
Dalvik这么做的好处是,当pause了之后,GC势必是相当快速的,但是如果出现GC频繁并且内存吃紧势必会导致UI卡顿、掉帧、操作不流畅等等。
后来ART改善了这种GC方式,主要的改善点在将其非并发过程改成了部分并发,还有就是对内存的重新分配管理。
当ART GC发生时:
可以看出整个过程做到了部分并发使得时间缩短,GC效率提高两倍。
Dalvik内存管理特点是:内存碎片化严重,当然这也是标记清除算法带来的弊端。
ART的解决: 在ART中,它将Java分了一块空间命名为 Large-Object-Space,这个内存空间的引入用来专文存放大对象,同时ART又引入了 moving collector 的技术,即将不连续的物理内存快进行对齐。对齐之后内存碎片化就得到了很好的解决。Large-Object-Space的引入是因为moving collector对大块内存的位移时间成本太高。据官方统计,ART的内存利用率提高了10倍左右,大大提高了内存的利用率。
前台进程一般有以下特点:
利用Activity提升权限
监控手机锁屏解锁事件,在屏幕锁屏时启动1像素的Activity,在用户解锁时将Activity销毁,注意该Activity需设计成用户无感知。
Notification提升权限
Android中Service的优先级为4,通过setForeground接口可以将后台Service设置为前台Service,使进程的优先级由4提升为2,从而是进程的优先级仅仅低于用户当前正在交互的进程,与可见进程优先级一致,使进程被杀死的概率大大降低。
利用系统广播拉活
在发生特定系统事件时,系统会发出相应的广播,通过在AndroidManifest中静态注册对应的广播监听器,即可在发生响应事件时拉活。
利用第三方应用广播拉活
该方案总的设计思想与接收系统广播类似,不同的是该方案为接收第三方Top应用广播。通过反编译第三方Top应用,如微信、支付宝等等,找出它们外发的广播,在应用中进行监听,这样当这些应用发出广播时,就会将我们的应用拉活。
利用系统Service机制拉活
将Service设置为START_STICKY,利用系统机制在Service挂掉后自动拉活。
利用Native进程拉活
利用Linux中的fork机制创建Native进程,在Native进程中监控主进程的存活,当主进程挂掉后,在Native进程中立即对主进程进行拉活。
原理:在Android中所有进程和系统组件的生命周期受ActivityManagerService的统一管理。而且,通过Linux的fork机制创建的进程为纯Linux进程,其生命周期不受Android管理。
利用 JobScheduler机制拉活
在Android5.0以后系统对Native进程等加强了管理,Native拉活方式失效。系统在Android5.0以后版本提供了 JobScheduler接口,系统会定时调用该进程以使应用进行一些逻辑操作。
利用账号同步机制拉活
Android系统的账号同步机制会定期同步账号进行,该方案目的在于利用同步机制进行进程的拉活。
在Android中使用消息机制,我们首先想到的就是Handler。没错,Handler是Android消息机制的上层接口。我们通常只会接触到Handler和Message开完成消息机制,其实内部还有两大助手共同完成消息传递。
消息机制主要包含:MessageQueue、Handler、Looper和Message这四大部分。
Message
需要传递的消息,可以传递数据
MessageQueue
消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能是向消息池传递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next)
Handle
消息辅助类,主要功能是向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)
Looper
不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。
运行流程:
在子线程执行完耗时操作,当Handler发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断的从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。
MessageQueue、Handler和Looper三者之间的关系:
每个线程中只能存在一个Looper,Looper是保存在ThreadLocal中。主线程已经创建一个Looper,所以在主线程中不需要在创建Looper,但是在其他线程中需要创建Looper。每个线程中可以有多个Handler,即一个Looper可以处理来自多个Handler的消息。Looper中维护一个MessageQueue,来维护消息队列,消息队列中的Message可以来自不同的Handler。
Android消息机制之ThreadLocal的工作原理:
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handle创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handle内部如何获取当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰的存储并提供数据,通过ThreadLocal可以轻松的获取每个线程的Looper。当然,需要注意的是,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程默认可以使用Handle的原因。
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说无法获取到数据。在日常开发中用到ThreadLocal的地方很少,但是在某些特殊的场景下,通过ThreadLocal可以轻松的实现一些看起来很复杂的功能,这一点在Android源码中也有所体现,比如Looper、ActivityThread以及AMS中都用到了ThreadLocal。具体到ThreadLocal的使用场景,这个不好统一来描述,一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以采用ThreadLocal。比如对于Handle来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松的实现Looper在线程中的存取。
ThreadLocal另外一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这个时候可以怎么做呢?其实就可以采用ThreadLocal,采用ThreadLocal可以让监听器作为线程内的局部对象而存在,在线程内部只要通过get方法就可以获取到监听器。而如果不采用ThreadLocal,那么我们能想到的可能就是一下两种方法:
将监听器通过参数的形式在函数调用栈中进行传递
在函数调用栈很深的时候,通过函数参数来传递监听器对象几乎是不可接受的
将监听器作为静态变量供线程访问
这是可以接受的,但是这种状态是不具有可扩充性的,比如如果同时有两个线程在执行,那就需要提供两个静态的监听器对象,如果有十个线程在并发执行呢?提供十个静态的监听器对象?这显然是不可思议的。而采用ThreadLocal每个监听器对象都在自己的线程内部存储,根本就不会有这个问题。
ThreadLocal<Boolean> threadLocal = new ThreadLocal<>();
threadLocal.set(true);
Log.i(TAG, "getMsg: MainThread"+threadLocal.get());
new Thread("Thread#1"){
@Override
public void run() {
threadLocal.set(false);
Log.i(TAG, "run: Thread#1" + threadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
Log.i(TAG, "run: Thread#2" + threadLocal.get());
}
}.start();
输出:true、false、null
虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们获取到的值却是不一样的。不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中取出一个数组,然后再从数据中根据当前ThreadLocal的索引去查找出对应的value值,很显然,不同线程中的数组是不同的,这就是为什么通过ThreadLocal可以在不同的线程中维护一套数据副本并且彼此互不干扰。
ThreadLocal是一个泛型类,里面有两个重要方法:get()和set()方法。它们所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的get和set方法,它们对ThreadLocal所做的读写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰的存储和修改数据。
Activity
Activity并不负者视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口。Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window以及View进行交互。
Window
Window是视图的承载器,内部持有一个DecorView,而这个DecorView才是view的跟布局。Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。PhoneWindow中有个内部类DecorView,通过创建DecorView来加载Activity中设置的布局。Window通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互。
DecorView
DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。
DecorView作为顶级View,一般情况下它内部包含一个竖直方向的LinearLayout,**在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面是内容栏。**在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏之中的,成为其唯一子View。
ViewRoot
ViewRoot可能比较陌生,但是其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。
ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带,View的三大流程(测量、布局、绘制)均通过ViewRoot来完成。
ViewRoot并不属于View树的一份子。从源码实现上来看,它既是非View的子类,也是非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。RootView继承了Handler类,可以接收事件并分发,Android的所有触屏事件,按键事件、界面刷新等事件都是通过ViewRoot来进行分发的。
Activity就像个控制器,不负责视图部分。Window像个承载器,装着内部视图。DecorView就是个顶级视图,是所有View的最外层布局。ViewRoot像个连接器,负者沟通,通过硬件感知来通知视图,进行用户之间的交互。
简述:
Activity --> ViewGroup --> View 责任链模式
dispatchTouchEvent 和 onTouchEvent 一旦 return true,事件就停止传递了(到达终点,没有谁再能收到这个事件),对于 return true 我们经常说事件被消费了,消费了的意思就是事件走到这里就是终点,不会往下传,没有谁能在收到这个事件了。
dispatchTouchEvent 和 onTouchEvent return false 的时候事件都会回传给父控件的 onTouchEvent处理。对于dispatchTouchEvent返回false的含义应该是:事件停止往子View传递和分发同时开始往父控件回溯(父控件的onTouchEvent开始从下往上回传直到某个onTouchEvent return true),事件分发机制就像递归,return false 的意义就是递归停止然后开始回溯。
对于onTouchEvent return false就比较简单了,它就是不消费事件,并让事件继续往父控件的方向从下往上流动。
oninterceptTouchEvent,用于事件拦截,只存在于ViewGroup中,如果返回true就会交给自己的onTouchEvent处理,如果不拦截就是往子控件往下传递。默认是不会去拦截的,因为子View也需要这个事件,所以onInterceptTouchEvent拦截器return super和false是一样的,事件往子View的dispatchTouchEvent传递。
对于ViewGroup,dispatchTouchEvent,之前说的return true就是终结传递,return false就是回溯到父View的onTouchEvent。那么ViewGroup怎样通过dispatchTouchEvent方法能把事件分发到自己的onTouchEvent处理呢?return false 和 true 都不行,那么只能通过 onInterceptTouchEvent把事件拦截下来给自己的onTouchEvent,所以ViewGroup的dispatchTouchEvent方法的super默认实现就是去调用onInterceptTouchEvent,记住这一点。
总结:
Android中RelativeLayout和LinearLayout性能分析
选择LinearLayout,因为RelativeLayout在measure过程需要两次。
//RelativeLayout源码
View[] views = mSortedHorizontalChildren;
int count = views.length;
for (int i = 0; i < count; i++) {
/**/
measureChildHorizontal(child, params, myWidth, myHeight);
}
/**/
for (int i = 0; i < count; i++){
/***/
measureChild(child, params, myWidth, myHeight);
}
//LinearLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
从源码我们发现RelativeLayout会对子View做两次measure。这是为什么呢?首先RelativeLayout中子View的排列方式是基于彼此的依赖关系,而这个依赖关系可能和布局中View的顺序并不相同,在确定每个子View的位置的时候,就需要先给所有的子View排序一下。又因为RelativeLayout允许A B两个子View,横向上B依赖于A,纵向上A依赖于B,所以需要横向纵向分别进行一次排序测量。
RelativeLayout另外一个性能问题:
View的measure方法里对绘制过程做了一个优化,如果我们的子View没有要求强制刷新,而父View给子View的传入值也没有变化,也就说子View的位置没有变化,就不会做无谓的measure。但是上面已经说了RelativeLayout要做两次measure,而在做横向测量时,纵向的测量结果尚未完成,只好暂时使用myHeight传入子View系统,假如子View的Height不等于(设置了margin)myHeight的高度,那么measure中优化则不起作用,这一过程将进一步影响RelativeLayout的绘制性能。而LinearLayout则无这方面的担忧,解决这个问题也很好办,如果可以,尽量使用padding代替margin。
结论
静态使用
步骤:
动态使用
步骤:
回退栈
Fragment的回退栈是用来保存每一次Fragment事务发生的变化,如果将Fragment任务添加到回退栈,当用户点击后退按钮时,将会看到上一次保存的Fragment,一旦Fragment完全从回退栈中弹出,用户再次点击后退键,则会退出当前Activity。
FragmentTransaction.addToBackStack(String) 把当前事务的变化情况添加到回退栈。
Fragment与Activity之间的通信
Fragment依附于Activity存在,因此与Activity之间的通信可以归纳为以下几种:
通信优化
接口实现
如何处理运行时配置发生变化
横竖屏切换时导致Fragment多次重建绘制:
不要在构造函数中传递参数,最好通过setArguments()传递。
在onCreate(Bundle savedInstanceState)中判断savedInstanceState为空时在重建,当发生重建时,原本的数据如何保持?类似Activity,Fragment也有onSaveInstanceState方法,在此方法中进行保存数据,然后在onCreate或者onCreateView或者onActivityCreated进行恢复都可以。
View视图绘制需要搞清楚两个问题,一个是从哪里开始绘制,一个是怎么绘制?
从哪里开始绘制?我们平常使用Activity的时候,都会调用setContentView来设置布局文件,没错,视图绘制就是从这个方法开始。
怎么绘制?
在我们的Activity中调用了setContentView之后,会转而执行PhoneWindow的setContentView,在这个方法里面会判断我们存放内容的ViewGroup(这个ViewGroup可以是DecorView也可以是DecorView的子View)是否存在。不存在的话,则会创建一个DecorView处理,并且会创建出相应的窗体风格,存在的话则会删除原先的ViewGroup上面已有的View,接着会调用LayoutInflater的inflate方法以pull解析的方式将当前布局文件中存在的View通过addView的方式添加到ViewGroup上面来,接着在addView方法里面就会执行我们常见的invalidate方法了,这个方法不只是在View视图绘制的过程中经常用到,其实动画的实现原理也是不断的调用这个方法来实现视图不断重绘的,执行这个方法的时候会调用父View的invalidateChild方法,这个方法是属于ViewParent的,ViewGroup以及ViewRootImpl中都会他进行了实现,invalidateChild里面主要做的是就是通过do while循环一层一层计算出当前View的四个点所对应的矩阵在ViewRoot中所对应的位置,那么有了这个矩阵的位置之后最终都会执行ViewRootImpl的invalidateChildInParent方法,执行这个方法的时候首先会检查当前线程是不是主线程,因为我们要开始准备更新UI了,不是主线程的话是不允许更新UI的,接着就会执行scheduleTraversals方法了,这个方法会通过handler来执行doTraversal方法,在这个方法里面就见到了我们平常所熟悉的View视图绘制的起点方法performTraversals了。
那么接下来就是真正的视图绘制流程了,大体上讲View的绘制流程经历了Measure测量、Layout布局以及Draw绘制的三个过程,具体来讲是从ViewRootImpl的performTraversals方法开始,首先执行的将是performMeasure方法,这个方法里面会传入两个MeasureSpec类型的参数,它在很大程度上决定了View的尺寸规格,对于DecorView来说宽高的MeasureSpec值的获取与窗口尺寸以及自身的LayoutParams有关,对于普通View来说其宽高的MeasureSpec值获取由父容器以及自身的LayoutParams属性共同决定,在performMeasure里面会执行measure方法,在measure方法里面会执行onMeasure方法,到这里Measure测量过程对View与ViewGroup来说是没有区别的,但是从onMeasure开始两者有差别了,因为View本身已经不存在子View了,所以他onMeasure方法将执行setMeasuredDimension方法,该方法会设置View的测量值,但是对于ViewGroup来说,因为它里面还存在着子View,那么我们就需要继续测量它里面的子View了,调用的方法是measureChild方法,该方法内部又会执行measure方法,而measure方法转而又会执行onMeasure方法,这样不断的递归进行下去,直到整个View树测量结束,这样performMeasure方法执行结束了。接着便是执行performLayout方法了,performMeasure只是测量出了View树中View的大小了,但是还不知道View的位置,所以也就出现了performLayout方法了,performLayout方法首先会执行layout方法,以确定View自身的位置,如果当前View是ViewGroup的话,则会执行onLayout方法。在onLayout方法里面又会递归的执行layout方法,直到当前遍历到的View不再是ViewGroup为止,这样整个layout布局过程就结束了。在View树中View的大小以及位置都确定之后,接下来就是真正的绘制View显示在界面的过程了,该过程首先从performDraw方法开始,performDraw首先会执行draw方法,在draw方法中首先绘制背景,接着调用onDraw方法绘制自己,如果当前View是ViewGroup的话,还要调用dispatchDraw方法绘制当前ViewGroup的子View,而dispatchDraw方法里面实际上是通过drawChild方法间接调用draw方法形成递归绘制整个View树,直到当前View不再是ViewGroup为止,这样整个View的绘制过程就结束了。
总结:
- 在自定义View的过程中经常会遇到滑动冲突问题,一般滑动冲突的类型有三种:(1)外部View滑动方向和内部View滑动方向不一致;(2)外部View滑动方向和内部View滑动方向一致;(3)上述两种情况的嵌套
-
- 一般解决滑动冲突都是利用事件分发机制,有两种方式即外部拦截法和内部拦截法:
外部拦截法:
实现思路是事件首先是通过父容器的拦截处理,如果父容器不需要该事件,则不拦截,将事件传递到子View上面,如果父容器决定拦截的话,则在父容器的onTouchEvent里面直接处理该事件,这种方法符合事件分发机制;具体实现是修改父容器的onInterceptTouchEvent方法,在达到某一条件的时候,让该方法直接返回true,就可以把事件拦截下来进而调用自己的onTouchEvent方法来处理,但是有一点需要注意的是,如果想让子View能够收到事件,我们需要在onInterceptTouchEvent方法里面判断是DOWN事件的话,就返回false,这样后续的MOVE以及UP事件才有机会传递到子View上面,如果你直接在onInterceptTouchEvent方法里面DOWN情况下返回了true,那么后续的MOVE以及UP事件酱由当前View的onTouchEvent处理了,这样的拦截根本没有意义的,拦截只是在满足一定条件下才会拦截,并不是所有情况下都要拦截。
Android Studio点击build按钮之后,AS就会编译整个项目,并将apk安装到手机上,这个过程就是Android工程编译打包过程。主要的流程是:
编译 --> DEX --> 打包 --> 签名
APK构建步骤详解
主要分为六个步骤:
scheme是一种页面跳转协议。
通过定义自己的scheme协议,可以非常方便的跳转App中的各个页面;
通过scheme协议,服务器可以定制化告诉App跳转到App内部页面。
Scheme协议在Android中的使用场景
MVC模式
MVP模式
MVP模式核心思想:
MVP把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,Model类还是原来的Model。
这就是MVP模式,现在Activity的工作简单了,只是用来相应生命周期,其他工作都丢到Presenter中完成,从上图可以看出,Presenter是Model和View之间的桥梁,为了让结构变得更加简单,View并不能直接对Model进行操作,这也是MVP与MVC最大的不同之处。
MVP模式的作用
MVP模式的使用
从上述的UML图可以看出,使用MVP至少经历以下几个步骤:
总结 MVP模式的整个核心流程:
View与Model并不直接交互,而是使用Presenter作为View与Model之间的桥梁。其实Presenter中同时持有View层的interface的引用以及Model层的引用,而View层持有Presenter层引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的引用,然后Presenter层会调用Model层请求数据,当Model层数据加载成功之后会调用Presenter层的回调方法通知Presenter层数据加载情况,最后Presenter层在调用View层的接口将加载后的数据展示给用户。
SurfaceView继承至View的,与View的主要区别在于:
总结一句话就是:如果自定义View需要频繁的刷新,或者刷新时数据处理量比较大,那么就可以考虑使用SurfaceView来取代View。
HandlerThread继承了Thread,所以它本质上是一个Thread,与普通Thread的区别在于,它不仅建立了一个线程,并且创立了消息队列,有自己的looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己这个Looper对象的get方法。
HandlerThread自带的Looper使它可以通过消息队列,来重复使用当前的线程,节省系统资源开销。这是他的优点也是缺点,每一个任务队列都是以队列的方式逐个被执行到,一旦队列中某个任务执行时间过长,那么就会导致后续的任务都会被延时执行。
特点:
IntentService是继承并处理异步请求的一个类,其本质上是一个Service,因为它是继承至Service,所以开启IntentService和普通的Service一致。但是他和普通的Service不同之处在于它可以处理异步任务,在任务处理完之后会自动结束。另外,我们可以启动多次IntentService,而每一个耗时任务会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且是串行执行。其实IntentService的内部是通过HandleThread和Handle来实现异步操作的。
尽量减少Item布局嵌套,比如使用ConstraintLayout
可以的话,写死ItemView的布局宽高,然后设置RecyclerView.setHasFixedSize(true)。当知道Adapter内Item的改变不会影响RecyclerView的宽高时,这样做可以避免重新计算大小。
但是,如果调用notifyDataSetChanged(),大小还是会重新计算(requestLayout)。当调用Adapter的增删改查方法,最后就会根据mHasFixedSize这个值来判断需不需要requestLayout()。
根据需求修改RecyclerView默认的绘制缓存选项
recyclerView.setItemViewCacheSize(20)
recyclerView.setDrawingCacheEnabled(true)
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH)
在onBindViewHolder 或 getView 方法中,减少逻辑判断,减少临时对象创建。例如:复用事件监听,在其方法外部创建监听,可以避免生成大量的临时变量。
避免整个列表的数据更新,只更新受影响的布局。例如,加载更多时,不使用notifyDataSetChanged,而是使用notifyItemRangeInserted(rangeStart,rangeEnd)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。