Android开发艺术探索笔记 五到八章

来源:互联网 发布:农村淘宝在哪里设置 编辑:程序博客网 时间:2024/05/21 22:30

本篇博客介绍了RemoteViews的使用和内部机制、Android各种动画的使用和内部机制还有就是Window的使用和内部机制。

第五章 理解RemoteViews

RemotrViews是一种远程View,我们平时会看见一些桌面的控件还有就是自定义通知栏项目,这些控件就是基于RemoteViews。而这些控件是运行在和app不同的进程中的,所以一般的View在这个时候就不起作用了,但是RemoteViews这个时候就能起到很好的作用。

1.RemoteViews的应用

  • 1.在通知栏上面使用RemoteViews:我们都知道默认通知栏是怎么设置,而自定义的通知栏就是在默认通知栏的基础上,定义一个RemoteView,然后将这个实例传入通知栏实例就行了。所以我们只要知道怎么定义一个RemoteView就行了。
    • 1.new一个实例,然后传入两个参数:一个是包名getPackageName()。一个是我们定义的RemoteViews的xml布局。
    • 2.为RemoteViews中的View设置参数:注意这里的RemoteViews是没有findById这个函数的,我们用到的是其他函数。这里我用一个表列举几个常用的函数:
      RemoteViews的set方法
  • 2.RemoteViews在桌面小部件上的应用:AppWidgetProvider是提供桌面小部件的类,其本质是一个广播,要定义个一个桌面小部件必须要经过一下几个步骤:
    • 1.定义一个xml文件来提供RemoteViews的布局。
    • 2.配置一个xml文件用来定义小部件的配置:有一下几个参数
      • 1.initialLayout:用来添加1中的布局
      • 2.minHeight:用来设置小部件最小的高度
      • 3.minWidth:用来设置小部件最小的宽度
      • 4.updatePeriodMillis:用来设置小部件自动更新的周期,毫秒为单位。
    • 3.定义小部件的实现类即AppWidgetProvider,要实现这个类的话需要重写两个函数:一个是onReceive(),一个是onUpdate()。
      • 1.onReceive():由于我们前面说过了这个类实际上是一个广播,这个类可以接收到广播,而这个广播就是我们在RemoteViews中定义的PendindIntent,当我们触发了小部件上的事件的时候,小部件就会发出一个广播,然后传入到这个函数当中,最后我们通过判断事件的类型来进行小部件的操作。
      • 2.onUpdate():同1,这个类也可以发出广播,但我们需要对于小部件进行初始化或者更新的时候,我们就可以用这个方法。注意这个方法在小部件初始化的时候也要会调用这个函数,所以我们要在这里进行RemoteViews的初始化。初始化分为两个部分,1.定义一个RemoteViews的实体。2.调用这个AppWidgetManager的updateAppWidget()函数传入这个RemoteViews。
    • 4.在AndroidManifest中声明小部件:AppWidgetProvider本质上是一个广播组件,所以必须要注册,注册小部件也需要以下几个步骤
      • 1.在meta-data中设置该类的配置文件xml。
      • 2.在intent-filter中设置Action,用于识别之前我们定义的识别小部件单机的行为。
      • 3.在intent-filter中设置Action,这次是android.appwidget.action.APPWIDGET.UPDATE这个行为,这个是系统规范,必须定义。
    • 5.AppWidgetProvider还有一些回调能被onReceive方法在适合的时候调用:
      • 1.onEnable:当小部件第一次添加到桌面的时候被调用,重复添加的时候,只有第一次调用
      • 2.onDeleted:每删除一次小部件就调用一次。
      • 3.onDisabled:当最后一个这个类型的小部件被删除的时候调用这个方法。
      • 4.通过源码可以知道这几个方法也是通过,onReceive方法接收到相应的广播之后被调用的。
  • 3.PendingIntent概述:我们前面用到了这个类,这个类简单说来就是延迟Intent的触发。有点像异步操作,当某个操作完成之后PendingIntent中的Intent就会被调用。注意PendingIntent不继承与Intent,只是其内部保存有一个Intent
    • 1.先来说说PendingIntent的几个获取方法:
      • 1.getActivity(Context context,int requestCode,Intent intent,int flags):当某个意图触发的时候效果相当于Context.startActivty(Intent)。
      • 2.getService(Context context,int requestCode,Intent intent,int flags):当某个意图触发的时候效果相当于Context.startService(Intent)。
      • 3.getBroadCast(Context context,int requestCode,Intent intent,int flags):当某个意图触发的时候效果相当于Context.startBroadCast(Intent)。
      • 4.**我们来看看上面几个函数传入的参数:1和3就不看了我们来看2和4
        • 1.2和3这个两个参数是用来判断PendingIntent是否是同一类型。当两个PendingIntent是同一类型的时候,requestCode要相同,intent的ComponentName和intent-dilter要相同。反之,如果上面有一个不同那么就不是相同类型。**
        • 2.4这个参数是用来决定PendingIntent使用的方法:
          • 1.FLAG_ONE_SHOT:当前的PendingIntent只能使用一次,使用过了之后相同类型的PendingIntent都会失效,所有PendingIntent以第一个为模版。拿通知来说,当几个通知的id不一样的时候,其PendingIntent如果类型相同,后续的PendingIntent的内容和首个PendingIntent完全一样包括Extras,然后当我们触发了一个PendingIntent的时候,其他通知的PendingIntent都会失效。
          • 2.FLAG_NO_CREATE:一般用不到
          • 3.FLAG_CANCEL_CURRENT:只能使用最新的PendingIntent,之前的相同类型的PendingIntent都会失效。拿通知来说,当几个通知的id不一样的时候,前面发送的通知如果PendingIntent是同一类型的话,那么只有最后一个通知的PendindIntent能够触发。
          • FLAG_UPDATE_CURRENT:所有的PendingIntent都能触发,并且所有的PendingIntent以最后一个为模版。拿通知来说,当几个通知的id不一样的时候,前面发送的通知如果PendingIntent是同一类型的话,那么所有的PendingIntent的内容会更新到与最后一个相同,包括Extars。而且所有的PendingIntent都能被触发。

