【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处。尊重劳动成果】

1 背景

之所以写这一篇博客的原因是由于之前有写过一篇《Android应用setContentView与LayoutInflater载入解析机制源代码分析》。然后有人在文章以下评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow载入显示机制是咋回事,所以我就写一篇文章来分析分析吧(本文以Android5.1.1 (API 22)源代码为基础分析),以便大家在应用层开发时不再迷糊。

PS一句:不仅有人微博私信我这个问题,还有人问博客插图这些是用啥画的,这里告诉大家。就是我,快来猛戳我

还记得之前《Android应用setContentView与LayoutInflater载入解析机制源代码分析》这篇文章的最后分析结果吗?就是例如以下这幅图:

在那篇文章里我们当时重点是Activity的View载入解析xml机制分析,当时说到了Window的东西,但仅仅是皮毛的分析了Activity相关的一些逻辑。(PS:看到这不清楚上面啥意思的建议先移步到《Android应用setContentView与LayoutInflater载入解析机制源代码分析》,完事再回头继续看这篇文章。)当时给大家承诺过我们要从应用控件一点一点往下慢慢深入分析,所以如今開始深入,可是本篇的深入也仅仅是仅限Window相关的东东。之后文章还会继续慢慢深入。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处。尊重劳动成果】

2 浅析Window与WindowManager相关关系及源代码

通过上面那幅图能够非常直观的看见,Android屏幕显示的就是Window和各种View,Activity在当中的作用主要是管理生命周期、建立窗体等。也就是说Window相关的东西对于Android屏幕来说是至关重要的(尽管前面分析Activity的setContentView等原理时说过一点Window。但那仅仅是皮毛。),所以有必要在分析Android应用Activity、Dialog、PopWindow载入显示机制前再看看Window相关的一些东西。

2-1 Window与WindowManager基础关系

在分析Window与WindowManager之前我们先看一张图:

接下来看一点代码,例如以下:

/** Interface to let you add and remove child views to an Activity. To get an instance* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.*/
public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}

能够看见,ViewManager接口定义了一组规则。也就是add、update、remove的操作View接口。也就是说ViewManager是用来加入和移除activity中View的接口。

继续往下看:

public interface WindowManager extends ViewManager {......public Display getDefaultDisplay();public void removeViewImmediate(View view);......public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable {......}
}

看见没有。WindowManager继承自ViewManager,然后自己还是一个接口,同一时候又定义了一个静态内部类LayoutParams(这个类比較重要。后面会分析。提前透漏下。假设你在APP做过相似360助手屏幕的那个悬浮窗或者做过那种相似IOS的小白圆点,点击展开菜单功能,你或多或少就能猜到这个类的重要性。)。

WindowManager用来在应用与Window之间的接口、窗体顺序、消息等的管理。继续看下ViewManager的还有一个实现子类ViewGroup。例如以下:

public abstract class ViewGroup extends View implements ViewParent, ViewManager {//protected ViewParent mParent;//这个成员是View定义的,ViewGroup继承自View,所以也能够拥有。//这个变量就是前面我们一系列文章分析View向上传递的父节点,相似于一个链表Node的next一样//终于指向了ViewRoot......public void addView(View child, LayoutParams params) {addView(child, -1, params);}......public void addView(View child, int index, LayoutParams params) {......// addViewInner() will call child.requestLayout() when setting the new LayoutParams// therefore, we call requestLayout() on ourselves before, so that the child's request// will be blocked at our levelrequestLayout();invalidate(true);addViewInner(child, index, params, false);}......
}

这下理解上面那幅图了吧,所以说View通过ViewGroup的addView方法加入到ViewGroup中。而ViewGroup层层嵌套到最顶级都会显示在在一个窗体Window中(正如上面背景介绍中《Android应用setContentView与LayoutInflater载入解析机制源代码分析》的示意图一样)。当中每一个View都有一个ViewParent类型的父节点mParent,最顶上的节点也是一个viewGroup,也即前面文章分析的Window的内部类DecorView(从《Android应用setContentView与LayoutInflater载入解析机制源代码分析》的总结部分或者《Android应用层View绘制流程与源代码分析》的5-1小节都能够验证这个结论)对象。同一时候通过上面背景中那幅图能够看出来。对于一个Activity仅仅有一个DecorView(ViewRoot)。也仅仅有一个Window。

2-2 Activity窗体加入流程拓展

前面文章说过。ActivityThread类的performLaunchActivity方法中调运了activity.attach(…)方法进行初始化。例如以下是Activity的attach方法源代码:

    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类型的mWindow对象。实际为PhoneWindow类实现了抽象Window类mWindow = PolicyManager.makeNewWindow(this);......//通过抽象Window类的setWindowManager方法给Window类的成员变量WindowManager赋值实例化mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);......//把抽象Window类相关的WindowManager对象拿出来关联到Activity的WindowManager类型成员变量mWindowManagermWindowManager = mWindow.getWindowManager();......}

看见没有。Activity类中的attach方法又创建了Window类型的新成员变量mWindow(PhoneWindow实现类)与Activity相关联,接着在Activity类的attach方法最后又通过mWindow.setWindowManager(…)方法创建了与Window相关联的WindowManager对象,最后又通过mWindow.getWindowManager()将Window的WindowManager成员变量赋值给Activity的WindowManager成员变量mWindowManager。

接下来我们看下上面代码中的mWindow.setWindowManager(…)方法源代码(PhoneWindow没有重写抽象Window的setWindowManager方法,所以直接看Window类的该方法源代码),例如以下:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,boolean hardwareAccelerated) {......if (wm == null) {wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);}//实例化Window类的WindowManager类型成员mWindowManagermWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);}

能够看见,Window的setWindowManager方法中通过WindowManagerImpl实例的createLocalWindowManager方法获取了WindowManager实例。例如以下:

public final class WindowManagerImpl implements WindowManager {......private WindowManagerImpl(Display display, Window parentWindow) {mDisplay = display;mParentWindow = parentWindow;}......public WindowManagerImpl createLocalWindowManager(Window parentWindow) {return new WindowManagerImpl(mDisplay, parentWindow);}......
}

看见没有?这样就把Activity的Window与WindowManager关联起来了。Activity类的Window类型成员变量mWindow及WindowManager类型成员变量mWindowManager就是这么来的。

回过头继续看上面刚刚贴的Activity的attach方法代码,看见mWindow.setWindowManager方法传递的第一个參数没?有人会想(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)这行代码是什么意思。如今告诉你。

《Android应用Context具体解释及源代码解析》一文中第三部分以前说过ActivityThread中创建了Acitivty(运行attach等方法)等东东,在创建这个Activity之前得到了Context的实例。

记不记得当时说Context的实现类就是ContextImpl吗?以下我们看下ContextImpl类的静态方法块,例如以下:

class ContextImpl extends Context {......//静态代码块。类载入时运行一次static {......//这里有一堆相似的XXX_SERVICE的注冊......registerService(WINDOW_SERVICE, new ServiceFetcher() {Display mDefaultDisplay;public Object getService(ContextImpl ctx) {//搞一个Display实例Display display = ctx.mDisplay;if (display == null) {if (mDefaultDisplay == null) {DisplayManager dm = (DisplayManager)ctx.getOuterContext().getSystemService(Context.DISPLAY_SERVICE);mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);}display = mDefaultDisplay;}//返回一个WindowManagerImpl实例return new WindowManagerImpl(display);}});......}//这就是你在外面调运Context的getSystemService获取到的WindowManagerImpl实例@Overridepublic Object getSystemService(String name) {ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);return fetcher == null ? null : fetcher.getService(this);}//上面static代码块创建WindowManagerImpl实例用到的方法private static void registerService(String serviceName, ServiceFetcher fetcher) {if (!(fetcher instanceof StaticServiceFetcher)) {fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;}SYSTEM_SERVICE_MAP.put(serviceName, fetcher);}
}

