首页 文章 精选 留言 我的

精选列表

搜索[记忆机制],共10000篇文章
优秀的个人博客,低调大师

详解JVM内存管理与垃圾回收机制2 - 何为垃圾

随着编程语言的发展,GC的功能不断增强,性能也不断提高,作为语言背后的无名英雄,GC离我们的工作似乎越来越远。作为Java程序员,对这一点也许会有更深的体会,我们不需要了解太多与GC相关的知识,就能很好的完成工作。那还有必要深入了解GC吗?学习GC的意义在哪儿? 不管性能提高到何种程度,GC都需要花费一定的时间,对于实时性要求较高的场景,就必须尽量压低GC导致的最大暂停时间 (GC会导致应用线程处于暂停状态),举两个例子: 实时对战游戏:如果因为GC导致玩家频繁卡顿,任谁都会想摔手机吧。 金融交易:在某些对价格非常敏感的交易场景下(比如,外汇交易中价格的变动非常频繁),如果因为GC导致没有按照交易者指定的价格进行交易,相信我,这些交易者非生吃了你。 但也有许多场景,GC的最大暂停时间没那么重要,比如,离线分析、视频网站等等。因此,知道这个GC算法有这样的特征,所以它适合这个场景,对程序员来说非常有价值,这就是我们学习GC最重要的意义。接下来,我们将一步步走进GC的世界。 从诞生之初,人们就在思考GC需要完成的3件事情:何为垃圾?何时回收?如何回收?垃圾收集器在对内存进行回收前,第一件事就是要确定这些对象之中哪些还”活着“,哪些已经”死去“,而这些”死去“的对象,也就是我们所说的垃圾。 引用计数法 判断对象是否存活,其中一种方法是给对象添加一个引用计数器,每当有一个地方引用它,计数器的值就加1,当引用失效时,计数器的值减1,任一时刻,如果对象的计数器值为0,那么这个对象就不会再被使用,这种方法被称为引用计数法。在整个回收过程中,引用计数器的值会以极快的速度更新,因而计数值的更新任务变得繁重,而且需要给计数器预留足够大的内存空间,以确保它不会溢出。因此,引用计数法的算法很简单,但在实际运用中要考虑非常多的因素,所以它的实现往往比较复杂,更为重要的是它不能解决对象之间的循环引用问题。 举个栗子,下面的代码片段展示了为什么引用计数法无法解决循环引用的问题。 public class GcDemo { public static void main(String[] args) { // 在栈中分配内存空间给obj1,然后在堆中创建GcObject对象A // 将obj1指向A实例,这时A的引用计数值 = 1 GcObject obj1 = new GcObject(); // 同理,GcObject实例B的引用计数值 = 1 GcObject obj2 = new GcObject(); // GcObject实例2被引用,所以B引用计数值 = 2 obj1.instance = obj2; // 同理A的引用计数值 = 2 obj2.instance = obj1; // 栈中的obj1不再指向堆中A,这时A的计数值减1,变成1 obj1 = null; // 栈中的obj2不再指向堆中B,这时B的计数值减1,变成1 obj2 = null; } } class GcObject { public Object instance = null; } 仔细阅读代码中的注释,并结合下面的内存结构示意图,应该可以很好的理解其中的原因:如果JVM垃圾收集器采用引用计数法,当obj1和obj2不再指向堆中的实例A、B时,虽然A、B已经不可能再被访问,但彼此间相互引用导致计数器的值不为0,最终导致无法回收A和B。 图1 引用计数示意图 可达性分析 引用计数法有一个致命的问题,即无法释放有循环引用的垃圾,因此,主流的Java虚拟机都没有选用引用计数法来管理内存,而是通过可达性分析 (Reachability Analysis)来判定对象是否存活。 可达性分析的基本思路是找到一系列被称为”GC Roots“的对象引用 (Reference) 作为起始节点,通过引用关系向下搜索,能被遍历到的 (可到达的) 对象就被判定为存活,其余对象 (也就是没有被遍历到的) 自然被判定为死亡。这里需要着重理解的是:可达性分析本质是找出活的对象来把其余空间判定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间,简略的示意图如下所示。 图2 可达性算法示意图 从图中可以看出,经过可达性分析后,有不少对象没有在GC Roots的引用链条上,其中还包含一些相互引用的对象,这些对象在不久以后都会被垃圾收集器回收,因此,可达性分析算法可以有效解决引用计数法存在的致命问题。 但是,首次被标记的对象并一定会被回收,它还有自救的机会。一个对象真正的死亡至少需要经历两次标记过程: 标记所有不可达对象,并进行筛选,筛选的标准是该对象覆盖了finalize()方法且finalize()方法没有被虚拟机调用过,选出的对象将被放置在一个“即将被回收”的队列中。稍后虚拟机会创建一个低优先级的Finalizer线程去遍历队列中的所有对象并执行finalize()方法 对队列中的对象进行第二次标记,如果对象在finalize()方法中重新与引用链上的任何一个对象建立关联,那么这个对象将被移除队列,而还留在队列中的对象,就会被回收了。 要正确的实现可达性分析算法,就必须完整地枚举出所有的GC Roots,否则就有可能会漏掉本应存活的对象,如果垃圾收集器错误的回收了这些被漏掉的活对象,将会造成严重的bug。GC Roots作为垃圾回收的起点,必须是一些列活的引用 (Reference) 集合,那这个集合中究竟包含哪些引用?为什么这些引用可以作为GC Roots?要回答好这两个问题,需要对Java对象在内存中布局有一些初步的了解,所以,在下节会对相关知识进行补充。 参考资料 周志明 著; 深入理解Java虚拟机(第2版); 机械工业出版社,2013 知乎上关于GC ROOTS的问题

优秀的个人博客,低调大师

初探JAVA代码在虚拟机中的运行机制

前言 从这篇文章开始,后面我们将持续介绍JAVA虚拟机的工作原理。作为一名Android程序员,我们都知道Java代码有很多种运行方式,比如:可以在命令行中运行,可以在开发工具中运行,可以以jar文件的形式运行,甚至可以在网页中运行。这些执行的方式都离不开JRE(Java Runtime Environment) 即Java运行时环境。 我们平时开发之前搭建环境安装的JDK里面也有JRE,下面这张图片中我们可以看出JRE包含Java虚拟机和Java的一些核心组件库。 什么是JAVA虚拟机 我们常说我们写的代码是在JAVA虚拟机上运行的,到底什么是Java虚拟机昵? JAVA虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算功能来实现的。Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还有相应的指令系统。JVM屏蔽了与具体操作系统平台的相关信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在各种平台上不加修改的运行。 这么说固然没有毛病,但是感觉有些笼统,感觉这篇文章中写的不错: 深入理解Java虚拟机到底是什么 JAVA为什么要在虚拟机里运行 Java是一门语法复杂,抽象程度很高的高级程序语言。因此,直接在硬件设备上运行Java语言并不可行。所以,在运行Java之前我们要将Java语言进行转换。 将它转换成硬件能够识别的语言,目前这种转换的主流思想是:设计一个面向Java语言特性的虚拟机,并通过编译器将Java程序转换为该虚拟机所能识别的指令序列,也就是Java字节码。之所以叫Java字节码是因为Java字节码指令的操作码(opcode)被固定为一个字节。 我们写一个简单的HelloWorld.java的程序,编译成字节码就是下面图中的中间一列,可以看到它是由一个个字节组成的。并且我们可以将其反汇编为人类可读的代码格式。相对于C语言来说Java版本的反汇编结果相对比较精简,这是因为Java虚拟机相对于物理机而言,抽象程度更高。 # 最左列是偏移|中间列给虚拟机读的机器码|最右列是给人读的代码 0x00: b2 00 02 getstatic java.lang.System.out 0x03: 12 03 ldc 'Hello, World!' 0x05: b6 00 04 invokevirtual java.io.PrintStream.println 0x08: b1 return Java虚拟机可以由硬件实现,但更为常见的是在各个现有平台(WINDOWS_x64,Linux_aarch64)上提供软件实现。这么做的意义在于,一旦一个程序被转换为JAVA字节码,那么它便可以在不同平台上的虚拟机实现里运行。这就是所谓的“一次编写,到处运行”。 虚拟机的另一个好处是它带来了托管环境(Managed Runtime)。这个托管环境能够代替我们处理一些代码中冗长而且容易出错的部分。其中最广为人知的当属于自动内存管理和垃圾回收,这部分内容催生了一波垃圾回收调优的业务。 除此之外,托管环境还提供了诸如数组越界,动态类型,安全权限等的动态检测,使我们免于书写这些与业务无关的代码。 JAVA虚拟机具体怎么运行Java字节码 下面以标准JDK中的HotSpot虚拟机为例,从虚拟机以及底层硬件两个角度,探索Java具体怎么运行Java字节码的。 虚拟机角度 从虚拟机角度来讲,执行Java代码首先要将.java文件编译成class文件加载到Java虚拟机中。加载后的Java类会被存放于方法区(Method Area)中。实际运行时,虚拟机会执行方法区内的代码。这和段式内存管理中的代码段类似。Java虚拟机也在内存中划分出堆和栈来存储运行时数据。 不同的是,Java虚拟机会将栈细分为Java方法栈和本地方法栈,以及存放各个线程执行位置的PC寄存器。 在运行过程中,每当调用一个java方法时,Java虚拟机会在当前县城的Java方法栈中生成一个栈帧,用来存放局部变量及字节码的操作数。这个栈帧的大小时提前计算好的,而且Java虚拟机不要求栈帧在内存里连续分布。 当退出当前执行的方法时,不管是正常返回还是异常返回,Java虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。 硬件角度 从硬件角度来看,Java字节码无法直接执行,所以Java虚拟机需要将字节码翻译成机器码。 在HotSpot里面,上述翻译过程有两种形式:第一种是解释执行,即逐条将字节码翻译成机器码并执行;第二种是即使编译(Just-In-Time compliation ,JIT)即将一个方法中所有的字节码翻译成机器码后再执行。 前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快。HotSpot默认采用的是混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码以方法为单位进行即时编译。 JAVA虚拟机的运行效率 HotSpot虚拟机采用了多种技术来提升启动性能以及峰值性能,前面所说的即时编译就是最重要的技术之一。即时编译建立在程序符合二八定律的假设上(就是程序中20%的代码占据80%的资源)。对于占据大部分的不常用的代码,我们就不需要花费时间将其编译成机器码,而是采用解释执行的方式执行;另一方面占据小部分的最常用到的代码,我们采用即时编译将其编译为机器码,来达到理想的运行速度。 从理论上来讲,即时编译后的Java程序的执行效率是可能超过C++程序的。这是由于与静态编译相比,即时编译拥有程序的运行时信息,并且能够根据这个信息作出相应的调优。举个例子,我们都知道虚方法是用来实现面向对象语言多态性的。对于一个虚方法调用,尽管他有很多个目标方法,但在实际运行过程中可能只调用其中的一个。这个信息可以被即时编译利用从而减小虚方法调用的开销,从而达到比静态编译的C++程序更高的性能。 为了满足用户在不同场景的需要,HotSpot内置了多个即时编译器:C1、C2和Graal。Graal是Java10引入的实验性即时编译器。为什么会引入这么多编译器,就是为了在编译时间和生成代码的执行效率之间取舍。 C1编译器叫做Client编译器,主要面向的是对启动性能有要求的客户端GUI程序,采用的优化方案简单,因此编译时间较短。 C2又叫Server编译器,面向的是对峰值性能有要求的服务器端程序,优化手段相对比较复杂,所以编译时间较长,但是相对来说编译生成的代码执行效率高。 从JAVA7开始,HotSpot采用分层编译的方式:热点方法首先会被C1编译,而后热点中的热点会进一步被C2编译。为了不干扰应用的正常运行,HotSpot的即时编译放在额外的编译线程中进行的,HotSpot会根据CPU的数量设置编译线程的数目,并且按照1:2的比例配置分配给C1和C2。在计算资源充足的情况下,字节码的解释执行和即时编译可同时进行。编译完成后机器码会在下次调用该方法时启用,以代替原本的解释执行。 小结 前面提到几个问题: 1. Java代码为什么要在虚拟机中运行? 因为Java提供了可移植性,一旦Java代码被编译为Java字节码,就可以在不同平台上的Java虚拟机实现上运行。此外处理机还提供了一托管环境,帮我们处理一些冗长而且容易出错的事物,如内存管理等。 2. Java代码如何在虚拟机中运行? Java虚拟机将内存分为5个部分方法区,堆,Java方法栈,本地方法栈,PC寄存器。Java代码编译生成的class文件,首先需要加载到方法区中,然后才能在Java虚拟机中运行。 3. Java虚拟机的效率怎么样? 1、理论上讲,Java虚拟机的执行效率可能高于C++程序。 2、为了提高运行效率,标准JDK中HotSpot虚拟机采用混合执行的策略。它会解释执行Java字节码,将其中反复执行的热点代码以方法为单位进行即时编译,翻译成机器码后直接运行在底层硬件上。 3、HotSpot装载了多个不同的即时编译器,以便在编译时间和生成代码的执行效率之间取舍。 感谢 深入拆解JAVA虚拟机–郑雨迪

优秀的个人博客,低调大师

利用java反射机制,使用android系统的内部类成员函数

//访问某个属性 Context mmsCtx = createPackageContext( "com.android.lockscreen" , Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); Class<?> maClass = Class.forName( "com.android.lockscreen.LockScreenActivity" , true , mmsCtx.getClassLoader()); Object maObject = maClass.newInstance(); Field f_mPm = maClass.getDeclaredField( "TAG" ); f_mPm.setAccessible( true ); Log.i(LOG_TAG, "___________________________________________obj:" +f_mPm.get(maObject)); //访问方法 //访问 try { Context mmsCtx = createPackageContext( "com.android.lockscreen" , Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); Class<?> maClass = Class.forName( "com.android.lockscreen.LockScreenActivity" , true , mmsCtx.getClassLoader()); Object maObject = maClass.newInstance(); Field f_mPm = maClass.getDeclaredField( "TAG" ); //maClass.getDeclaredMethod("", parameterTypes); f_mPm.setAccessible( true ); Log.i(LOG_TAG, "___________________________________________obj:" +f_mPm.get(maObject)); Class[] resource = new Class[]{Context. class }; Object[] resourceObj = new Object[]{mmsCtx}; Method addsMethod = maClass.getDeclaredMethod( "getLockScreenView" , resource); addsMethod.setAccessible( true ); View addsResult = (View)addsMethod.invoke(maObject, resourceObj); Log.i(LOG_TAG, "___________________________________________addsResult:" +addsResult); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } //定义被访问 public class LockScreenActivity extends Activity { private String TAG = "LockScreenActivity" ; //private LockScreen mScreen = new LockScreen(LockScreenActivity.this); private LockScreen getLockScreenView(Context context){ Log.i(TAG, "_________________________________________context:" +context); return new LockScreen(context); } } //获取setting包的的Context Context mmsCtx = createPackageContext( "com.android.settings" , Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY); //使用setting的classloader加载com.android.settings.ManageApplications类 Class<?> maClass = Class.forName( "com.android.settings.ManageApplications" , true , mmsCtx.getClassLoader()); //创建它的一个对象 Object maObject = maClass.newInstance(); /* * 将私有域mPm赋值。因为mPm在SizeObserver的invokeGetSize中用到了, * 却因为没有执行onCreate而没有初始化,所以要在此处初始化。 */ Field f_mPm = maClass.getDeclaredField( "mPm" ); f_mPm.setAccessible( true ); f_mPm.set(maObject, mmsCtx.getPackageManager()); 分类: android solve 本文转自wanqi博客园博客,原文链接http://www.cnblogs.com/wanqieddy/archive/2011/10/25/2224200.html :如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(19)

Step 21.NativeInputQueue.handleReceiveCallback 这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中: intNativeInputQueue::handleReceiveCallback(intreceiveFd,intevents,void*data){ NativeInputQueue*q=static_cast<NativeInputQueue*>(data); JNIEnv*env=AndroidRuntime::getJNIEnv(); sp<Connection>connection; InputEvent*inputEvent; jobjectinputHandlerObjLocal; jlongfinishedToken; {//acquirelock AutoMutex_l(q->mLock); ssize_tconnectionIndex=q->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... connection=q->mConnectionsByReceiveFd.valueAt(connectionIndex); ...... status_tstatus=connection->inputConsumer.receiveDispatchSignal(); if(status){ ...... return0;//removethecallback } ...... status=connection->inputConsumer.consume(&connection->inputEventFactory,&inputEvent); ...... finishedToken=generateFinishedToken(receiveFd,connection->id,connection->messageSeqNum); inputHandlerObjLocal=env->NewLocalRef(connection->inputHandlerObjGlobal); }//releaselock ...... int32_tinputEventType=inputEvent->getType(); jobjectinputEventObj; jmethodIDdispatchMethodId; switch(inputEventType){ caseAINPUT_EVENT_TYPE_KEY: ...... inputEventObj=android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent)); dispatchMethodId=gInputQueueClassInfo.dispatchKeyEvent; break; } ...... } ...... env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, dispatchMethodId,inputHandlerObjLocal,inputEventObj, jlong(finishedToken)); ...... return1; } 这个函数首先是通过参数data获得当初注册InputChannel的NativeInputQueue对象,具体可以参考前面介绍的应用程序注册键盘消息接收通道过程的Step 21。接下来再通过参数receiveFd获得保存在这个NativeInputQueue对象中的mConnectionsByReceiveFd成员变量中的Connection对象。有了这个Connection对象后,就可以获得它内部的InputConsumer对象,这个InputConsumer对象是和上面的Step 18中介绍的InputPublisher对象相应的。 在InputChannel内部中,分别有一个InputPublisher对象和一个InputConsumer对象,对于Server端的InputChannel来说,它使用的是InputPublisher对象,通过它进行键盘消息的分发,而对于Client端的InputChannel来说,它使用的是InputConsumer对象,通过它进行键盘消息的读取。 获得了这个InputConsumer对象后首先是调用它的receiveDispatchSignal来确认是否是接收到了键盘消息的通知,如果是的话,再调用它的consume函数来把键盘事件读取出来,最后,调用Java层的回调对象InputQueue的DispatchKeyEvent来处理这个键盘事件。下面,我们就依次来分析这些过程。 Step 22.InputConsumer.receiveDispatchSignal 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputConsumer::receiveDispatchSignal(){ ...... charsignal; status_tresult=mChannel->receiveSignal(&signal); if(result){ returnresult; } if(signal!=INPUT_SIGNAL_DISPATCH){ ...... returnUNKNOWN_ERROR; } returnOK; } 这个函数很简单,它通过它内部对象mChannel来从前向管道的读端读入一个字符,看看是否是前面的Step 20中写入的INPUT_SIGNAL_DISPATCH字符。 InputChannel类的receiveSignal函数也是定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputChannel::receiveSignal(char*outSignal){ ssize_tnRead; do{ nRead=::read(mReceivePipeFd,outSignal,1); }while(nRead==-1&&errno==EINTR); if(nRead==1){ ...... returnOK; } ...... return-errno; } Step 23.InputConsumer.consume 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputConsumer::consume(InputEventFactoryInterface*factory,InputEvent**outEvent){ ...... *outEvent=NULL; intashmemFd=mChannel->getAshmemFd(); intresult=ashmem_pin_region(ashmemFd,0,0); ...... if(mSharedMessage->consumed){ ...... returnINVALID_OPERATION; } //Acquirebut*neverrelease*thesemaphore.Contentiononthesemaphoreisusedtosignal //tothepublisherthatthemessagehasbeenconsumed(orisintheprocessofbeing //consumed).Eventuallythepublisherwillreinitializethesemaphoreforthenextmessage. result=sem_wait(&mSharedMessage->semaphore); ...... mSharedMessage->consumed=true; switch(mSharedMessage->type){ caseAINPUT_EVENT_TYPE_KEY:{ KeyEvent*keyEvent=factory->createKeyEvent(); if(!keyEvent)returnNO_MEMORY; populateKeyEvent(keyEvent); *outEvent=keyEvent; break; } ...... } returnOK; } 这个函数很简单,只要对照前面的Step 18(InputPublisher.publishKeyEvent)来逻辑来看就可以了,后者是往匿名共享内存中写入键盘事件,前者是从这个匿名共享内存中把这个键盘事件的内容读取出来。 回到Step 21中的handleReceiveCallback函数中,从InputConsumer中获得了键盘事件的内容(保存在本地变量inputEvent中)后,就开始要通知Java层的应用程序了。在前面分析应用程序注册键盘消息接收通道的过程时,在Step 21中(NativeInputQueue.registerInputChannel),会把传进来的对象inputHandlerObj保存在Connection对象中: connection->inputHandlerObjGlobal=env->NewGlobalRef(inputHandlerObj); 这个inputHandlerObj对象的类型为Java层的InputHandler对象,因此,这里首先把它取回来: inputHandlerObjLocal=env->NewLocalRef(connection->inputHandlerObjGlobal); 取回来之后,我们要把作为参数来调用InputQueue类的dispatchKeyEvent静态成员函数来通知应用程序,有键盘事件发生了,因此,先找到InputQueue类的静态成员函数dispatchKeyEvent的ID: dispatchMethodId=gInputQueueClassInfo.dispatchKeyEvent; 在回调用这个InputQueue类的dispatchKeyEvent静态成员函数之前,还要把前面获得的inputEvent对象转换成Java层的KeyEvent对象: inputEventObj=android_view_KeyEvent_fromNative(env, static_cast<KeyEvent*>(inputEvent)); 万事具备了,就可以通知Java层的InputQueue来处理这个键盘事件了: env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, dispatchMethodId,inputHandlerObjLocal,inputEventObj, jlong(finishedToken)); 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966638,如需转载请自行联系原作者

优秀的个人博客,低调大师

droid应用程序键盘(Keyboard)消息处理机制分析(10)

Step 2.ViewRoot.requestLayout 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... publicvoidrequestLayout(){ ...... mLayoutRequested=true; scheduleTraversals(); } ...... } 这个函数调用了scheduleTraversals函数来进一步执行操作,由于篇幅关系,我们就不详细描述scheduleTraversals函数了,简单来说,在scheduleTraversals函数中,会通过sendEmptyMessage(DO_TRAVERSAL)发送一个消息到应用程序的消息队列中,这个消息最终由ViewRoot的handleMessage函数处理,而ViewRoot的handleMessage函数把这个消息交给ViewRoot类的performTraversals来处理,在performTraversals函数中,又会调用ViewRoot类的relayoutWindow函数来进一步执行操作,最后在relayoutWindow函数中,就会通过WindowManagerService内部类Session的远程接口sWindowSession的relayout函数来进入到WindowManagerService中。 Step 3.WindowManagerService.Session.relayout 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... privatefinalclassSessionextendsIWindowSession.Stub implementsIBinder.DeathRecipient{ ...... publicintrelayout(IWindowwindow,WindowManager.LayoutParamsattrs, intrequestedWidth,intrequestedHeight,intviewFlags, booleaninsetsPending,RectoutFrame,RectoutContentInsets, RectoutVisibleInsets,ConfigurationoutConfig,SurfaceoutSurface){ //Log.d(TAG,">>>>>>ENTEREDrelayoutfrom"+Binder.getCallingPid()); intres=relayoutWindow(this,window,attrs, requestedWidth,requestedHeight,viewFlags,insetsPending, outFrame,outContentInsets,outVisibleInsets,outConfig,outSurface); //Log.d(TAG,"<<<<<<EXITINGrelayoutto"+Binder.getCallingPid()); returnres; } ...... } ...... } 这个函数只是简单地调用WindowManagerService的成员函数relayoutWIndow来进一步处理。 Step 4.WindowManagerService.relayoutWIndow 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... publicintrelayoutWindow(Sessionsession,IWindowclient, WindowManager.LayoutParamsattrs,intrequestedWidth, intrequestedHeight,intviewVisibility,booleaninsetsPending, RectoutFrame,RectoutContentInsets,RectoutVisibleInsets, ConfigurationoutConfig,SurfaceoutSurface){ ...... synchronized(mWindowMap){ ...... mInputMonitor.updateInputWindowsLw(); } ...... } ...... } 这个函数又会继续调用mInputMonitor的updateInputWindowsLw成员函数来更新当前的输入窗口,mInputMonitor是WindowManagerService的成员变量,它的类型为InputMonitor。 Step 5.InputMonitor.updateInputWindowsLw 这个函数定义在frameworks/base/services/java/com/android/server/WindowManagerService.java文件中: publicclassWindowManagerServiceextendsIWindowManager.Stub implementsWatchdog.Monitor{ ...... finalclassInputMonitor{ ...... /*Updatesthecachedwindowinformationprovidedtotheinputdispatcher.*/ publicvoidupdateInputWindowsLw(){ //Populatetheinputwindowlistwithinformationaboutallofthewindowsthat //couldpotentiallyreceiveinput. //Asanoptimization,wecouldtrytoprunethelistofwindowsbutthisturns //outtobedifficultbecauseonlythenativecodeknowsforsurewhichwindow //currentlyhastouchfocus. finalArrayList<WindowState>windows=mWindows; finalintN=windows.size(); for(inti=N-1;i>=0;i--){ finalWindowStatechild=windows.get(i); if(child.mInputChannel==null||child.mRemoved){ //Skipthiswindowbecauseitcannotpossiblyreceiveinput. continue; } ...... //Addawindowtoourlistofinputwindows. finalInputWindowinputWindow=mTempInputWindows.add(); ...... } //Sendwindowstonativecode. mInputManager.setInputWindows(mTempInputWindows.toNullTerminatedArray()); ...... } ...... } ...... } 这个函数将当前系统中带有InputChannel的Activity窗口都设置为InputManager的输入窗口,但是后面我们会看到,只有当前激活的窗口才会响应键盘消息。 Step 6.InputManager.setInputWindows 这个函数定义在frameworks/base/services/java/com/android/server/InputManager.java文件中: publicclassInputManager{ ...... publicvoidsetInputWindows(InputWindow[]windows){ nativeSetInputWindows(windows); } ...... } 这个函数调用了本地方法nativeSetInputWindows来进一步执行操作。 Step 7.InputManager.nativeSetInputWindows 这个函数定义在frameworks/base/services/jni/com_android_server_InputManager.cpp文件中: staticvoidandroid_server_InputManager_nativeSetInputWindows(JNIEnv*env,jclassclazz, jobjectArraywindowObjArray){ if(checkInputManagerUnitialized(env)){ return; } gNativeInputManager->setInputWindows(env,windowObjArray); } 这里的gNativeInputManager我们前面分析InputManager的启动过程时已经见过了,这是一个本地InputManager对象,通过它进一步设置当前系统的输入窗口。 本文转自 Luoshengyang 51CTO博客,原文链接:xxxxxxx,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(7)

函数首先根据文件名来打开这个设备文件: fd=open(deviceName,O_RDWR); 系统中所有输入设备文件信息都保存在成员变量mDevicesById中,因此,先在mDevicesById找到一个空位置来保存当前打开的设备文件信息: mDevicesById[devid].seq=(mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK; if(mDevicesById[devid].seq==0){ mDevicesById[devid].seq=1<<SEQ_SHIFT; } 找到了空闲位置后,就为这个输入设备文件创建相应的device_t信息: mDevicesById[devid].seq=(mDevicesById[devid].seq+(1<<SEQ_SHIFT))&SEQ_MASK; if(mDevicesById[devid].seq==0){ mDevicesById[devid].seq=1<<SEQ_SHIFT; } new_mFDs=(pollfd*)realloc(mFDs,sizeof(mFDs[0])*(mFDCount+1)); new_devices=(device_t**)realloc(mDevices,sizeof(mDevices[0])*(mFDCount+1)); if(new_mFDs==NULL||new_devices==NULL){ LOGE("outofmemory"); return-1; } mFDs=new_mFDs; mDevices=new_devices; ...... device_t*device=newdevice_t(devid|mDevicesById[devid].seq,deviceName,name); if(device==NULL){ LOGE("outofmemory"); return-1; } device->fd=fd; 同时,这个设备文件还会保存在数组mFDs中: mFDs[mFDCount].fd=fd; mFDs[mFDCount].events=POLLIN; mFDs[mFDCount].revents=0; 接下来查看这个设备是不是键盘: //Figureoutthekindsofeventsthedevicereports. uint8_tkey_bitmask[sizeof_bit_array(KEY_MAX+1)]; memset(key_bitmask,0,sizeof(key_bitmask)); LOGV("Gettingkeys..."); if(ioctl(fd,EVIOCGBIT(EV_KEY,sizeof(key_bitmask)),key_bitmask)>=0){ //Seeifthisisakeyboard.Ignoreeverythinginthebuttonrangeexceptfor //gamepadswhicharealsoconsideredkeyboards. if(containsNonZeroByte(key_bitmask,0,sizeof_bit_array(BTN_MISC)) ||containsNonZeroByte(key_bitmask,sizeof_bit_array(BTN_GAMEPAD), sizeof_bit_array(BTN_DIGI)) ||containsNonZeroByte(key_bitmask,sizeof_bit_array(KEY_OK), sizeof_bit_array(KEY_MAX+1))){ device->classes|=INPUT_DEVICE_CLASS_KEYBOARD; device->keyBitmask=newuint8_t[sizeof(key_bitmask)]; if(device->keyBitmask!=NULL){ memcpy(device->keyBitmask,key_bitmask,sizeof(key_bitmask)); }else{ deletedevice; LOGE("outofmemoryallocatingkeybitmask"); return-1; } } } 如果是的话,还要继续进一步初始化前面为这个设备文件所创建的device_t结构体,主要就是把结构体device的classes成员变量的INPUT_DEVICE_CLASS_KEYBOARD位置为1了,以表明这是一个键盘。 如果是键盘设备,初始化工作还未完成,还要继续设置键盘的布局等信息: if((device->classes&INPUT_DEVICE_CLASS_KEYBOARD)!=0){ chartmpfn[sizeof(name)]; charkeylayoutFilename[300]; //amoredescriptivename device->name=name; //replaceallthespaceswithunderscores strcpy(tmpfn,name); for(char*p=strchr(tmpfn,'');p&&*p;p=strchr(tmpfn,'')) *p='_'; //findthe.klfileweneedforthisdevice constchar*root=getenv("ANDROID_ROOT"); snprintf(keylayoutFilename,sizeof(keylayoutFilename), "%s/usr/keylayout/%s.kl",root,tmpfn); booldefaultKeymap=false; if(access(keylayoutFilename,R_OK)){ snprintf(keylayoutFilename,sizeof(keylayoutFilename), "%s/usr/keylayout/%s",root,"qwerty.kl"); defaultKeymap=true; } status_tstatus=device->layoutMap->load(keylayoutFilename); if(status){ LOGE("Error%dloadingkeylayout.",status); } //telltheworldaboutthedevname(thedescriptivename) if(!mHaveFirstKeyboard&&!defaultKeymap&&strstr(name,"-keypad")){ //thebuilt-inkeyboardhasawell-knowndeviceIDof0, //thisdevicebetternotgoaway. mHaveFirstKeyboard=true; mFirstKeyboardId=device->id; property_set("hw.keyboards.0.devname",name); }else{ //ensuremFirstKeyboardIdissetto-something-. if(mFirstKeyboardId==0){ mFirstKeyboardId=device->id; } } charpropName[100]; sprintf(propName,"hw.keyboards.%u.devname",device->id); property_set(propName,name); //'Q'keysupport=cheaptestofwhetherthisisanalpha-capablekbd if(hasKeycodeLocked(device,AKEYCODE_Q)){ device->classes|=INPUT_DEVICE_CLASS_ALPHAKEY; } //SeeifthisdevicehasaDPAD. if(hasKeycodeLocked(device,AKEYCODE_DPAD_UP)&& hasKeycodeLocked(device,AKEYCODE_DPAD_DOWN)&& hasKeycodeLocked(device,AKEYCODE_DPAD_LEFT)&& hasKeycodeLocked(device,AKEYCODE_DPAD_RIGHT)&& hasKeycodeLocked(device,AKEYCODE_DPAD_CENTER)){ device->classes|=INPUT_DEVICE_CLASS_DPAD; } //Seeifthisdevicehasagamepad. for(size_ti=0;i<sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]);i++){ if(hasKeycodeLocked(device,GAMEPAD_KEYCODES[i])){ device->classes|=INPUT_DEVICE_CLASS_GAMEPAD; break; } } LOGI("Newkeyboard:device->id=0x%xdevname='%s'propName='%s'keylayout='%s'\n", device->id,name,propName,keylayoutFilename); } 到这里,系统中的输入设备文件就打开了。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966619,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(22)

Step 36. ViewRoot.finishInputEvent 这个函数定义在frameworks/base/core/java/android/view/ViewRoot.java文件中: publicfinalclassViewRootextendsHandlerimplementsViewParent, View.AttachInfo.Callbacks{ ...... privatevoidfinishInputEvent(){ ...... if(mFinishedCallback!=null){ mFinishedCallback.run(); mFinishedCallback=null; }else{ ...... } } ...... } ViewRoot类里面的成员变量mFinishedCallback是在前面Step 25中由InputQueue设置的,它是一个Runnable对象,实际类型是定义在InputQueue的内部类FinishedCallback,因此,这里调用它的run方法时,接下来就会调用InputQueue的内部类FinishedCallback的run成员函数: publicfinalclassInputQueue{ ...... privatestaticclassFinishedCallbackimplementsRunnable{ ...... publicvoidrun(){ synchronized(sLock){ ...... nativeFinished(mFinishedToken); ...... } } ...... } ...... } 这里它调用外部类InputQueue的本地方法nativeFinished来进一步处理。 Step 37. InputQueue.nativeFinished 这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中: staticvoidandroid_view_InputQueue_nativeFinished(JNIEnv*env,jclassclazz, jlongfinishedToken){ status_tstatus=gNativeInputQueue.finished( env,finishedToken,false/*ignoreSpuriousFinish*/); ...... } 这个函数只是简单只调用NativeInputQueue的finished方法来进一处处理。 Step 38. NativeInputQueue.finished 这个函数定义在frameworks/base/core/jni/android_view_InputQueue.cpp文件中: status_tNativeInputQueue::finished(JNIEnv*env,jlongfinishedToken,boolignoreSpuriousFinish){ int32_treceiveFd; uint16_tconnectionId; uint16_tmessageSeqNum; parseFinishedToken(finishedToken,&receiveFd,&connectionId,&messageSeqNum); {//acquirelock AutoMutex_l(mLock); ssize_tconnectionIndex=mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex); ...... connection->messageInProgress=false; status_tstatus=connection->inputConsumer.sendFinishedSignal(); ...... }//releaselock returnOK; } 这个函数最重要的参数便是finishedToken了,通过它可以获得之前通知Java层的InputQueue类来处理键盘事件的Connection对象,它的值是在上面的Step 21(NativeInputQueue.handleReceiveCallback)中生成的: finishedToken=generateFinishedToken(receiveFd,connection->id,connection->messageSeqNum); 函数generateFinishedToken的定义如下: jlongNativeInputQueue::generateFinishedToken(int32_treceiveFd,uint16_tconnectionId, uint16_tmessageSeqNum){ return(jlong(receiveFd)<<32)|(jlong(connectionId)<<16)|jlong(messageSeqNum); } 它的实现很简单,只是把receiveFd(前向管道的读端文件描述符)、connectionId(Client端的InputChannel对应的Connection对象在NativeInputQueue中的索引)和messageSeqNum(键盘消息的序列号)三个数值通过移位的方式编码在一个jlong值里面,即编码在上面的finishedToken参数里面。 因此,在上面的finished函数里面,首先就是要对参数值finishedToken进行解码,把receiveFd、connectionId和messageSeqNum三个值分别取回来: parseFinishedToken(finishedToken,&receiveFd,&connectionId,&messageSeqNum); parseFinishedToken的定义如下: voidNativeInputQueue::parseFinishedToken(jlongfinishedToken, int32_t*outReceiveFd,uint16_t*outConnectionId,uint16_t*outMessageIndex){ *outReceiveFd=int32_t(finishedToken>>32); *outConnectionId=uint16_t(finishedToken>>16); *outMessageIndex=uint16_t(finishedToken); } 有了这个receiveFd和connectionId之后,就可以把相应的Connection对象取回来了: ssize_tconnectionIndex=mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection>connection=mConnectionsByReceiveFd.valueAt(connectionIndex); 接下来就是调用这个connection对象中的inputConsumer对象来发送信号通知Server端的InputChannel,应用程序这一侧处理完刚才发生的键盘事件了: status_tstatus=connection->inputConsumer.sendFinishedSignal(); 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966645,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(23)

Step 39. InputConsumer.sendFinishedSignal 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputConsumer::sendFinishedSignal(){ ...... returnmChannel->sendSignal(INPUT_SIGNAL_FINISHED); } 这个函数的实现很简单,只是调用其内部对象mChannel的sendSignal函数来执行发送信号的通知。前面我们已经说过,这里的mChannel的类型为InputChannel,它是注册在应用程序一侧的Client端InputChannel,它的成员函数sendSignal的定义我们在上面的Step 20中已经分析过了,这里不再详述,不过,这里和上面Step 20不一样的地方是,它里的通知方向是从反向管道的写端(在应用程序这一侧)到反向管道的读端(在InputDispatcher这一侧)。 前面我们在分析应用程序注册键盘消息接收通道的过程时,在Step 18(InputDispatcher.registerInputChannel)中,说到InputDispatcher把一个反向管道的读端文件描述符添加到WindowManagerService所运行的线程中的Looper对象中去,然后就会在这个反向管道的读端上睡眠等待有这个管道有新的内容可读。现在,InputConsumer往这个反向管道写入新的内容了,于是,InputDispatcher就被唤醒过来了,唤醒过来后,它所调用的函数是InputDispatcher.handleReceiveCallback函数,这与前面的Step 21的逻辑是一样的。 Step 40. InputDispatcher.handleReceiveCallack 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中: intInputDispatcher::handleReceiveCallback(intreceiveFd,intevents,void*data){ InputDispatcher*d=static_cast<InputDispatcher*>(data); {//acquirelock AutoMutex_l(d->mLock); ssize_tconnectionIndex=d->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... nsecs_tcurrentTime=now(); sp<Connection>connection=d->mConnectionsByReceiveFd.valueAt(connectionIndex); ...... status_tstatus=connection->inputPublisher.receiveFinishedSignal(); if(status){ ...... return0;//removethecallback } d->finishDispatchCycleLocked(currentTime,connection); ...... return1; }//releaselock } 这个函数首先是通过传进来的receiveFd参数(反向管道的读端文件描述符)的值取得相应的Connection对象: ssize_tconnectionIndex=d->mConnectionsByReceiveFd.indexOfKey(receiveFd); ...... sp<Connection>connection=d->mConnectionsByReceiveFd.valueAt(connectionIndex); 然后通过调用这个connection对象的内部对象inputPublisher的receiveFinishedSignal函数来确认是否真的收到键盘事件处理完成的信号,确认之后,就会调用InputDispatcher对象d的finishDispatchCycleLocked函数来执行一些善后工作。下面我们就依次分析这两个过程。 Step 41. InputPublisher.receiverFinishedSignal 这个函数定义在frameworks/base/libs/ui/InputTransport.cpp文件中: status_tInputPublisher::receiveFinishedSignal(){ .... charsignal; status_tresult=mChannel->receiveSignal(&signal); if(result){ returnresult; } if(signal!=INPUT_SIGNAL_FINISHED){ ....... returnUNKNOWN_ERROR; } returnOK; } 这里的逻辑和前面的Step 22中NativeInputQueue确认是否真的收到键盘事件分发的信号的逻辑是一致的,都是通过InputChannel的receiveSignal函数来确认是否在管道中收到了某一个约定的字符值,不过,这里约定的字符值为INPUT_SIGNAL_FINISHED。 回到前面的Step 40中,确认了是真的收到了键盘事件处理完成的信号后,就调用InputDispatcher的finishDispatchCycleLocked函数来执行一些善后工作了。 Step 42. InputDispatcher.finishDispatchCycleLocked 这个函数定义在frameworks/base/libs/ui/InputDispatcher.cpp文件中: voidInputDispatcher::finishDispatchCycleLocked(nsecs_tcurrentTime, constsp<Connection>&connection){ ...... //Notifyothersystemcomponents. onDispatchCycleFinishedLocked(currentTime,connection); //Resetthepublishersincetheeventhasbeenconsumed. //Wedothisnowsothatthepublishercanreleasesomeofitsinternalresources //whilewaitingforthenextdispatchcycletobegin. status_tstatus=connection->inputPublisher.reset(); ...... startNextDispatchCycleLocked(currentTime,connection); } 这个函数主要就是做了三件事情: 一是通知其它系统,InputDispatcher完成了一次键盘事件的处理: //Notifyothersystemcomponents. onDispatchCycleFinishedLocked(currentTime,connection); 二是调用相应的connection对象的内部对象inputPublisher来的reset函数来回收一些资源,它里面其实就是释放前面在Step 18(InputPublisher.publishKeyEvent)使用的匿名共享内存了: //Resetthepublishersincetheeventhasbeenconsumed. //Wedothisnowsothatthepublishercanreleasesomeofitsinternalresources //whilewaitingforthenextdispatchcycletobegin. status_tstatus=connection->inputPublisher.reset(); 三是调用InputDispatcher的startNextDispatchCycleLocked函数来处理下一个键盘事件: startNextDispatchCycleLocked(currentTime,connection); 因为正在处理当前这个键盘事件的时候,很有可能又同时发生了其它的键盘事件,因此,这里InputDispatcher还不能停下来,需要继续调用startNextDispatchCycleLocked继续处理键盘事件,不过下一个键盘事件的处理过程和我们现在分析的过程就是一样的了。 至此,InputManager分发键盘消息给应用程序的过程就分析完成了,这是一个比较复杂的过程,不过,只要我们抓住主要的线索,就不难理解了,现在我们就小结一下这个过程的四个主要线索: A. 键盘事件发生,InputManager中的InputReader被唤醒,此前InputReader睡眠在/dev/input/event0这个设备文件上; B.InputReader被唤醒后,它接着唤醒InputManager中的InputDispatcher,此前InputDispatcher睡眠在InputManager所运行的线程中的Looper对象里面的管道的读端上; C. InputDispatcher被唤醒后,它接着唤醒应用程序的主线程来处理这个键盘事件,此前应用程序的主线程睡眠在Client端InputChannel中的前向管道的读端上; D. 应用程序处理处理键盘事件之后,它接着唤醒InputDispatcher来执行善后工作,此前InputDispatcher睡眠在Server端InputChannel的反向管道的读端上,注意这里与第二个线索处的区别。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966648,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android应用程序键盘(Keyboard)消息处理机制分析(24)

4.应用程序注销键盘消息接收通道的过程分析 当Activity窗口创建时,它会向InputManager注册键盘消息接收通道,而当Activity窗口销毁时,它就会向InputManager注销前面注册的键盘消息接收通道了,本节内容就来看看应用程序注销键盘消息接收通道的过程。 当我们按下键盘上的Back键时,当前激活的Activity窗口就会被失去焦点,但是这时候它还没有被销毁,它的状态被设置为Stopped;当新的Activity窗口即将要显示时,它会通知WindowManagerService,这时候WindowManagerService就会处理当前处理Stopped状态的Activity窗口了,要执行的操作就是销毁它们了,在销毁的时候,就会注销它们之前所注册的键盘消息接收通道。 新的Activity窗口通知WindowManagerService它即将要显示的过程比较复杂,但是它与我们本节要介绍的内容不是很相关,因此,这里就略过大部过程了,我们从ActvitiyRecord的windowsVisible函数开始分析。注意,这里的ActivityRecord是新的Activity窗口在ActivityManangerService的代表,而那些处于Stopped状态的Activity窗口 会放在ActivityStack类的一个等待可见的mWaitingVisibleActivities列表里面,事实于,对于那些Stopped状态的Activity窗口来说,它们是等待销毁,而不是等待可见。 像前面一样,我们先来看一张应用程序注销键盘消息接收通道的过程的序列图,然后根据这个序列图来详细分析互一个步骤: Step 1. ActivityRecord.windowsVisible 这个函数定义在frameworks/base/services/java/com/android/server/am/ActivityRecord.java文件中: classActivityRecordextendsIApplicationToken.Stub{ ...... booleannowVisible;//isthisactivity'swindowvisible? booleanidle;//hastheactivitygoneidle? ...... publicvoidwindowsVisible(){ synchronized(service){ ...... if(!nowVisible){ nowVisible=true; if(!idle){ ....... }else{ //Ifthisactivitywasalreadyidle,thenwenowneedto //makesureweperformthefullstopofanyactivities //thatarewaitingtodoso.Thisisbecausewewon't //dothatwhiletheyarestillwaitingforthisoneto //becomevisible. finalintN=stack.mWaitingVisibleActivities.size(); if(N>0){ for(inti=0;i<N;i++){ ActivityRecordr=(ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible=false; ...... } stack.mWaitingVisibleActivities.clear(); Messagemsg=Message.obtain(); msg.what=ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); } } ...... } } } ...... } 应用程序中的每一个Activity在ActivityManagerService都有一个代表ActivityRecord,它们以堆栈的形式组织在ActivityManaerService中的ActivityStack中。一个即将要显示,但是还没有显示的Activity,它在ActivityManagerService中的ActivityRecord的成员变量nowVisible为false,而成员变量idle为ture,表示这个即将要显示的Activity窗口处于空闲状态。因此,在上面的这个函数中,会执行下面的语句: finalintN=stack.mWaitingVisibleActivities.size(); if(N>0){ for(inti=0;i<N;i++){ ActivityRecordr=(ActivityRecord) stack.mWaitingVisibleActivities.get(i); r.waitingVisible=false; ...... } stack.mWaitingVisibleActivities.clear(); Messagemsg=Message.obtain(); msg.what=ActivityStack.IDLE_NOW_MSG; stack.mHandler.sendMessage(msg); } 前面我们说过,当用户按下键盘上的Back键时,当前激活的Activity记录就被放在ActivityStack对象stack的成员变量mWaitingVisibleActivities中了,这时候就要对它进行处理了。首先是将它们的Activity记录的waitingVisible设置为false,然后就把它们从ActivityStack对象stack的成员变量mWaitingVisibleActivities清空,最后向ActivityStack对象stack发送一个ActivityStack.IDLE_NOW_MSG消息。这个消息最终是由ActivityStack类的activityIdleInternal函数来处理的。 本文转自 Luoshengyang 51CTO博客,原文链接:http://blog.51cto.com/shyluo/966649,如需转载请自行联系原作者

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册