2.RemoteViews的内部机制

首先我们先来看看RemoteViews中能够放入哪些View:

  • Layout:FrameLayout,LinearLayout,RelativeLayout,DridLayout
  • View:AnalogCLock,Button,Chronometer,ImageButton,ImageView,ProgressBar,TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper,ViewStub
  • 如果xml文件中放入了除上面以外的View的话,那么就会报错。我们前面说过RemoteViews中是没有findViewById,所有的View我们如果要改变他的属性的话,就要去调用我们之前说的set函数,这些函数是通过反射来实现的。

接下来我们就来看看RemoteViews的实现机制

大家应该都知道通知栏和桌面小部件是分别被NotificationManagerAppWidgetManager中管理的,而这两个东西又是分别运行在SystemServiceNotificationManangerServiceAppWidgetManagerService中的,所以这里就构成了跨进程通讯的场景。我们先来简单用理论分析一下:

  • 1.因为实现了Parceable接口,RemoteViews首先会通过Binder传递到SystemService进程。
  • 2.然后系统会根据RemoteViews中的包信息去还用配置文件去获取应用的资源,然后通过xml文件加载一个View在SystemService中,这个View对于SystemService这个进程来说是普通的View,对于我们的进程来说是一个RemoteViews
  • 3.由于RemoteViews中记录这一系列View中需要显示的信息,所以在执行的时候,这些信息就会被加载到View中,最后显示在桌面或者任务栏中。
  • 4.当我们需要更新RemoteViews中的信息中,我们就需要在AppWidgetProvider更新RemoteViews的信息,最后由这个类发出信息给SystemService,让其更新RemoteViews。
  • 5.理论上来说我们可以通过Binder来进行View的所有操作,但是这样太浪费系统资源了。所以系统使用了Action这个类,这个类也实现了Parceable接口。

    • 1.我们当调用了一个set方法之后,就会创建一个Action放在RemoteViews中,然后通过Binder传输到SystemService进程。最后相应的Manager会调用RemoteViews的apply方法,来遍历RemoteViews中的Action并调用其apply来进行RemoteViews的设置。
    • 2.若我们给RemoteViews加入了触发事件的话,那么得分两个方式:
      • 1.若是通知栏的话:当我们触发了该事件,那么我们传入的PendingIntent就会被触发。
      • 2.若是小部件的话:当我们触发了该事件,那么其会发出带有Action的广播,然后传到AppWidgetProvider中,最后由我们自己来判断操作。
        接下来我们就来从源码中看看RemoteViews的机制,我们就看setTextViewText()是怎么运行的:
  • 1.这个函数最终会调用addAction(new ReflectionAction(viewId,methodName,ReflectionAction.CHAR_SEQUENCE,value)),viewId就是xml中定义的View的id,methodName是我们需要设置的View的方法的名字,第三个是传入参数的种类,最后一个是要传入的参数。

  • 2.addAction()中就是将这个ReflectionAction传入到RemoteViews中,RemoteViews中持有了一个Action的List。
  • 3.我们前面说到RemoteViews是通过自身的apply方法来遍历调用Action的apply方法的,所以我们再来看看apply(Context context,ViewGroup parent,onCLickHandler handler)。
  • 4.这个方法里会通过LayoutInflater来加载RemoteViews中的xml文件,最后调用performApply(View,ViewGroup,handler)
  • 5.performApply()中就是遍历前面的Action的list,然后调用相应的apply()。
  • 6.apply()这个方法是初始化View时候调用的,reApply()是在更新View的时候调用的。以上的机制是通知栏和小部件共用的。
  • 7.ReflectionAction继承与Action,其apply方法内部就是靠反射来调用View相应的方法实现对View属性的修改。