看见没有,我们都知道Java的静态代码块是类载入是运行一次的。也就相当于一个全局的,这样就相当于每一个Application仅仅有一个WindowManagerImpl(display)实例。

还记不记得《Android应用setContentView与LayoutInflater载入解析机制源代码分析》一文2-6小节中说的,setContentView的实质显示是触发了Activity的resume状态。也就是触发了makeVisible方法。那我们再来看下这种方法。例如以下:

    void makeVisible() {if (!mWindowAdded) {//也就是获取Activity的mWindowManager//这个mWindowManager是在Activity的attach中通过mWindow.getWindowManager()获得ViewManager wm = getWindowManager();//调运的实质就是ViewManager接口的addView方法,传入的是mDecorViewwm.addView(mDecor, getWindow().getAttributes());mWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);}

特别注意,看见makeVisible方法的wm变量没。这个变量就是Window类中通过调运WindowManagerImpl的createLocalWindowManager创建的实例,也就是说每一个Activity都会新创建这么一个WindowManager实例来显示Activity的界面的,有点和上面分析的ContextImpl中static块创建的WindowManager不太一样的地方就在于Context的WindowManager对每一个APP来说是一个全局单例的,而Activity的WindowManager是每一个Activity都会新创建一个的(事实上你从上面分析的两个实例化WindowManagerImpl的构造函数參数传递就能够看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值)。

继续看makeVisible中调运的WindowManagerImpl的addView方法例如以下:

public final class WindowManagerImpl implements WindowManager {//继承自Object的单例类private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();......public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);//mParentWindow是上面分析的在Activity中获取WindowManagerImpl实例化时传入的当前Window//view是Activity中最顶层的mDecormGlobal.addView(view, params, mDisplay, mParentWindow);}......
}

这里当前传入的view是mDecor。LayoutParams呢?能够看见是getWindow().getAttributes()。那我们进去看看Window类的这个属性。例如以下:

// The current window attributes.private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

原来是WindowManager的静态内部类LayoutParams的默认构造函数:

public LayoutParams() {super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);type = TYPE_APPLICATION;format = PixelFormat.OPAQUE;
}

看见没有。Activity窗体的WindowManager.LayoutParams类型是TYPE_APPLICATION的。

继续回到WindowManagerImpl的addView方法。分析能够看见WindowManagerImpl中有一个单例模式的WindowManagerGlobal成员mGlobal。addView终于调运了WindowManagerGlobal的addView,源代码例如以下:

public final class WindowManagerGlobal {......private final ArrayList<View> mViews = new ArrayList<View>();private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();private final ArraySet<View> mDyingViews = new ArraySet<View>();......public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {......//获取Activity的Window的getWindow().getAttributes()的LayoutParams final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;//假设是Activity中调运的,parentWindow=Window。假设不是Activity的。譬如是Context的静态代码块的实例化则parentWindow为nullif (parentWindow != null) {//根据当前Activity的Window调节sub Window的LayoutParamsparentWindow.adjustLayoutParamsForSubWindow(wparams);} else {......}ViewRootImpl root;......synchronized (mLock) {......//为当前Window创建ViewRootroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);//把当前Window相关的东西存入各自的List中,在remove中会删掉mViews.add(view);mRoots.add(root);mParams.add(wparams);}// do this last because it fires off messages to start doing thingstry {//把View和ViewRoot关联起来。非常重要!!!

root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ...... } } ...... }

能够看见,在addView方法中会利用LayoutParams获得Window的属性,然后为每一个Window创建ViewRootImpl。最后通过ViewRootImpl的setView方法通过mSession向WindowManagerService发送加入窗体请求把窗体加入到WindowManager中。并且由WindowManager来管理窗体的view、事件、消息收集处理等(ViewRootImpl的这一加入过程后面会写文章分析,这里先记住这个概念就可以)。

至此我们对上面背景中那幅图,也就是《Android应用setContentView与LayoutInflater载入解析机制源代码分析》这篇文章总结部分的那幅图又进行了更深入的一点分析。其目的也就是为了以下分析Android应用Dialog、PopWindow、Toast载入显示机制做铺垫准备。

2-3 继续顺藤摸瓜WindowManager.LayoutParams类的源代码

上面2-1分析Window与WindowManager基础关系时提到了WindowManager有一个静态内部类LayoutParams。它继承于ViewGroup.LayoutParams。用于向WindowManager描写叙述Window的管理策略。如今我们来看下这个类(PS:在AD上也能够看见,自备梯子点我看AD的),例如以下:

    public static class LayoutParams extends ViewGroup.LayoutParamsimplements Parcelable {//窗体的绝对XY位置,须要考虑gravity属性public int x;public int y;//在横纵方向上为相关的View预留多少扩展像素。假设是0则此view不能被拉伸。其它情况下扩展像素被widget均分public float horizontalWeight;public float verticalWeight;//窗体类型//有3种主要类型例如以下://ApplicationWindows取值在FIRST_APPLICATION_WINDOW与LAST_APPLICATION_WINDOW之间,是经常使用的顶层应用程序窗体,须将token设置成Activity的token;//SubWindows取值在FIRST_SUB_WINDOW和LAST_SUB_WINDOW之间,与顶层窗体相关联。需将token设置成它所附着宿主窗体的token。//SystemWindows取值在FIRST_SYSTEM_WINDOW和LAST_SYSTEM_WINDOW之间,不能用于应用程序。使用时须要有特殊权限。它是特定的系统功能才干使用;public int type;//WindowType:開始应用程序窗体public static final int FIRST_APPLICATION_WINDOW = 1;//WindowType:全部程序窗体的base窗体,其它应用程序窗体都显示在它上面public static final int TYPE_BASE_APPLICATION   = 1;//WindowType:普通应用程序窗体,token必须设置为Activity的token来指定窗体属于谁public static final int TYPE_APPLICATION        = 2;//WindowType:应用程序启动时所显示的窗体,应用自己不要使用这样的类型,它被系统用来显示一些信息,直到应用程序能够开启自己的窗体为止public static final int TYPE_APPLICATION_STARTING = 3;//WindowType:结束应用程序窗体public static final int LAST_APPLICATION_WINDOW = 99;//WindowType:SubWindows子窗体。子窗体的Z序和坐标空间都依赖于他们的宿主窗体public static final int FIRST_SUB_WINDOW        = 1000;//WindowType: 面板窗体。显示于宿主窗体的上层public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;//WindowType:媒体窗体(比如视频)。显示于宿主窗体下层public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;//WindowType:应用程序窗体的子面板。显示于全部面板窗体的上层public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;//WindowType:对话框,相似于面板窗体,绘制相似于顶层窗体,而不是宿主的子窗体public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;//WindowType:媒体信息。显示在媒体层和程序窗体之间。须要实现半透明效果public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;//WindowType:子窗体结束public static final int LAST_SUB_WINDOW         = 1999;//WindowType:系统窗体,非应用程序创建public static final int FIRST_SYSTEM_WINDOW     = 2000;//WindowType:状态栏,仅仅能有一个状态栏。位于屏幕顶端,其它窗体都位于它下方public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;//WindowType:搜索栏。仅仅能有一个搜索栏,位于屏幕上方public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;//WindowType:电话窗体,它用于电话交互(特别是呼入),置于全部应用程序之上,状态栏之下public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;//WindowType:系统提示,出如今应用程序窗体之上public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;//WindowType:锁屏窗体public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;//WindowType:信息窗体。用于显示Toastpublic static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;//WindowType:系统顶层窗体。显示在其它一切内容之上,此窗体不能获得输入焦点。否则影响锁屏public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;//WindowType:电话优先,当锁屏时显示,此窗体不能获得输入焦点,否则影响锁屏public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;//WindowType:系统对话框public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;//WindowType:锁屏时显示的对话框public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;//WindowType:系统内部错误提示,显示于全部内容之上public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;//WindowType:内部输入法窗体,显示于普通UI之上,应用程序可又一次布局以免被此窗体覆盖public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;//WindowType:内部输入法对话框。显示于当前输入法窗体之上public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;//WindowType:墙纸窗体public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;//WindowType:状态栏的滑动面板public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;//WindowType:安全系统覆盖窗体。这些窗户必须不带输入焦点。否则会干扰键盘public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;//WindowType:拖放伪窗体,仅仅有一个阻力层(最多),它被放置在全部其它窗体上面public static final int TYPE_DRAG               = FIRST_SYSTEM_WINDOW+16;//WindowType:状态栏下拉面板public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;//WindowType:鼠标指针public static final int TYPE_POINTER = FIRST_SYSTEM_WINDOW+18;//WindowType:导航栏(有别于状态栏时)public static final int TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19;//WindowType:音量级别的覆盖对话框。显示当用户更改系统音量大小public static final int TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20;//WindowType:起机进度框,在一切之上public static final int TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21;//WindowType:假窗,消费导航栏隐藏时触摸事件public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;//WindowType:梦想(屏保)窗体,略高于键盘public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;//WindowType:导航栏面板(不同于状态栏的导航栏)public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;//WindowType:universe背后真正的窗户public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;//WindowType:显示窗体覆盖,用于模拟辅助显示设备public static final int TYPE_DISPLAY_OVERLAY = FIRST_SYSTEM_WINDOW+26;//WindowType:放大窗体覆盖。用于突出显示的放大部分可訪问性放大时启用public static final int TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+27;//WindowType:......public static final int TYPE_KEYGUARD_SCRIM           = FIRST_SYSTEM_WINDOW+29;public static final int TYPE_PRIVATE_PRESENTATION = FIRST_SYSTEM_WINDOW+30;public static final int TYPE_VOICE_INTERACTION = FIRST_SYSTEM_WINDOW+31;public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;//WindowType:系统窗体结束public static final int LAST_SYSTEM_WINDOW      = 2999;//MemoryType:窗体缓冲位于主内存public static final int MEMORY_TYPE_NORMAL = 0;//MemoryType:窗体缓冲位于能够被DMA訪问,或者硬件加速的内存区域public static final int MEMORY_TYPE_HARDWARE = 1;//MemoryType:窗体缓冲位于可被图形加速器訪问的区域public static final int MEMORY_TYPE_GPU = 2;//MemoryType:窗体缓冲不拥有自己的缓冲区,不能被锁定,缓冲区由本地方法提供public static final int MEMORY_TYPE_PUSH_BUFFERS = 3;//指出窗体所使用的内存缓冲类型,默觉得NORMAL public int memoryType;//Flag:当该window对用户可见的时候。同意锁屏public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;//Flag:让该window后全部的东西都成暗淡public static final int FLAG_DIM_BEHIND        = 0x00000002;//Flag:让该window后全部东西都模糊(4.0以上已经放弃这样的毛玻璃效果)public static final int FLAG_BLUR_BEHIND        = 0x00000004;//Flag:让window不能获得焦点,这样用户快就不能向该window发送按键事public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;//Flag:让该window不接受触摸屏事件public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;//Flag:即使在该window在可获得焦点情况下,依然把该window之外的不论什么event发送到该window之后的其它windowpublic static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;//Flag:当手机处于睡眠状态时,假设屏幕被按下。那么该window将第一个收到public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;//Flag:当该window对用户可见时,让设备屏幕处于高亮(bright)状态public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;//Flag:让window占满整个手机屏幕。不留不论什么边界public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;//Flag:window大小不再不受手机屏幕限制大小,即window可能超出屏幕之外public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;//Flag:window全屏显示public static final int FLAG_FULLSCREEN      = 0x00000400;//Flag:恢复window非全屏显示public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;//Flag:开启抖动(dithering)public static final int FLAG_DITHER             = 0x00001000;//Flag:当该window在进行显示的时候,不同意截屏public static final int FLAG_SECURE             = 0x00002000;//Flag:一个特殊模式的布局參数用于运行扩展表面合成时到屏幕上public static final int FLAG_SCALED             = 0x00004000;//Flag:用于windows时,经常会使用屏幕用户持有反对他们的脸,它将积极过滤事件流,以防止意外按在这样的情况下,可能不须要为特定的窗体,在检測到这样一个事件流时,应用程序将接收取消运动事件表明,这样应用程序能够处理这相应地採取不论什么行动的事件,直到手指释放public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;//Flag:一个特殊的选项仅仅用于结合FLAG_LAYOUT_IN_SCpublic static final int FLAG_LAYOUT_INSET_DECOR = 0x00010000;//Flag:转化的状态FLAG_NOT_FOCUSABLE对这个窗体当前怎样进行交互的方法public static final int FLAG_ALT_FOCUSABLE_IM = 0x00020000;//Flag:假设你设置了该flag,那么在你FLAG_NOT_TOUNCH_MODAL的情况下。即使触摸屏事件发送在该window之外。其事件被发送到了后面的window,那么该window仍然将以MotionEvent.ACTION_OUTSIDE形式收到该触摸屏事件public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;//Flag:当锁屏的时候,显示该windowpublic static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;//Flag:在该window后显示系统的墙纸public static final int FLAG_SHOW_WALLPAPER = 0x00100000;//Flag:当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕public static final int FLAG_TURN_SCREEN_ON = 0x00200000;//Flag:消失键盘public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;//Flag:当该window在能够接受触摸屏情况下。让因在该window之外,而发送到后面的window的触摸屏能够支持split touchpublic static final int FLAG_SPLIT_TOUCH = 0x00800000;//Flag:对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;//Flag:让window占满整个手机屏幕。不留不论什么边界public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;//Flag:请求一个半透明的状态栏背景以最小的系统提供保护public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;//Flag:请求一个半透明的导航栏背景以最小的系统提供保护public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;//Flag:......public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;public static final int FLAG_SLIPPERY = 0x20000000;public static final int FLAG_LAYOUT_ATTACHED_IN_DECOR = 0x40000000;public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;//行为选项标记public int flags;//PrivateFlags:......public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 0x00000040;public static final int PRIVATE_FLAG_COMPATIBLE_WINDOW = 0x00000080;public static final int PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100;public static final int PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR = 0x00000200;public static final int PRIVATE_FLAG_KEYGUARD = 0x00000400;public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 0x00000800;//私有的行为选项标记public int privateFlags;public static final int NEEDS_MENU_UNSET = 0;public static final int NEEDS_MENU_SET_TRUE = 1;public static final int NEEDS_MENU_SET_FALSE = 2;public int needsMenuKey = NEEDS_MENU_UNSET;public static boolean mayUseInputMethod(int flags) {......}//SOFT_INPUT:用于描写叙述软键盘显示规则的bite的maskpublic static final int SOFT_INPUT_MASK_STATE = 0x0f;//SOFT_INPUT:没有软键盘显示的约定规则public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;//SOFT_INPUT:可见性状态softInputMode,请不要改变软输入区域的状态public static final int SOFT_INPUT_STATE_UNCHANGED = 1;//SOFT_INPUT:用户导航(navigate)到你的窗体时隐藏软键盘public static final int SOFT_INPUT_STATE_HIDDEN = 2;//SOFT_INPUT:总是隐藏软键盘public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;//SOFT_INPUT:用户导航(navigate)到你的窗体时显示软键盘public static final int SOFT_INPUT_STATE_VISIBLE = 4;//SOFT_INPUT:总是显示软键盘public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;//SOFT_INPUT:显示软键盘时用于表示window调整方式的bite的maskpublic static final int SOFT_INPUT_MASK_ADJUST = 0xf0;//SOFT_INPUT:不指定显示软件盘时,window的调整方式public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;//SOFT_INPUT:当显示软键盘时,调整window内的控件大小以便显示软键盘public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;//SOFT_INPUT:当显示软键盘时,调整window的空白区域来显示软键盘,即使调整空白区域,软键盘还是有可能遮挡一些有内容区域。这时用户就仅仅有退出软键盘才干看到这些被遮挡区域并进行public static final int SOFT_INPUT_ADJUST_PAN = 0x20;//SOFT_INPUT:当显示软键盘时,不调整window的布局public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;//SOFT_INPUT:用户导航(navigate)到了你的windowpublic static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;//软输入法模式选项public int softInputMode;//窗体怎样停靠public int gravity;//水平边距,容器与widget之间的距离,占容器宽度的百分率public float horizontalMargin;//纵向边距public float verticalMargin;//积极的insets画图表面和窗体之间的内容public final Rect surfaceInsets = new Rect();//期望的位图格式,默觉得不透明,參考android.graphics.PixelFormatpublic int format;//窗体所使用的动画设置,它必须是一个系统资源而不是应用程序资源,由于窗体管理器不能訪问应用程序public int windowAnimations;//整个窗体的半透明值,1.0表示不透明,0.0表示全透明public float alpha = 1.0f;//当FLAG_DIM_BEHIND设置后生效,该变量指示后面的窗体变暗的程度。1.0表示全然不透明,0.0表示没有变暗public float dimAmount = 1.0f;public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;//用来覆盖用户设置的屏幕亮度,表示应用用户设置的屏幕亮度。从0到1调整亮度从暗到最亮发生变化public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;public static final int ROTATION_ANIMATION_ROTATE = 0;public static final int ROTATION_ANIMATION_CROSSFADE = 1;public static final int ROTATION_ANIMATION_JUMPCUT = 2;//定义出入境动画在这个窗体旋转设备时使用public int rotationAnimation = ROTATION_ANIMATION_ROTATE;//窗体的标示符public IBinder token = null;//此窗体所在的包名public String packageName = null;//屏幕方向public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;//首选的刷新率的窗体public float preferredRefreshRate;//控制status bar是否显示public int systemUiVisibility;//ui能见度所请求的视图层次结构public int subtreeSystemUiVisibility;//得到关于系统ui能见度变化的回调public boolean hasSystemUiListeners;public static final int INPUT_FEATURE_DISABLE_POINTER_GESTURES = 0x00000001;public static final int INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002;public static final int INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004;public int inputFeatures;public long userActivityTimeout = -1;......public final int copyFrom(LayoutParams o) {......}......public void scale(float scale) {......}......}

看见没有。从上面类能够看出,Android窗体类型主要分成了三大类:

  1. 应用程序窗体。

    一般应用程序的窗体,比方我们应用程序的Activity的窗体。

  2. 子窗体。一般在Activity里面的窗体,比方对话框等。
  3. 系统窗体。系统的窗体,比方输入法,Toast,墙纸等。

同一时候还能够看见,WindowManager.LayoutParams里面窗体的type类型值定义是一个递增保留的连续增大数值,从凝视能够看出来事实上就是窗体的Z-ORDER序列(值越大显示的位置越在上面,你须要将屏幕想成三维坐标模式)。创建不同类型的窗体须要设置不同的type值,譬如上面拓展Activity窗体载入时分析的makeVisible方法中的Window默认属性的type=TYPE_APPLICATION。

既然说这个类非常重要。那总得感性的体验一下重要性吧,所以我们先来看几个实例。

2-4 通过上面WindowManager.LayoutParams分析引出的应用层开发经常使用经典实例

有了上面分析相信你一定觉得WindowManager.LayoutParams还是蛮熟悉的,不信我们来看下。

Part1:开发APP时设置Activity全屏常亮的一种办法(设置Activity也就是Activity的Window):

public class MainActivity extends ActionBarActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//设置Activity的Window为全屏。当然也能够在xml中设置Window window = getWindow();WindowManager.LayoutParams windowAttributes = window.getAttributes();windowAttributes.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN | windowAttributes.flags;window.setAttributes(windowAttributes);//设置Activity的Window为保持屏幕亮window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);setContentView(R.layout.activity_main);}
}