3.RemoteViews的意义

这一节就是讲RemoteViews的用法,只要跟着书中实践就行了。

第六章 Android的Drawable

这一章我觉得不是很重要就略过了。

第七章 Android动画深入分析

Android中动画分为三种:View动画,帧动画,属性动画。View动画是对View的影像进行平移、收缩、旋转、透明度变化等操作。注意这里只是对View的影像进行操作,也就是说View实际的位置还是在原来的地方,具体的表现就是View的点击事件还是只能在原地触发。帧动画是将一系列图片组合起来,以一张张播放的形式来使眼睛形成动画的感觉,像我们平时看的电影和电视剧就是这样由一张张胶片制作而成,而在帧动画我们可以把图片看出胶片,那么最后就能形成连续的动画了。属性动画,这个是API11也就是Android3.0中的新特性在低版本中不能使用,是View动画的补充,也就是说属性动画就是View的整个变化了,而不再局限于影像的变化,

1.先来说说View动画

对于View动画我想在开发中都用到过,所以不需要多说只是总结几个点:

  • 1.建议在xml中定义View动画比较简单,只要设置相应动画的属性就行了。
  • 2.xml中定义的时候,可以单个动画也可以用set标记来定义一个组合动画。这里提一下set的两个属性:
    • 1.android:interpolator表示动画的插值器,如果一个动画需要是非匀速播放,那么就可以用到插值器,系统默认设置的是加减速插值器。这个在后面会详细的讲解。
    • 2.android:shareInterpolator这个表示该集合是否共用一个插值器。如果集合不指定插值器那么其内部的子动画就必须自行设置或者使用默认的插值器。

2.帧动画

帧动画是在xml中定义好一系列图片之后,使用AnimationDrawable来播放的动画。