这是运行结果:

Part2:App开发中弹出软键盘时以下的输入框被软件盘挡住问题的解决的方法:

在Activity中的onCreate中setContentView之前写例如以下代码:

//你也能够在xml文件里设置。一样的效果
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE|WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);

Part3:创建悬浮窗体(仿IPhone的小圆点或者魅族的小白点或者360手机卫士的小浮标)。退出当前Activity依然可见的一种实现方法:

省略了Activity的start与stop Service的按钮代码,直接给出了核心代码例如以下:

/*** Author       : yanbo* Time         : 14:47* Description  : 手机屏幕悬浮窗,仿IPhone小圆点*               (未全然实现。仅仅提供思路,如需请自行实现)* Notice       : <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />*/
public class WindowService extends Service {private WindowManager mWindowManager;private ImageView mImageView;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();//创建悬浮窗createFloatWindow();}private void createFloatWindow() {//这里的參数设置上面刚刚讲过。不再说明WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);//设置window的typelayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;//设置效果为背景透明layoutParams.format = PixelFormat.RGBA_8888;//设置浮动窗体不可聚焦layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;layoutParams.gravity = Gravity.BOTTOM | Gravity.RIGHT;layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;layoutParams.x = -50;layoutParams.y = -50;mImageView = new ImageView(this);mImageView.setImageResource(android.R.drawable.ic_menu_add);//加入到WindowmWindowManager.addView(mImageView, layoutParams);//设置监听mImageView.setOnTouchListener(touchListener);}@Overridepublic void onDestroy() {super.onDestroy();if (mImageView != null) {//讲WindowManager时说过。add,remove成对出现,所以须要removemWindowManager.removeView(mImageView);}}private View.OnTouchListener touchListener = new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {//模拟触摸触发的事件Intent intent = new Intent(Intent.ACTION_VIEW);intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);return false;}};
}

例如以下是运行过程模拟,特别留意屏幕右下角的变化:

怎么样,通过最后这个样例你是不是就能体会到WindowManager.LayoutParams的Z-ORDER序列类型,值越大显示的位置越在上面。

2-5 总结Activity的窗体加入机制

有了上面这么多分析和前几篇的分析,我们对Activity的窗体载入再次深入分析总结例如以下:

能够看见Context的WindowManager对每一个APP来说是一个全局单例的。而Activity的WindowManager是每一个Activity都会新创建一个的(事实上你从上面分析的两个实例化WindowManagerImpl的构造函数參数传递就能够看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象。而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值)。所以上面模拟苹果浮动小图标使用了Application的WindowManager而不是Activity的,原因就在于这里;使用Activity的WindowManager时当Activity结束时WindowManager就无效了。所以使用Activity的getSysytemService(WINDOW_SERVICE)获取的是Local的WindowManager。同一时候能够看出来Activity中的WindowManager.LayoutParams的type为TYPE_APPLICATION。

好了。上面也说了不少了,有了上面这些知识点以后我们就来開始分析Android应用Activity、Dialog、PopWindow窗体显示机制。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果】

3 Android应用Dialog窗体加入显示机制源代码

3-1 Dialog窗体源代码分析

写过APP都知道,Dialog是一系列XXXDialog的基类,我们能够new随意Dialog或者通过Activity提供的onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法来管理我们的Dialog,可是究事实上质都是来源于Dialog基类,所以我们对于各种XXXDialog来说仅仅用分析Dialog的窗体载入就能够了。

例如以下从Dialog的构造函数開始分析:

public class Dialog implements DialogInterface, Window.Callback,KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {......public Dialog(Context context) {this(context, 0, true);}//构造函数终于都调运了这个默认的构造函数Dialog(Context context, int theme, boolean createContextThemeWrapper) {//默认构造函数的createContextThemeWrapper为trueif (createContextThemeWrapper) {//默认构造函数的theme为0if (theme == 0) {TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,outValue, true);theme = outValue.resourceId;}mContext = new ContextThemeWrapper(context, theme);} else {mContext = context;}//mContext已经从外部传入的context对象获得值(通常是个Activity)。!。非常重要。先记住!!。//获取WindowManager对象mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);//为Dialog创建新的WindowWindow w = PolicyManager.makeNewWindow(mContext);mWindow = w;//Dialog能够接受到按键事件的原因w.setCallback(this);w.setOnWindowDismissedCallback(this);//关联WindowManager与新Window。特别注意第二个參数token为null。也就是说Dialog没有自己的token//一个Window属于Dialog的话。那么该Window的mAppToken对象是nullw.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);mListenersHandler = new ListenersHandler(this);}......
}

能够看到。Dialog构造函数首先把外部传入的參数context对象赋值给了当前类的成员(我们的Dialog一般都是在Activity中启动的。所以这个context通常是个Activity),然后调用context.getSystemService(Context.WINDOW_SERVICE)获取WindowManager。这个WindowManager是哪来的呢?先依照上面说的context通常是个Activity来看待,能够发现这句实质就是Activity的getSystemService方法,我们看下源代码,例如以下:

    @Overridepublic Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}//我们Dialog中获得的WindowManager对象就是这个分支if (WINDOW_SERVICE.equals(name)) {//Activity的WindowManagerreturn mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);}

看见没有。Dialog中的WindowManager成员实质和Activity里面是一样的,也就是共用了一个WindowManager。

回到Dialog的构造函数继续分析,在得到了WindowManager之后,程序又新建了一个Window对象(类型是PhoneWindow类型,和Activity的Window新建过程相似)。接着通过w.setCallback(this)设置Dialog为当前window的回调接口。这样Dialog就能够接收事件处理了;接着把从Activity拿到的WindowManager对象关联到新创建的Window中。

至此Dialog的创建过程Window处理已经完成,非常easy。所以接下来我们继续看看Dialog的show与cancel方法。例如以下:

    public void show() {......if (!mCreated) {//回调Dialog的onCreate方法dispatchOnCreate(null);}//回调Dialog的onStart方法onStart();//相似于Activity,获取当前新Window的DecorView对象,所以有一种自己定义Dialog布局的方式就是重写Dialog的onCreate方法,使用setContentView传入布局。就像前面文章分析Activity相似mDecor = mWindow.getDecorView();......//获取新Window的WindowManager.LayoutParams參数。和上面分析的Activity一样type为TYPE_APPLICATIONWindowManager.LayoutParams l = mWindow.getAttributes();......try {//把一个View加入到Activity共用的windowManager里面去mWindowManager.addView(mDecor, l);......} finally {}}

能够看见Dialog的新Window与Activity的Window的type相同都为TYPE_APPLICATION,上面介绍WindowManager.LayoutParams时TYPE_APPLICATION的凝视明白说过,普通应用程序窗体TYPE_APPLICATION的token必须设置为Activity的token来指定窗体属于谁。所以能够看见,既然Dialog和Activity共享同一个WindowManager(也就是上面分析的WindowManagerImpl),而WindowManagerImpl里面有个Window类型的mParentWindow变量。这个变量在Activity的attach中创建WindowManagerImpl时传入的为当前Activity的Window,而当前Activity的Window里面的mAppToken值又为当前Activity的token。所以Activity与Dialog共享了同一个mAppToken值,仅仅是Dialog和Activity的Window对象不同。

3-2 Dialog窗体载入总结

通过上面分析Dialog的窗体载入原理,我们总结例如以下图:

从图中能够看出。Activity和Dialog共用了一个Token对象,Dialog必须依赖于Activity而显示(通过别的context搞完之后token都为null,终于会在ViewRootImpl的setView方法中载入时由于token为null抛出异常)。所以Dialog的Context传入參数通常是一个存在的Activity,假设Dialog弹出来之前Activity已经被销毁了,则这个Dialog在弹出的时候就会抛出异常。由于token不可用了。在Dialog的构造函数中我们关联了新Window的callback事件监听处理,所以当Dialog显示时Activity无法消费当前的事件。

到此Dialog的窗体载入机制就分析完成了,接下来我们说说应用开发中常见的一个诡异问题。

3-3 从Dialog窗体载入分析引出的应用开发问题

有了上面的分析我们接下来看下平时开发App刚開始学习的人easy犯的几个错误。

实如今一个Activity中显示一个Dialog,例如以下代码:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {setContentView(R.layout.activity_main);//重点关注构造函数的參数,创建一个Dialog然后显示出来Dialog dialog = new ProgressDialog(this);dialog.setTitle("TestDialogContext");dialog.show();}
}

分析:使用了Activity为context,也即和Activity共用token,符合上面的分析。所以不会报错,正常运行。

实如今一个Activity中显示一个Dialog,例如以下代码:

public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {setContentView(R.layout.activity_main);//重点关注构造函数的參数,创建一个Dialog然后显示出来Dialog dialog = new ProgressDialog(getApplicationContext());dialog.setTitle("TestDialogContext");dialog.show();}
}

分析:传入的是Application的Context,导致TYPE_APPLICATION类型Dialog的token为null。所以抛出例如以下异常,无法显示对话框。

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationat android.view.ViewRootImpl.setView(ViewRootImpl.java:566)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)at android.app.Dialog.show(Dialog.java:298)

实如今一个Service中显示一个Dialog。例如以下代码:

public class WindowService extends Service {@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onCreate() {super.onCreate();//重点关注构造函数的參数Dialog dialog = new ProgressDialog(this);dialog.setTitle("TestDialogContext");dialog.show();}
}

分析:传入的Context是一个Service,相似上面传入ApplicationContext一样的后果,一样的原因。抛出例如以下异常:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an applicationat android.view.ViewRootImpl.setView(ViewRootImpl.java:566)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:272)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)at android.app.Dialog.show(Dialog.java:298)

至此通过我们平时使用最多的Dialog也验证了Dialog成功显示的必要条件,同一时候也让大家避免了再次使用Dialog不当出现异常的情况,或者出现相似异常后知道真实的背后原因是什么的问题。

能够看见。Dialog的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果】

4 Android应用PopWindow窗体加入显示机制源代码

PopWindow实质就是弹出式菜单。它与Dialag不同的地方是不会使依赖的Activity组件失去焦点(PopupWindow弹出后能够继续与依赖的Activity进行交互)。Dialog却不能这样。同一时候PopupWindow与Dialog还有一个不同点是PopupWindow是一个堵塞的对话框。假设你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow必须在某个事件中显示地或者是开启一个新线程去调用。

说这么多还是直接看代码吧。

4-1 PopWindow窗体源代码分析

根据PopWindow的使用,我们选择最经常使用的方式来分析。例如以下先看当中经常使用的一种构造函数:

public class PopupWindow {......//我们仅仅分析最经常使用的一种构造函数public PopupWindow(View contentView, int width, int height, boolean focusable) {if (contentView != null) {//获取mContext。contentView实质是View,View的mContext都是构造函数传入的,View又层级传递,所以终于这个mContext实质是Activity。!!

非常重要

mContext = contentView.getContext(); //获取Activity的getSystemService的WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } //进行一些Window类的成员变量初始化赋值操作 setContentView(contentView); setWidth(width); setHeight(height); setFocusable(focusable); } ...... }

能够看见,构造函数仅仅是初始化了一些变量,看完构造函数继续看下PopWindow的展示函数。例如以下:

    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {......//anchor是Activity中PopWindow准备依附的View,这个View的token实质也是Activity的Window中的token,也即Activity的token//第一步   初始化WindowManager.LayoutParamsWindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());//第二步preparePopup(p);......//第三步invokePopup(p);}

能够看见,当我们想将PopWindow展示在anchor的下方向(Z轴是在anchor的上面)旁边时经理了上面三步。我们一步一步来分析。先看第一步。源代码例如以下:

    private WindowManager.LayoutParams createPopupLayout(IBinder token) {//实例化一个默认的WindowManager.LayoutParams。当中type=TYPE_APPLICATIONWindowManager.LayoutParams p = new WindowManager.LayoutParams();//设置Gravityp.gravity = Gravity.START | Gravity.TOP;//设置宽高p.width = mLastWidth = mWidth;p.height = mLastHeight = mHeight;//根据背景设置formatif (mBackground != null) {p.format = mBackground.getOpacity();} else {p.format = PixelFormat.TRANSLUCENT;}//设置flagsp.flags = computeFlags(p.flags);//改动type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type类型为子窗体p.type = mWindowLayoutType;//设置token为Activity的tokenp.token = token;......return p;}

接着回到showAsDropDown方法看看第二步,例如以下源代码:

    private void preparePopup(WindowManager.LayoutParams p) {......//有无设置PopWindow的background差别if (mBackground != null) {......//假设有背景则创建一个PopupViewContainer对象的ViewGroupPopupViewContainer popupViewContainer = new PopupViewContainer(mContext);PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height);//把背景设置给PopupViewContainer的ViewGrouppopupViewContainer.setBackground(mBackground);//把我们构造函数传入的View加入到这个ViewGrouppopupViewContainer.addView(mContentView, listParams);//返回这个ViewGroupmPopupView = popupViewContainer;} else {//假设没有通过PopWindow的setBackgroundDrawable设置背景则直接赋值当前传入的View为PopWindow的ViewmPopupView = mContentView;}......}

能够看见preparePopup方法的作用就是推断设置View。假设有背景则会在传入的contentView外面包一层PopupViewContainer(实质是一个重写了事件处理的FrameLayout)之后作为mPopupView。假设没有背景则直接用contentView作为mPopupView。

我们再来看下这里的PopupViewContainer类。例如以下源代码:

    private class PopupViewContainer extends FrameLayout {......@Overrideprotected int[] onCreateDrawableState(int extraSpace) {......}@Overridepublic boolean dispatchKeyEvent(KeyEvent event) {......}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {return true;}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {......if(xxx) {dismiss();}......}@Overridepublic void sendAccessibilityEvent(int eventType) {......}}

能够看见,这个PopupViewContainer是一个PopWindow的内部私有类,它继承了FrameLayout。在当中重写了Key和Touch事件的分发处理逻辑。同一时候查阅PopupView能够发现,PopupView类自身没有重写Key和Touch事件的处理,所以假设没有将传入的View对象放入封装的ViewGroup中。则点击Back键或者PopWindow以外的区域PopWindow是不会消失的(事实上PopWindow中没有向Activity及Dialog一样new新的Window,所以不会有新的callback设置。也就没法处理事件消费了)。

接着继续回到showAsDropDown方法看看第三步。例如以下源代码:

    private void invokePopup(WindowManager.LayoutParams p) {if (mContext != null) {p.packageName = mContext.getPackageName();}mPopupView.setFitsSystemWindows(mLayoutInsetDecor);setLayoutDirectionFromAnchor();mWindowManager.addView(mPopupView, p);}

能够看见。这里使用了Activity的WindowManager将我们的PopWindow进行了显示。

到此能够发现。PopWindow的实质无非也是使用WindowManager的addView、updateViewLayout、removeView进行一些操作展示。与Dialog不同的地方是没有新new Window而已(也就没法设置callback。无法消费事件,也就是前面说的PopupWindow弹出后能够继续与依赖的Activity进行交互的原因)。

到此PopWindw的窗体载入显示机制就分析完成了,接下来进行总结与应用开发技巧提示。

4-2 PopWindow窗体源代码分析总结及应用开发技巧提示

通过上面分析能够发现总结例如以下图:

能够看见,PopWindow全然使用了Activity的Window与WindowManager,相对来说比較简单easy记理解。

再来看一个开发技巧:

假设设置了PopupWindow的background。则点击Back键或者点击PopupWindow以外的区域时PopupWindow就会dismiss;假设不设置PopupWindow的background。则点击Back键或者点击PopupWindow以外的区域PopupWindow不会消失。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果】

5 Android应用Toast窗体加入显示机制源代码

5-1 基础知识准备

在開始分析这几个窗体之前须要脑补一点东东,我们从应用层开发来直观脑补,这样以下分析源代码时就不蛋疼了。

例如以下是一个我们写的两个应用实现Service跨进程调用服务ADIL的样例,client调运远程Service的start与stop方法控制远程Service的操作。

Android系统中的应用程序都运行在各自的进程中,进程之间是无法直接交换数据的。可是Android为开发人员提供了AIDL跨进程调用Service的功能。事实上AIDL就相当于两方约定的一个规则而已。

先看下在Android Studio中AIDL开发的project文件夹结构,例如以下:

由于AIDL文件里不能出现訪问修饰符(如public),同一时候AIDL文件在两个项目中要全然一致并且仅仅支持基本类型。所以我们定义的AIDL文件例如以下:

ITestService.aidl

package io.github.yanbober.myapplication;interface ITestService {void start(int id);void stop(int id);
}

再来看下根据aidl文件自己主动生成的ITestService.java文件吧。例如以下:

/** This file is auto-generated.  DO NOT MODIFY.*/
package io.github.yanbober.myapplication;
public interface ITestService extends android.os.IInterface
{//Stub类是ITestService接口的内部静态抽象类,该类继承了Binder类public static abstract class Stub extends android.os.Binder implements io.github.yanbober.myapplication.ITestService{......//这是抽象静态Stub类中的asInterface方法,该方法负责将service返回至client的对象转换为ITestService.Stub//把远程Service的Binder对象传递进去,得到的是远程服务的本地代理public static io.github.yanbober.myapplication.ITestService asInterface(android.os.IBinder obj){......}......//远程服务的本地代理,也会继承自ITestServiceprivate static class Proxy implements io.github.yanbober.myapplication.ITestService{......@Overridepublic void start(int id) throws android.os.RemoteException{......}@Overridepublic void stop(int id) throws android.os.RemoteException{......}}......}//两个方法是aidl文件里定义的方法public void start(int id) throws android.os.RemoteException;public void stop(int id) throws android.os.RemoteException;
}

这就是自己主动生成的java文件,接下来我们看看服务端的Service源代码,例如以下:

//记得在AndroidManifet.xml中注冊Service的<action android:name="io.github.yanbober.myapplication.aidl" />public class TestService extends Service {private TestBinder mTestBinder;//该类继承ITestService.Stub类而不是Binder类,由于ITestService.Stub是Binder的子类//进程内的Service定义TestBinder内部类是继承Binder类public class TestBinder extends ITestService.Stub {@Overridepublic void start(int id) throws RemoteException {Log.i(null, "Server Service is start!");}@Overridepublic void stop(int id) throws RemoteException {Log.i(null, "Server Service is stop!");}}@Overridepublic IBinder onBind(Intent intent) {//返回Binderreturn mTestBinder;}@Overridepublic void onCreate() {super.onCreate();//实例化BindermTestBinder = new TestBinder();}
}

如今服务端App的代码已经OK,我们来看下client的代码。client首先也要像上面的project结构一样,把AIDL文件放好。接着在client使用远程服务端的Service代码例如以下:

public class MainActivity extends Activity {private static final String REMOT_SERVICE_ACTION = "io.github.yanbober.myapplication.aidl";private Button mStart, mStop;private ITestService mBinder;private ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//获得还有一个进程中的Service传递过来的IBinder对象//用IMyService.Stub.asInterface方法转换该对象mBinder = ITestService.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mStart = (Button) this.findViewById(R.id.start);mStop = (Button) this.findViewById(R.id.stop);mStart.setOnClickListener(clickListener);mStop.setOnClickListener(clickListener);//绑定远程跨进程ServicebindService(new Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);}@Overrideprotected void onDestroy() {super.onDestroy();//取消绑定远程跨进程ServiceunbindService(connection);}private View.OnClickListener clickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {调用远程Service中的start与stop方法switch (v.getId()) {case R.id.start:try {mBinder.start(0x110);} catch (RemoteException e) {e.printStackTrace();}break;case R.id.stop:try {mBinder.stop(0x120);} catch (RemoteException e) {e.printStackTrace();}break;}}};
}

到此你相应用层通过AIDL使用远程Service的形式已经非常熟悉了,至于实质的通信使用Binder的机制我们后面会写文章一步一步往下分析。到此的准备知识已经足够用来理解以下我们的源代码分析了。

5-2 Toast窗体源代码分析

我们经常使用的Toast窗体事实上和前面分析的Activity、Dialog、PopWindow都是不同的。由于它和输入法、墙纸相似。都是系统窗体。

我们还是依照最经常使用的方式来分析源代码吧。

我们先看下Toast的静态makeText方法吧,例如以下:

    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {//new一个Toast对象Toast result = new Toast(context);//获取前面有篇文章分析的LayoutInflaterLayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//载入解析Toast的布局。实质transient_notification.xml是一个LinearLayout中套了一个@android:id/message的TextView而已View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);//取出布局中的TextViewTextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);//把我们的文字设置到TextView上tv.setText(text);//设置一些属性result.mNextView = v;result.mDuration = duration;//返回新建的Toastreturn result;}

能够看见。这种方法构造了一个Toast,然后把要显示的文本放到这个View的TextView中,然后初始化相关属性后返回这个新的Toast对象。

当我们有了这个Toast对象之后。能够通过show方法来显示出来。例如以下看下show方法源代码:

    public void show() {......//通过AIDL(Binder)通信拿到NotificationManagerService的服务訪问接口。当前Toast类相当于上面样例的client!

!。相当重要!!!

INotificationManager service = getService(); String pkg = mContext.getOpPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { //把TN对象和一些參数传递到远程NotificationManagerService中去 service.enqueueToast(pkg, tn, mDuration); } catch (RemoteException e) { // Empty } }

我们看看show方法中调运的getService方法,例如以下:

    //远程NotificationManagerService的服务訪问接口private static INotificationManager sService;static private INotificationManager getService() {//单例模式if (sService != null) {return sService;}//通过AIDL(Binder)通信拿到NotificationManagerService的服务訪问接口sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));return sService;}

通过上面我们的基础脑补实例你也能看懂这个getService方法了吧。那接着我们来看mTN吧,好像mTN在Toast的构造函数里见过一眼,我们来看看。例如以下:

    public Toast(Context context) {mContext = context;mTN = new TN();mTN.mY = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.toast_y_offset);mTN.mGravity = context.getResources().getInteger(com.android.internal.R.integer.config_toastDefaultGravity);}

能够看见mTN确实是在构造函数中实例化的,那我们就来看看这个TN类,例如以下:

    //相似于上面样例的服务端实例化的Service内部类Binderprivate static class TN extends ITransientNotification.Stub {......//实现了AIDL的show与hide方法@Overridepublic void show() {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.post(mShow);}@Overridepublic void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide);}......}

看见没有,TN是Toast内部的一个私有静态类,继承自ITransientNotification.Stub。你这时指定好奇ITransientNotification.Stub是个啥玩意,对吧?事实上你在上面的脑补实例中见过它的,他出如今服务端实现的Service中,就是一个Binder对象。也就是对一个aidl文件的实现而已,我们看下这个ITransientNotification.aidl文件。例如以下:

package android.app;/** @hide */
oneway interface ITransientNotification {void show();void hide();
}

看见没有,和我们上面的样例非常相似吧。

再回到上面分析的show()方法中能够看到。我们的Toast是传给远程的NotificationManagerService管理的,为了NotificationManagerService回到我们的应用程序(回调)。我们须要告诉NotificationManagerService我们当前程序的Binder引用是什么(也就是TN)。是不是觉得和上面样例有些不同,这里感觉Toast又充当client。又充当服务端的样子,实质就是一个回调过程而已。

继续来看Toast中的show方法的service.enqueueToast(pkg, tn, mDuration);语句。service实质是远程的NotificationManagerService。所以enqueueToast方法就是NotificationManagerService类的。例如以下:

    private final IBinder mService = new INotificationManager.Stub() {// Toasts// ============================================================================@Overridepublic void enqueueToast(String pkg, ITransientNotification callback, int duration){......synchronized (mToastQueue) {int callingPid = Binder.getCallingPid();long callingId = Binder.clearCallingIdentity();try {ToastRecord record;//查看该Toast是否已经在队列当中int index = indexOfToastLocked(pkg, callback);// If it's already in the queue, we update it in place, we don't// move it to the end of the queue.//凝视说了,已经存在则直接取出updateif (index >= 0) {record = mToastQueue.get(index);record.update(duration);} else {// Limit the number of toasts that any given package except the android// package can enqueue.  Prevents DOS attacks and deals with leaks.......//将Toast封装成ToastRecord对象,放入mToastQueue中record = new ToastRecord(callingPid, pkg, callback, duration);//把他加入到ToastQueue队列中mToastQueue.add(record);index = mToastQueue.size() - 1;//将当前Toast所在的进程设置为前台进程keepProcessAliveLocked(callingPid);}//假设index为0,说明当前入队的Toast在队头,须要调用showNextToastLocked方法直接显示if (index == 0) {showNextToastLocked();}} finally {Binder.restoreCallingIdentity(callingId);}}}}

继续看下该方法中调运的showNextToastLocked方法,例如以下:

    void showNextToastLocked() {//取出ToastQueue中队列最前面的ToastRecordToastRecord record = mToastQueue.get(0);while (record != null) {try {//Toast类中实现的ITransientNotification.Stub的Binder接口TN,调运了那个类的show方法record.callback.show();scheduleTimeoutLocked(record);return;} catch (RemoteException e) {......}}}

继续先看下该方法中调运的scheduleTimeoutLocked方法。例如以下:

    private void scheduleTimeoutLocked(ToastRecord r){//移除上一条消息mHandler.removeCallbacksAndMessages(r);//根据Toast传入的duration參数LENGTH_LONG=1来推断决定多久发送消息Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);long delay = r.duration == Toast.LENGTH_LONG ?

LONG_DELAY : SHORT_DELAY; //根据设置的MESSAGE_TIMEOUT后发送消息 mHandler.sendMessageDelayed(m, delay); }

能够看见这里先回调了Toast的TN的show,以下timeout可能就是hide了。接着还在该类的mHandler处理了这条消息。然后调运了例如以下处理方法:

    private void handleTimeout(ToastRecord record){......synchronized (mToastQueue) {int index = indexOfToastLocked(record.pkg, record.callback);if (index >= 0) {cancelToastLocked(index);}}}

我们继续看cancelToastLocked方法,例如以下:

    void cancelToastLocked(int index) {ToastRecord record = mToastQueue.get(index);try {//回调Toast的TN中实现的hide方法record.callback.hide();} catch (RemoteException e) {......}//从队列移除当前显示的ToastmToastQueue.remove(index);keepProcessAliveLocked(record.pid);if (mToastQueue.size() > 0) {//假设当前的Toast显示完成队列里还有其它的Toast则显示其它的ToastshowNextToastLocked();}}

到此能够发现,Toast的远程管理NotificationManagerService类的处理实质是通过Handler发送延时消息显示取消Toast的,并且在远程NotificationManagerService类中又远程回调了Toast的TN类实现的show与hide方法。

如今我们就回到Toast的TN类再看看这个show与hide方法,例如以下:

```javaprivate static class TN extends ITransientNotification.Stub {......//仅仅是实例化了一个Handler。非常重要!!!

!。!

!!

final Handler mHandler = new Handler(); ...... final Runnable mShow = new Runnable() { @Override public void run() { handleShow(); } }; final Runnable mHide = new Runnable() { @Override public void run() { handleHide(); // Don't do this in handleHide() because it is also invoked by handleShow() mNextView = null; } }; ...... //实现了AIDL的show与hide方法 @Override public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @Override public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } ...... }

能够看见。这里实现aidl接口的方法实质是通过handler的post来运行的一个方法,而这个Handler仅仅仅仅是new了一下。也就是说。假设我们写APP时使用Toast在子线程中则须要自行准备Looper对象。仅仅有主线程Activity创建时帮忙准备了Looper(关于Handler与Looper假设整不明白请阅读《Android异步消息处理机制具体解释及源代码分析》)。

那我们重点关注一下handleShow与handleHide方法。例如以下:

        public void handleShow() {if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView+ " mNextView=" + mNextView);if (mView != mNextView) {// remove the old view if necessary//假设有必要就通过WindowManager的remove删掉旧的handleHide();mView = mNextView;Context context = mView.getContext().getApplicationContext();String packageName = mView.getContext().getOpPackageName();if (context == null) {context = mView.getContext();}//通过得到的context(通常是ContextImpl的context)获取WindowManager对象(上一篇文章分析的单例的WindowManager)mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);......//在把Toast的View加入之前发现Toast的View已经被加入过(有partent)则删掉if (mView.getParent() != null) {......mWM.removeView(mView);}......//把Toast的View加入到窗体,当中mParams.type在构造函数中赋值为TYPE_TOAST!!!。!

特别重要

mWM.addView(mView, mParams); ...... } }

        public void handleHide() {if (mView != null) {// note: checking parent() just to make sure the view has// been added...  i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.//凝视说得非常清楚了,不解释,就是removeif (mView.getParent() != null) {mWM.removeView(mView);}mView = null;}}

到此Toast的窗体加入原理就分析完成了,接下来我们进行总结。

5-3 Toast窗体源代码分析总结及应用开发技巧

经过上面的分析我们总结例如以下:

通过上面分析及上图直观描写叙述能够发现,之所以Toast的显示交由远程的NotificationManagerService管理是由于Toast是每一个应用程序都会弹出的,并且位置和UI风格都差点儿相同。所以假设我们不统一管理就会出现覆盖叠加现象,同一时候导致不好控制。所以Google把Toast设计成为了系统级的窗体类型,由NotificationManagerService统一队列管理。

在我们开发应用程序时使用Toast注意事项:

  1. 通过分析TN类的handler能够发现,假设想在非UI线程使用Toast须要自行声明Looper。否则运行会抛出Looper相关的异常;UI线程不须要,由于系统已经帮忙声明。

  2. 在使用Toast时context參数尽量使用getApplicationContext()。能够有效的防止静态引用导致的内存泄漏。

  3. 有时候我们会发现Toast弹出过多就会延迟显示。由于上面源代码分析能够看见Toast.makeText是一个静态工厂方法,每次调用这种方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以假设我们不每次都产生一个新的Toast对象(使用单例来处理)就不须要排队。也就能及时更新了。

6 Android应用Activity、Dialog、PopWindow、Toast窗体显示机制总结

能够看见上面不管Acitivty、Dialog、PopWindow、Toast的实质事实上都是例如以下接口提供的方法操作:

public interface ViewManager
{public void addView(View view, ViewGroup.LayoutParams params);public void updateViewLayout(View view, ViewGroup.LayoutParams params);public void removeView(View view);
}

整个应用各种窗体的显示都离不开这三个方法而已。仅仅是token及type与Window是否共用的问题。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重劳动成果】

Android应用Activity、Dialog、PopWindow、Toast窗体加入机制及源代码分析相关推荐

  1. 《Android深度探索(卷2):系统应用源代码分析与ROM定制》——第6章,第6.4节分析第一个Android系统应用:计算器...

    本节书摘来自异步社区<Android深度探索(卷2):系统应用源代码分析与ROM定制>一书中的第6章,第6.4节分析第一个Android系统应用:计算器,作者 李宁,更多章节内容可以访问云 ...

  2. Android提醒:Dialog,Toast,Snackbar

    参考:http://blog.csdn.net/sinyu890807/article/details/51336415 Dialog 1.确定取消对话框 AlertDialog dialog = n ...

  3. Android应用程序绑定服务(bindService)的过程源代码分析

    Android应用程序组件Service与Activity一样,既可以在新的进程中启动,也可以在应用程序进程内部启动:前面我们已经分析了在新的进程中启动Service的过程,本文将要介绍在应用程序内部 ...

  4. Android系统默认Home应用程序 Launcher 的启动过程源代码分析

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 在前面一 ...

  5. Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析

    1  背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与LayoutInflater加载解析机制源码分析>, 然后有人在文章下面评论和微博 ...

  6. (转) Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析

    转载[工匠若水 http://blog.csdn.net/yanbober ] 1 背景 之所以写这一篇博客的原因是因为之前有写过一篇<Android应用setContentView与Layou ...

  7. android 开发零起步学习笔记(二十二):ANDROID应用ACTIVITY、DIALOG、POPWINDOW、TOAST窗口添加机制及源码分析(一)

    原文:http://www.cnblogs.com/shanzei/p/4654817.html 第一部分: ANDROID应用ACTIVITY.DIALOG.POPWINDOW.TOAST窗口添加机 ...

  8. Android ContextThemeWrapper cannot be cast to android.app.Activity

    java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activit ...

  9. android 窗口监听按键,Android编程实现Dialog窗体监听的方法

    本文实例讲述了Android编程实现Dialog窗体监听的方法.分享给大家供大家参考,具体如下: 今天做了一个Dialong窗体监听包括窗体内的xml监听. 效果图: test.class代码 pac ...

最新文章

  1. DPI — 业务识别技术
  2. 阚凯力:四核终端发展需软硬件相互促进
  3. java如何保证redis设置过期时间的原子性_2020年4月Redis面试题和答案整理
  4. Linux Mysql 1130错误解决
  5. 如何删除本地分支_如何上传项目到GitHub
  6. SpringMVC项目配置全过程详解
  7. Robot Application Builder
  8. 如何在Unity3d平台下低延迟播放RTMP或RTSP流
  9. idea进行断点快捷键
  10. 1014. 最佳观光组合
  11. 【重点】剑指offer——面试题36:数组中的逆序对
  12. WebRTC之gn与ninja(十三)
  13. 小程序开发解决方案_小程序开发方案怎么写
  14. beyond compare 对class文件反编译及比较
  15. 卡巴斯基正版半年注册码申请
  16. 人工智能剥夺就业岗位?不妨听听马斯克是如何建议的
  17. vue 脚手架启动html,vue脚手架项目创建步骤详解
  18. 蛙蛙推荐:蛙蛙教你发明一种新语言之二--代码生成
  19. hadoop+Kylin服务器搭建教程
  20. 如何在win7下装ubuntu(硬盘版安装)

热门文章

  1. 3.27模拟赛 sutoringu(后缀数组)
  2. 解决Ubuntu16.04虚拟机窗口全屏问题
  3. 解决waitfor()阻塞问题
  4. java堆内存 和栈内存
  5. Android 7.0 Nougat介绍
  6. Oracle日期转换为周,月,季度,半年,年
  7. 【公告】百度应用开放平台关于工具/生活类应用优化展现试验的公告
  8. WinHand.cpp Line 199 错误 WinHand.cpp Line 218 错误
  9. VS2010/MFC编程入门之四(MFC应用程序框架分析)
  10. uni-app实现传值路径乱码的问题