3.一些View动画使用的特殊场景

  • 1.ViewGroup元素控制子View出场动画,要使用这个动画有一下几步:
    • 1.定义一个layoutAnimation为根节点的xml文件,其有几个属性
      • 1.android:delay 元素延迟出现的事件,比如说动画为300毫秒,如果这个设置为0.5那么每个元素都会延迟150毫秒出现。
      • 2.android:animationOrder 有三个选项:normal、reverse、random。分别表示按顺序显示、逆向显示、无规律显示。
      • 3.android:animation 为子元素提供动画,传入的参数是xml。
        -2.为ViewGroup指定android:layoutAnimation属性,参数是1中定义的xml文件。
  • 2.Activity的切换效果:直接调用overridePendingTrasition(int enterAnim,int exitAnim)传入资源xml就行.注意需要在startActivity()后面或者finish后面调用。

4.View的属性动画

  • 1.属性动画的使用比较简单,按照书上来就行。一般有三种方式来定义属性动画
    • 1.xml文件中定义
    • 2.使用objectAnimator之类的动画类定义。
    • 3.直接调用View的方法来设置动画。
  • 2.插值器和估值器
    • 1.插值器:作用是根据时间的流逝的百分比来计算属性改变的百分比,例如用到线性差值器,动画的一个周期为300ms。那么当时间过了150ms的时候,View的属性变化了设置的一半。系统中预置了:LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:两头快中间慢动画)、DecelerateInterpolator(减速插值器:越来越慢动画)。
    • 2.估值器:在1的基础上由这个东西来计算出属性到底变化了多少数值的类。默认的算法是:假如属性由插值器算出变化了0.5,变化宽度设置为0——400的话,那么这时估值器算出来的就是200。
    • 3.由上面两点可以了解到动画的基本原理:其实就是利用插值器和估值器,来计算出各个时刻View的属性,然后通过改变View的属性来,实现View的动画效果。
    • 4.如果我们需要自定义动画的话:那么插值器需要实现Interpolator或者TimeInterpolator,估值器则需要实现TypeEvaluator。实现起来也很简单,就是直接重写接口的方法返回自己需要的值就行。
    1. 属性动画的监听器:属性动画主要有以下两个接口
      • 1.AnimatorUpdateListener:其有四个方法,可以在相应的时刻进行回调分别是onAnimationStart()、onAnimationEnd()、onAnimationCancel()、onAnimationRepeat()。为了开发方便系统还提供了AnimatorListenerAdapter这个类,让我们只需要重写自己希望的方法。
      • 2.AnimatorUpdateListener:这个接口会监听整个动画过程,动画是由许多帧构成的,每播放一帧,其中的onAnimatorUpdate()方法就会被调用一次。
  • 4.对任意属性做动画:前面我们说过属性动画其实就是通过插值器和估值器来获取每个时刻View的属性,最后通过改变该时刻View的属性来使View产生动画。所以我们在使用属性动画的时候需要遵循一下几个条件:

    • 1.View对象需要提供set和get属性:属性动画是如何传入每个时刻View的属性给View的呢?重点就在设置属性动画时我们传入的属性名,属性动画的内部是通过反射的机制来获取View传入的属性名的set和get方法从而更改View的属性的。注意一件事:如果某个我们传入的属性名没有set和get方法的时候,那么程序就会crash
    • 2.View对象提供的set和get方法需要是能够改变View该属性的方法:我们前面分析过View的绘制流程,知道了一个View的绘制是由:x、y、translationX、translationY、left、top、right、bottom等等属性直接影响的,所以我们可以在属性动画的代码中传入这些属性名,然后让系统通过反射来设置这些属性。但是有些属性并不决定View的绘制,例如Button的width属性,如果我们使用了这个属性,那么View将毫无反应。究其原因,其实这个属性只是用来设置Button的最大宽度和最小宽度的,而对Button的绘制毫无作用。
  • 5.那么怎样才能对系统没有定义set和get的View属性设置动画呢?官方文档提供了三种方法:

    • 1.给你的对象加上set和get方法如果你有权限:这个不用多说,没啥用。
    • 2.用一个类来包装原始对象,间接为其提供get和set方法。这个其实很简单,我们只要自定义一个类,然后定义getXXX()和setXXX()。然后在get中提供原始对象的属性,在set中设置原始对象的属性。最后将这个类像View一样设置属性动画就行了。因为我们一旦我们这样做了,那么在动画进行的时候。ValueAniamtor(ObjectAnimator的父类)会根据插值器和估器算出此时View应有的属性,然后传入到该类set方法中,最后将属性传给View。
    • 3.使用ValueAnimator,这个的原理和2是一样的只不过更底层一些所以就不细说了。
  • 6.属性动画的工作原理

4.使用动画的注意事项

  • 1.OOM问题:主要出现在帧动画中,当图片数量或者质量很大的时候,会出现这个问题。
  • 2.内存泄漏:当一个动画是循环动画的时候,那么要在Activity退出的时候及时停止动画,要么会使Activity无法释放而产生内存泄漏。
  • 3.兼容问题:3.0以下的系统不能运行属性动画。
  • 4.View动画只是影像变化。
  • 5.不要使用px
  • 6.使用动画过程中可以开启硬件加速,能够提高动画的流畅性。

第八章 理解windows和WindowManager

Window表示一个窗口,手机上所有的视图都是显示在各种Window中的,有时我们需要一个悬浮窗那么就可以用window来实现。Windows是一个抽象类,其实现是PhoneWindow,其具体存在于WindowManagerService中,其增删改是由WindowManager来实现,由于WindowManagerService存在于其他进程中,所以WindowManager和WindowManagerService的交互过程是一个IPC过程。另外Window是没有实体的,也就是说所有显示在界面上的视图都是View,而Window只是一个接收窗口变化,还有屏幕事件的类,我们通过前面的View的事件机制分析可以了解,最后window接收到的事件都传给了View。

1.Window和WindowManager

  • 1.如何添加一个Window,有如下几个步骤:
    • 1.获取WindowManager实例,创建一个View,创建一个WindowManager.LayoutParams实体。
    • 2.为WindowManager.LayoutParams添加flags和type,这两个参数在后面分析。
    • 3.为WindowManager.LayoutParams添加各种位置信息。
    • 4.调用WindowMnager的addView(View,WindowManager.LayoutParams)方法
    • 5.这里Window的参考坐标系到底是相对的还是绝对的,暂时还要留一个疑问,书中也没具体举出来。
  • 2.我们来说一下1中提到的flags参数:具体来说flags有三种常用的:FLAG_NOT_FOCUSABLE、FLAG_NOT_TOUCH_MODAL、FLAG_SHOW_WHEN_LOCKED。我会再说些其他的flags**首先有个前提,如果没有设置flags那么该的窗口会接收所有传到这个窗口的事件,不管这个事件是不是在这个窗口区域中。**
    • 1.FLAG_NOT_TOUCH_MODAL:表示系统会将该Window区域以外的触摸事件传给下层Window,否则自己处理。一般来说Window都需要开启这个flags,要么其他Window无法接收到触摸事件
    • 2.FLAG_NOT_FOCUSABLE:表示这个Window不需要获取焦点,不接收任何输入事件。事件最终将传递到下层有焦点的Window,因为Window的事件会传递,所以这个flag开启的同时也会开启FLAG_NOT_TOUCH_MODAL。
    • 3.FLAG_SHOW_WHEN_LOCKED:开启此模式可以让Window显示在锁屏界面上。
    • 4.FLAG_KEEP_SCREEN_ON:当此窗口为用户可见时,保持设备常开,并保持亮度不变。
    • 5.FLAG_FULLSCREEN:窗口显示时,隐藏所有的屏幕装饰(例如状态条)。使窗口占用整个显示区域。
    • 6.注意这些flags是可以叠加的,我们只要用“|”这个符号将flags连起来就可以了。
  • 3.再来看1中提到的type:type有三种类型:应用Window、子Window、系统Window。Window是分层的,每个Windows都有对应的z-ordered,数字越大越顶层。我们从最顶层的Window说起。
    • 1.系统window: 例如Toast和系统状态栏,层级为2000-2999,对应着许多值一般有TYPE_SYSTEM_OVERLAY和TYPE_SYSTEM_ERROR。我们只需要设置type为他们就行。需要注意的是系统Window需要在AndroidManifest中设置相应的权限,,否则会报错。
    • 2.子Window:例如Dialog,层级为1000-1999,子Window是不能单独存在的,它需要依附在特定的父Window。
    • 3.应用Window:例如Activity,层级为1-99。
    • 4.注意type是int型的,所以我们可以自己设置type的数字。
  • 4.WindowManager提供了三个常用的方法:addView(View,ViewGroup.LayoutParams)、updataViewLayout(View,ViewGroup.LayoutParams)、removeView(View)。所以浮动窗口就只要创建一个窗口并添加到WindowManager中,最后根据手指的位置调用updataViewLayout()方法就行了。

2.Window的内部机制

前面说了Window的实体是View,Window只是用于接收信息然后传递给View而已。从WindowManager的三个方法我们也可以看出WindowManger实际操作的东西其实只是View。如果要实际操作Window的话必须通过windowManager来进行。

  • 1.WindowManagerGlobal:WindowManager是一个接口,其实现类是WindowMangerImpl,我们进入源码可以看见**最终Window的三个操作都是由WindowManagerGlobal的三个同名方法来实现。**WindowManagerGlobal有几个ArrayList比较重要:
    • 1.mViews:储存所有Window对应的View。
    • 2.mRoots:储存所有Window对应的ViewRootImpl。
    • 3.mParams:储存所有Window对应的WindowManager.LayoutParams。
    • 4.mDyingViews:储存所有正在被删除的View对象,从后面我们可以知道,如果要异步删除一个View,那么就会先把View存入这个ArrayList中。
    • 4.我们可以总结出,一个Window有一个对应的View和一个对应的ViewRootImpl。Window通过ViewRootImpl来链接View。
  • 2.Window的添加过程:最终会调用WindowManagerGlobal的addView(View,ViewGroup.LayoutParams,Display,Window)。
    • 1.判断View,Display是否为null,判断ViewGroup.LayoutParams是否是WindowManger.LayoutParam,否的话抛出异常。如果父Window不为null,调整窗口。
    • 2.创建ViewRootImpl,将param设置到View中(从这个我们也可以看出Window的实体就是View,因为Window的配置最终是设置到View中的,不过这里得注意,View使用到的是ViewGroup.LayoutParams的参数,我们传入的是Windowmanager.LayoutParams继承于ViewGroup.LayoutParams。),将view、viewRootImpl、param传到windowManagerGlobal的三个List中。
    • 3.通过ViewRootImpl的setView(View,WindowManger.LayoutParams,View),绘制View并远程添加Window。绘制最后会通过requestLayout()的scheduleTraVersals()来完成异步刷新。添加Window最后会通过WindowSession的addToDisplay()方法来实现,WindowsSession是一个Binder对象。addToDisplay()的内部是将Window的添加请求交给WindowMangerService处理,WindowManagerService内部会为每个应用保留一个单独的Session。
  • 3.Window的删除过程:调用的是WindowManagerGlobal的removeView()。
    • 1.通过findViewLocked()找出要删除的Window的View的索引。
    • 2.调用removeViewLocked(index,immediate):这个方法通过immediate来判断是同步还是异步删除View,为了防止同步删除出现的错误,一般都是异步删除的。
    • 3.removeViewLocked()中会调用ViewRootImpl的die(Boolean)方法。在异步删除的情况下,die()会通过Handler发送一个请求MSG_DIE的消息然后返回,最终在Handler中调用doDie()。在同步删除的情况下其内部会调用doDie()方法直接删除View。
    • 4.调用了die()之后,会返回一个bolean判断View是否已经被删除。如果是异步删除的话,这时View还没被删除掉。此时会将View存入我们前面说的mDyingViews中。
    • 5.我们在3中说到的doDie()方法会调用dispatchDetachedFromWindow()方法,真正删除View的逻辑就在其中,这个方法主要做四件事:
      • 1.垃圾回收如:清除数据、消息、回调。
      • 2.通过WindowSession的remove方法删除Window,这也是个IPC过程,最终会调用WindowmangerService的remove()方法。
      • 3.调用View的dispatchDetachedWindow(),内部会调用View的onDetachedFromWindow()和onDetechedFromWindowInternal()。我们可以通过重写onDetachedFromWindow()来做一些资源回收的工作如:终止动画、停止线程等。
      • 4.调用WindowMangerGloabl的doRemoveView()来刷新mRoots、mParams、mViews和mDyingViews。
  • 4.Window的更新过程:和上面的两个过程一样调用WindowMangerGlobal的updataLayout()。里面会更新View的ViewRootImpl的LayoutParams,然后通过ViewRootImpl的scheduleTraversals()来刷新View。最后通过WindowSession来更新Window,同样是个IPC过程。

3.window的创建过程

Android中所有的视图都是附着于Window上的,常见的有Activity、Dialog和Toast。每一个视图都对应着一个Window,所以这一节就来讲一讲这一些Window的创建过程。

  • 1.Activity的Window的创建过程:我们在后面会讲到Activity的创建流程,这里只是讲讲Window的创建。
    • 1.attach():Activity的Window创建是在Activity的attach()方法中开始的。首先创建一个PhoneWindow的实例(注意这里和书上不一样:我看了6.0的代码发现书上的介绍已经过时了)。
    • 2.attach():由于Activity实现了Window的CallBack接口,所以Window会设置一个回调这些回调包括:onAttachedToWindow、onDetachedFromWindow和dispatchTouchEvent等等。(从这里可以看出我前面的分析,Window只是一个接收事件和窗口变化的类,是没有实体的)
    • 3.attach()方法创建完Window之后就需要将View和Window关联起来,所以这时候就Activity的setContentView()就出来了。我们都知道我们在布局的id会传入这个函数中。我们看源码可以知道最终会调用PhoneWindow的setContentView(int layoutResId)。
    • 4.setContentView():我们在前面介绍过DecorView是Activity中的顶级View,所以这个时候如果PhoneWindow中没有它的话就会调用installDecor()中调用generateDecor()来创建一个空白的。如果有的话就会移除其中的所有View。
    • 5.installDecor():由于DecorView是有结构的所以这时候还需要调用generateLayout(DecorView)来加载布局文件,这个和系统的版本和主题有关。
    • 6.setContentView():调用LayoutInflater的inflate(int layoutResID,View mContentParent)来将我们定义的视图添加到DecorView中。
    • 7.setContentView():由于Activity实现了Window的接口,所以这个时候就会回调Activity的onContentChanged()方法了。我们可以在里面实现自己的逻辑。
    • 8.到这个时候Window和与其相连的View已经初始化完毕了,但是我们通过前面介绍的Window的添加可以知道,我们还需要调用WindowManger的addView()方法来在WindowManagerService中注册window和使用ViewRootImpl将Window和View连接起来。
    • 9.最后ActivityThread会在handleResumeActivity()中调用Activity的onResume()方法,然后在其中再调用makeVisiable(),最后makeVisiable()就会调用WindowManger的addView()来让View和Window建立联系。
  • 2.Dialog的创建和Activity类似:
    • 1.Dialog的window是在其构造函数中创建的,同样是创建一个PhoneWindow实例,然后设置给Window设置回调,不过这次是Dialog来实现这个回调。
    • 2.同样Dialog中的setContentView()最终会调用PhoneWindow的同名函数,然后就是重复Activity的Window创建步骤。
    • 3.最后Dialog会在其show()方法中调用WindowManager的addView()。
    • 4.当调用Dialog的cancel()时会调用WindowManger的removeViewImmediate()
    • 5.注意普通Dialog有个特殊的地方就是必须采用Activity的Context,因为窗口的创建需要应用的token,而这个东西只有Activity有。此外系统Window可以不需要token,所以我们可以通过指定我们前面说到的type来将Dialog的Window提升到系统Window
  • 3.Toast的Window创建过程:略
0 0