Android窗口系统第二篇---Window的添加过程

以前写过客户端Window的创建过程,大概是这样子的。我们一开始从Thread中的handleLaunchActivity方法开始分析,首先加载Activity的字节码文件,利用反射的方式创建一个Activity对象,调用Activity对象的attach方法,在attach方法中,创建系统需要的Window并为设置回调,这个回调定义在Window之中,由Activity实现,当Window的状态发生变化的时候,就会回调Activity实现的这些回调方法。调用attach方法之后,Window被创建完成,这时候需要关联我们的视图,在handleLaunchActivity中的attach执行之后就要执行handleLaunchActivity中的callActivityOnCreate,在onCreate中我们会调用setContentView方法。通过setContentView,创建了Activity的顶级View---DecorView,DecorView的内容栏(mContentParent)用来显示我们的布局,这只是添加的过程,还要有一个显示的过程,显示的过程就要调用ActivityThead中handleLaunchActivity中的handleResumeActivity方法了,最后会调用makeVisible方法,把这个DecorView显示出来。

那么今天所讨论的是话题主要有,创建后的Activity的窗口是怎么添加的,WindowManagerService是如何感知Activity窗口添加的?系统中有很多应用,每个应用有多个Activity,一个Activity对应一个Window,WindowManagerService是怎么管理的?本文基于Android7.0源码。

一、从ActivityThread#handleResumeActivity方法说起

final void handleResumeActivity(IBinder token,
           boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
       ActivityClientRecord r = mActivities.get(token);
       ....
       if (r != null) {
           final Activity a = r.activity;
          ....
           //Activity的Window没有被添加过并且Activity没有finish和需要设置成可见
           if (r.window == null && !a.mFinished && willBeVisible) {
              //对Actiivty成员变量window赋值
               r.window = r.activity.getWindow();
               //获取Window的DecorView
               View decor = r.window.getDecorView();
               //将DecorView设置成可见
               decor.setVisibility(View.INVISIBLE);
               ViewManager wm = a.getWindowManager();
               WindowManager.LayoutParams l = r.window.getAttributes();
               a.mDecor = decor;
               l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
              ...
               if (a.mVisibleFromClient && !a.mWindowAdded) {
                   a.mWindowAdded = true;
                   //调用ViewManager的方法添加decor
                   wm.addView(decor, l);
               }
         ...
   }

ViewManager定义 了操作View的三大方法,addView,updateViewLayout,removeView;比如ViewGroup就实现了这个接口。

public interface ViewManager{
   /**
    * Assign the passed LayoutParams to the passed View and add the view to the window.
    * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
    * errors, such as adding a second view to a window without removing the first view.
    * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
    * secondary {@link Display} and the specified display can't be found
    * (see {@link android.app.Presentation}).
    * @param view The view to be added to this window.
    * @param params The LayoutParams to assign to view.
    */
   public void addView(View view, ViewGroup.LayoutParams params);
   public void updateViewLayout(View view, ViewGroup.LayoutParams params);
   public void removeView(View view);
}

但是这个地方,我们要看的ViewManager的实现类是WindowManagerImpl,所以调用的是WindowManagerImpl的addView方法,


   private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

   @Override
   public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       applyDefaultToken(params);
       mGlobal.addView(view, params, mDisplay, mParentWindow);
   }

WindowManagerImpl紧接就把这个活交给了WindowManagerGlobal,所以要去看WindowManagerGlobal的addView方法。WindowManagerGlobal是用来管理Window的全局类,它里面维护了几个全局的列表。

//存储所有Window所对应的View
private final ArrayList<View> mViews = new ArrayList<View>();

//存储所有Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

//存储所有Window对应的布局参数
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

//存储所有将要被删除的View,即Window
private final ArraySet<View> mDyingViews = new ArraySet<View>();

下面是addView方法,很容易懂,主要做了如下几个事情

public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {

     //第一件事情:检查参数是否合法,如果是子Window,还要调整布局参数
       if (view == null) {
           throw new IllegalArgumentException("view must not be null");
       }
       if (display == null) {
           throw new IllegalArgumentException("display must not be null");
       }
       if (!(params instanceof WindowManager.LayoutParams)) {
           throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
       }

       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
       if (parentWindow != null) {
           parentWindow.adjustLayoutParamsForSubWindow(wparams);
       } else {
           // If there's no parent and we're running on L or above (or in the
           // system context), assume we want hardware acceleration.
           final Context context = view.getContext();
           if (context != null
                   && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
               wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
           }
       }

       ViewRootImpl root;
       View panelParentView = null;

       synchronized (mLock) {
         .....

        //第二件事情   创建ViewRootImpl,将View添加到列表中,这里每次都会new一个对象,所以说调用一次addView,就会有一个ViewRootImpl。
           root = new ViewRootImpl(view.getContext(), display);

           view.setLayoutParams(wparams);

           mViews.add(view);
           mRoots.add(root);
           mParams.add(wparams);
       }

       // do this last because it fires off messages to start doing things
       try {

       //第三件事情 :使用ViewRootImpl对象,调用setView
           root.setView(view, wparams, panelParentView);
       } catch (RuntimeException e) {
           // BadTokenException or InvalidDisplayException, clean up.
           synchronized (mLock) {
               final int index = findViewLocked(view, false);
               if (index >= 0) {
                   removeViewLocked(index, true);
               }
           }
           throw e;
       }
   }

ViewRootImpl是是个什么呢?

  • 简单来说,ViewRoot相当于是MVC模型中的Controller,它有以下职责:1. 负责为应用程序窗口视图创建Surface。 2. 配合WindowManagerService来管理系统的应用程序窗口。 3. 负责管理、布局和渲染应用程序窗口视图的UI。*我们在看ViewRootImpl的setView 方法。
 */
   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
       synchronized (this) {
           if (mView == null) {
               mView = view;

               ......
               mAdded = true;
               int res; /* = WindowManagerImpl.ADD_OKAY; */

               //1、调用requestLayout方法进行绘制
               requestLayout();
               ....
               try {
                   mOrigWindowType = mWindowAttributes.type;
                   mAttachInfo.mRecomputeGlobalAttributes = true;
                   collectViewAttributes();
                   //2、调用mWindowSession添加View
                   res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                           getHostVisibility(), mDisplay.getDisplayId(),
                           mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
               } catch (RemoteException e) {
                   mAdded = false;
                   mView = null;
                   mAttachInfo.mRootView = null;
                   mInputChannel = null;
                   mFallbackEventHandler.setView(null);
                   unscheduleTraversals();
                   setAccessibilityFocus(null, null);
                   throw new RuntimeException("Adding window failed", e);
               } finally {
                   if (restore) {
                       attrs.restore();
                   }
               }
              ......
       }
   }

看看requestLayout方法,首先检验是不是主线程在修改UI,然后 调用scheduleTraversals,在scheduleTraversals开始执行了,才会有我们熟悉的onDraw,onLayout,onMeasure。

  @Override
   public void requestLayout() {
       if (!mHandlingLayoutInLayoutRequest) {
           checkThread();
           mLayoutRequested = true;
           scheduleTraversals();http://www.cnblogs.com/GMCisMarkdownCraftsman/p/6117129.html
       }
   }


  void checkThread() {
       if (mThread != Thread.currentThread()) {
           throw new CalledFromWrongThreadException(
                   "Only the original thread that created a view hierarchy can touch its views.");
       }
   }

二、APP端与WMS的IPC过程

继续回到setView方法,重点理解这行代码的意思。

      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                           getHostVisibility(), mDisplay.getDisplayId(),
                           mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);

这行代码大部分人都知道这个是与WMS做IPC通信的,但是具体是个什么过程呢?我们可以将这个过程和AMS与应用进程IPC的过程做对比。

AMS与应用进程IPC

ActivityManager类内部调用ActivityManagerNative的getDefault函数得到一个ActivityManagerProxy对象,AMS继承ActivityManagerNative,通过它可与AMS通信。所以说,ActivityManagerProxy(是AMS在客户端进程的一个代理,通过AMP里面的方法请求AMS。那么客户端进程在AMS的代理呢?这个代理就是ApplicationThreadProxy,如果AMS要通知Activity,那么就是使用ApplicationThreadProxy。现在看APP端和WMS的IPC。

App与WMS的连接

App与WMS的连接,首先会建立一个Session到WMS,之后就会通过IWindowSession接口与WMS中的Session直接通信,IWindowSession类是什么?它指向了一个实现了IWindowSession接口的Session代理对象。当应用程序进程启动第一个Activity组件的时候,它就会请求WMS服务发送一个建立连接的Binder进程间通信请求。WMS服务接收到这个请求之后,就会在内部创建一个类型为Session的Binder本地对象,并且将这个Binder本地对象返回给应用程序进程,App就会得到一个Session代理对象,并且保存在ViewRootImpl类的成员变量mWindowSession中。大概代码如下。

public ViewRootImpl(Context context, Display display) {
     ....
       //创建了WindowSession对象
       mWindowSession = WindowManagerGlobal.getWindowSession();
      //也创建了W的对象mWindow
       mWindow = new W(this)
      ....
}

private static IWindowSession sWindowSession;

public static IWindowSession getWindowSession() {
       synchronized (WindowManagerGlobal.class) {
           if (sWindowSession == null) {
               try {
                   InputMethodManager imm = InputMethodManager.getInstance();
                   IWindowManager windowManager = getWindowManagerService();
           //调用WMS的openSession创建一个Session对象
                   sWindowSession = windowManager.openSession(
                           new IWindowSessionCallback.Stub() {
                               @Override
                               public void onAnimatorScaleChanged(float scale) {
                                   ValueAnimator.setDurationScale(scale);
                               }
                           },
                           imm.getClient(), imm.getInputContext());
               } catch (RemoteException e) {
                   throw e.rethrowFromSystemServer();
               }
           }
           return sWindowSession;
       }
   }
 com.android.server.wm.WindowManagerService.java

 @Override
   public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) {
       if (client == null) throw new IllegalArgumentException("null client");
       if (inputContext == null) throw new IllegalArgumentException("null inputContext");
       Session session = new Session(this, callback, client, inputContext);
       return session;
  }

当App有了这个远端Session代理对象mWindowSession之后,所有向WMS的请求都通过mWindowSession来进行。举例来说:ViewRootImpl要添加窗口,就使用mWindowSession代理对象的addToDisplay方法调用到远端Session对象的addToDisplay方法。

mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                           getHostVisibility(), mDisplay.getDisplayId(),
                           mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                           mAttachInfo.mOutsets, mInputChannel);

远端Session对象收到这个请求后,转接给WMS。

final class Session extends IWindowSession.Stub  implements IBinder.DeathRecipient {
  final WindowManagerService mService; 
  .....
  @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
           int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
           Rect outOutsets, InputChannel outInputChannel) {
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                  outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
}

注意到addToDisplay中的参数mWindow是一个W对象,W对象是什么?ViewRootImpl::W:用于向WMS提供接口,让WMS控制App端的窗口。它可看作是个代理,很多时候会调用ViewRootImpl中的功能。

W的声明中有两个成员变量:mViewAncestor和mWindowSession,它一头连着App端的ViewRootImpl,一头连着WMS中的Session,且实现了IWindow的接口。意味着它是App和WMS的桥梁,是WMS用来回调App端,让ViewRootImpl做事用的。

举例来说,dispatchAppVisibility()的流程就是经过它来完成的:WMS ->ViewRootImpl::W->ViewRootHandler->handleAppVisibility()->scheduleTraversals()。

类似这种话结构的还有,PhoneWindow::DecorView,这种设计使得系统满足最小隔离原则,Client端该用到哪些接口就暴露哪些接口。因为这种类的函数都是跑在Binder线程中的,所以其中不能调用非线程安全的函数,也不能直接操作UI控件,所以一般都是往主线程消息队列里丢一个消息让其异步执行。

三、addWindow方法解读

addWindow方法比较长,粗略的分成下面9点来解释。

public int addWindow(Session session, IWindow client, int seq,
           WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
           Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
           InputChannel outInputChannel) {
1、窗口添加权限校验
2、检查特殊窗口attr.token和attr.type的一致性
3、创建窗口对象
4、调用adjustWindowParamsLw对窗口参数进行调整
5、创建pipe,用于输入消息的传递
6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中
7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow 
8、添加窗口到Windows列表,确定窗口的位置
9、窗口已经添加了,调用assignLayersLocked调整一下层值
}

1、窗口添加权限校验

   int[] appOp = new int[1];
       int res = mPolicy.checkAddPermission(attrs, appOp);
       if (res != WindowManagerGlobal.ADD_OKAY) {
           return res;
       }

从参数attrs中可以取出窗口的type,对type类型除了Toast窗口、屏保窗口、输入法窗口、墙纸窗口、语音交互窗口等少数几个类型窗口不需要进行权限判断外,其余的窗口都需要检查是否有android.Manifest.permission.SYSTEM_ALERT_WINDOW权限或者 android.Manifest.permission.INTERNAL_SYSTEM_WINDOW权限。

2、检查特殊窗口attr.token和attr.type的一致性

synchronized(mWindowMap) {
           //屏幕没有准备好,不给添加窗口
           if (!mDisplayReady) {
               throw new IllegalStateException("Display has not been initialialized");
           }
           //获取当前窗口需要添加在哪一个屏幕上
           final DisplayContent displayContent = getDisplayContentLocked(displayId);
           if (displayContent == null) {
               Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                       + displayId + ".  Aborting.");
               return WindowManagerGlobal.ADD_INVALID_DISPLAY;
           }
           //该窗口有没有权限在这个屏幕上添加
           if (!displayContent.hasAccess(session.mUid)) {
               Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                       + "does not have access: " + displayId + ".  Aborting.");
               return WindowManagerGlobal.ADD_INVALID_DISPLAY;
           }
           //该窗口是否已经添加过,WMS会把添加的窗口Token保存在mWindowMap中
           if (mWindowMap.containsKey(client.asBinder())) {
               Slog.w(TAG_WM, "Window " + client + " is already added");
               return WindowManagerGlobal.ADD_DUPLICATE_ADD;
           }
          //如果窗口类型是子窗口
           if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
         //把这个子窗口的父窗口取出来
               attachedWindow = windowForClientLocked(null, attrs.token, false);
               //父窗口如果为空,就会return,因为子窗口需要依赖一个父窗口存在
               if (attachedWindow == null) {
                   Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
               }
               //父窗口不为null,但是父窗口的类型也是一个子窗口,也会return
               if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                       && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                   Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                           + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
               }
           }
           //这个type表示该窗口是要添加到虚拟设备上的,但是该设备却不是虚拟的,也要返回
           if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
               Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
               return WindowManagerGlobal.ADD_PERMISSION_DENIED;
           }
         
           boolean addToken = false;
           //从mTokenMap取出WindowToken,WindowToken是窗口分组的标志,因为addWindow方法调用之前,AMS等服务会提前向WMS注册一个token到mTokenMap,所以这里一般是可以取到的,也有些情况是取不到的,下面分析。
           WindowToken token = mTokenMap.get(attrs.token);
           AppWindowToken atoken = null;
           boolean addToastWindowRequiresToken = false;

           if (token == null) {
               //如果这个窗口是应用程序窗口,上面取出的token为null,需要返回
               if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                   Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
                //如果这个窗口是输出法窗口,上面取出的token为null,需要返回
               if (type == TYPE_INPUT_METHOD) {
                   Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
                //如果这个窗口是语音交互窗口,上面取出的token为null,需要返回
               if (type == TYPE_VOICE_INTERACTION) {
                   Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
          //如果这个窗口是墙纸窗口,上面取出的token为null,需要返回
               if (type == TYPE_WALLPAPER) {
                   Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
         //如果这个窗口是屏保窗口,上面取出的token为null,需要返回
               if (type == TYPE_DREAM) {
                   Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
         //如果这个窗口QS_DIALOG,上面取出的token为null,需要返回
               if (type == TYPE_QS_DIALOG) {
                   Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                         + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
         //如果这个窗口TYPE_ACCESSIBILITY_OVERLAY类型窗口,上面取出的token为null,需要返回
               if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                   Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                           + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
               if (type == TYPE_TOAST) {
                   // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                   if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                           attachedWindow)) {
                       Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                               + attrs.token + ".  Aborting.");
                       return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                   }
               }
               //如果不是上面这些类型窗口,并且从mWindowMap中又不能取出对应的token的话,
          这里会隐士的创建一个第四个参数等于false,代表隐士创建,隐士创建的token在窗口销毁的时候,是不需要移除的
               token = new WindowToken(this, attrs.token, -1, false);
               addToken = true;
             //上面取出的窗口不为null,并且type是应用类型的窗口
           } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
         //取出appWindowToken,appWindowToken是WindowToken的子类,一个appWindowToken一般表示这个窗口是一个Activity窗口
               atoken = token.appWindowToken;
               //取出atoken为null的话,不能添加,窗口的添加是必须有一个token的
               if (atoken == null) {
                   Slog.w(TAG_WM, "Attempted to add window with non-application token "
                         + token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
               } else if (atoken.removed) {
                   Slog.w(TAG_WM, "Attempted to add window with exiting application token "
                         + token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_APP_EXITING;
               }
                 //如果这个窗口的类型是一个启动窗口的话,atoken的firstWindowDrawn等于true,不需要添加
               if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                   // No need for this guy!
                   if (DEBUG_STARTING_WINDOW || localLOGV) Slog.v(
                           TAG_WM, "**** NO NEED TO START: " + attrs.getTitle());
                   return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
               }
           } else if (type == TYPE_INPUT_METHOD) {
                 //窗口的类型是输入法窗口,但是它的token不是输入法类型的token,也要返回,不给添加,下面几个是类似的
               if (token.windowType != TYPE_INPUT_METHOD) {
                   Slog.w(TAG_WM, "Attempted to add input method window with bad token "
                           + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_VOICE_INTERACTION) {
               if (token.windowType != TYPE_VOICE_INTERACTION) {
                   Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
                           + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_WALLPAPER) {
               if (token.windowType != TYPE_WALLPAPER) {
                   Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
                           + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_DREAM) {
               if (token.windowType != TYPE_DREAM) {
                   Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
                           + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
               if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                   Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
                           + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_TOAST) {
               // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
               addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
                       callingUid, attachedWindow);
               if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
                   Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
                           + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (type == TYPE_QS_DIALOG) {
               if (token.windowType != TYPE_QS_DIALOG) {
                   Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
                           + attrs.token + ".  Aborting.");
                   return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
               }
           } else if (token.appWindowToken != null) {
               Slog.w(TAG_WM, "Non-null appWindowToken for system window of type=" + type);
               // It is not valid to use an app token with other system types; we will
               // instead make a new token for it (as if null had been passed in for the token).
               attrs.token = null;
               token = new WindowToken(this, null, -1, false);
               addToken = true;
           }
   .......
}

3、创建窗口对象

WindowState win = new WindowState(this, session, client, token,
                   attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);

WindowState是WMS中真正的窗口对象

4、调用PhoneWindowManager的adjustWindowParamsLw调整布局参数

public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
       switch (attrs.type) {
           case TYPE_SYSTEM_OVERLAY:
           case TYPE_SECURE_SYSTEM_OVERLAY:            
               attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                       | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
               attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
               break;
           case TYPE_STATUS_BAR:
               if (mKeyguardHidden) {
                   attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
                   attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
               }
               break;

           case TYPE_SCREENSHOT:
               attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
               break;

           case TYPE_TOAST:
               if (attrs.hideTimeoutMilliseconds < 0
                       || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
                   attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
               }
               attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
               break;
       }

       if (attrs.type != TYPE_STATUS_BAR) {
           // The status bar is the only window allowed to exhibit keyguard behavior.
           attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
       }

       if (ActivityManager.isHighEndGfx()) {
           if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
               attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
           }
           final boolean forceWindowDrawsStatusBarBackground =
                   (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
                           != 0;
           if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                   || forceWindowDrawsStatusBarBackground
                           && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
               attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
           }
       }
   }

这段代码主要调整状态栏等几个特殊窗口的FLAG,比如不让它获取用户焦点,不让它响应触摸消息等。

5、创建pipe,用于输入消息的传递

      final boolean openInputChannels = (outInputChannel != null
                   && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
           if  (openInputChannels) {
               win.openInputChannel(outInputChannel);
           }

窗口需要接收事件,所以需要向InputManagerService注册InputChannel。

6、调用窗口的attach,初始化Surface相关的变量,将窗口win放到mWindowMap中

 if (addToken) {
      mTokenMap.put(attrs.token, token);
 }
  win.attach();
  mWindowMap.put(client.asBinder(), win);
 void attach() {
       if (WindowManagerService.localLOGV) Slog.v(
           TAG, "Attaching " + this + " token=" + mToken
           + ", list=" + mToken.windows);
       mSession.windowAddedLocked();
   }
 void windowAddedLocked() {
       if (mSurfaceSession == null) {
           if (WindowManagerService.localLOGV) Slog.v(
               TAG_WM, "First window added to " + this + ", creating SurfaceSession");
           //创建窗口所对应的SurfaceSession
           mSurfaceSession = new SurfaceSession();
           if (SHOW_TRANSACTIONS) Slog.i(
                   TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
           mService.mSessions.add(this);
           if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
               mService.dispatchNewAnimatorScaleLocked(this);
           }
       }
       mNumWindow++;
   }
7、如果type == TYPE_APPLICATION_STARTING ,说明这个是启动窗口,把win赋值给token.appWindowToken.startingWindow
  if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
      token.appWindowToken.startingWindow = win;
       if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + token.appWindowToken
                       + " startingWindow=" + win);
  }
8、添加窗口到Windows列表,确定窗口的位置
     if (type == TYPE_INPUT_METHOD) {
               win.mGivenInsetsPending = true;
               mInputMethodWindow = win;
               addInputMethodWindowToListLocked(win);
               imMayMove = false;
           } else if (type == TYPE_INPUT_METHOD_DIALOG) {
               mInputMethodDialogs.add(win);
               addWindowToListInOrderLocked(win, true);
               moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
               imMayMove = false;
           } else {
               addWindowToListInOrderLocked(win, true);
               if (type == TYPE_WALLPAPER) {
                   mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
                   displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
               } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                   displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
               } else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) {
                   // If there is currently a wallpaper being shown, and
                   // the base layer of the new window is below the current
                   // layer of the target window, then adjust the wallpaper.
                   // This is to avoid a new window being placed between the
                   // wallpaper and its target.
                   displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
               }
           }

确定窗口位置,分成几种情况,如果是输入法类型的窗口,调用addInputMethodWindowToListLocked插入窗口列表,如果是输入法对话框,调用addWindowToListInOrderLocked插入窗口列表,如果是其他类型的窗口,比如应用类型的窗口,调用addWindowToListInOrderLocked插入窗口列表,现在重点关注addWindowToListInOrderLocked方法。

   private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
       if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win +
               " Callers=" + Debug.getCallers(4));
       if (win.mAttachedWindow == null) {
           final WindowToken token = win.mToken;
          //tokenWindowsPos表示该WindowState对象在所属同一WindowToken的所有WindowState中的位置
           int tokenWindowsPos = 0;
           if (token.appWindowToken != null) {
               tokenWindowsPos = addAppWindowToListLocked(win);
           } else {
               addFreeWindowToListLocked(win);
           }
           if (addToToken) {
               if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token);
                // token.windows就是描述所属该token下的所有WindowState对象
             // 比如一个activity弹出了一个AlertDialog窗口,这两个窗口的AppWindowToken是一个
               token.windows.add(tokenWindowsPos, win);
           }
       } else {
           addAttachedWindowToListLocked(win, addToToken);
       }

       final AppWindowToken appToken = win.mAppToken;
       if (appToken != null) {
           if (addToToken) {
               appToken.addWindow(win);
           }
       }
   }

先看窗口的父窗口mAttachedWindow是不是存在,如果不存在,说明这个窗口应用类型的窗口,反之就是一个子窗口,走else分之,使用addAttachedWindowToListLocked方法将子窗口插入窗口列表。假设不是一个子窗口,那么需要取出这个窗口的token,如果token里面的appWindowToken不为null的话,就表明这是一个Activity窗口,需要调用addAppWindowToListLocked将Activity窗口插入窗口堆栈,反之使用addFreeWindowToListLocked方法将非Activity窗口插入窗口堆栈。

 private int addAppWindowToListLocked(final WindowState win) {
   //获取WindowState要插入的屏幕对象displayContent
       final DisplayContent displayContent = win.getDisplayContent();
       if (displayContent == null) {
           // It doesn't matter this display is going away.
           return 0;
       }
       final IWindow client = win.mClient;
       final WindowToken token = win.mToken;
   //获取当前屏幕上所有窗口列表对象windows
       final WindowList windows = displayContent.getWindowList();
   //获取属于displayContent这个屏幕上,属于token所描述的WindowList,WindowList存放了token所描述的WindowState的列表
       WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
       int tokenWindowsPos = 0;
   //tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入
   //比如当一个Activity窗口上弹出一个Dialog窗口,
     //那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走这个逻辑
       if (!tokenWindowList.isEmpty()) {
           return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList);
       }

       // No windows from this token on this display
       if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window " + client.asBinder()
               + " (token=" + token + ")");
       // Figure out where the window should go, based on the
       // order of applications.
   //pos记录源码插入的WindowState
       WindowState pos = null;
   //遍历这个屏幕中的Task
       final ArrayList<Task> tasks = displayContent.getTasks();
       int taskNdx;
       int tokenNdx = -1;
   //自顶向下遍历Task,每一个Task中取出它的tokens列表
       for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
           AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
     //自顶向下遍历tokens列表,就可以取出AppWindowToken
           for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
               final AppWindowToken t = tokens.get(tokenNdx);
       //一般来说,第一个就是要匹配的AppWindowToken,因为都是自顶向下遍历的,这个时候,跳出循环
               if (t == token) {
         //tokenNdx记录着AppWindowToken在tokens中的位置
                   --tokenNdx;
         //如果tokenNdx小于0,那么是无效的,需要取下一个Task,把它插入下一个Task的顶部 
                   if (tokenNdx < 0) {
                       --taskNdx;
                       if (taskNdx >= 0) {
                           tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
                       }
                   }
                   break;
               }

               // We haven't reached the token yet; if this token
               // is not going to the bottom and has windows on this display, we can
               // use it as an anchor for when we do reach the token.
       //如果当前tasks中第一个AppWindowToken不等于t,那么执行getTokenWindowsOnDisplay,
         //获取属于displayContent这个屏幕上,属于token所描述的WindowList
               tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
               if (!t.sendingToBottom && tokenWindowList.size() > 0) {
          //pos就指向当前遍历到AppWindowToken所属的最下面一个的WindowState
                   pos = tokenWindowList.get(0);
               }
           }
           if (tokenNdx >= 0) {
               // early exit
               break;
           }
       }

       // We now know the index into the apps.  If we found
       // an app window above, that gives us the position; else
       // we need to look some more.
   //如果pos不为null,说明pos是上面指向的当前遍历到AppWindowToken所属的最下面一个的WindowState
       if (pos != null) {
           // Move behind any windows attached to this one.
           WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
           if (atoken != null) {
               tokenWindowList =
                       getTokenWindowsOnDisplay(atoken, displayContent);
               final int NC = tokenWindowList.size();
               if (NC > 0) {
                   WindowState bottom = tokenWindowList.get(0);
                   if (bottom.mSubLayer < 0) {
           //判断pos的token所描述的窗口列表中最下面的窗口对象bottom的子序是否小于0,
           //如果小于0,pos需要指向这个窗口, 确保pos指向的是上面AppWindowToken的最后一个WindowState
                       pos = bottom;
                   }
               }
           }
     //插入最下面的一个WindowState的后面
           placeWindowBefore(pos, win);
           return tokenWindowsPos;
       }

       // Continue looking down until we find the first
       // token that has windows on this display.
   //走到这里,说明第一个就是要匹配的AppWindowToken,taskNdx记录了是在哪一个taskNdx中,,再次自定向下遍历
       for (; taskNdx >= 0; --taskNdx) {
           AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
           for (; tokenNdx >= 0; --tokenNdx) {
       //获取顶部最顶部Task中最顶部的AppWindowToken
               final AppWindowToken t = tokens.get(tokenNdx);
               tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
               final int NW = tokenWindowList.size();
               if (NW > 0) {
         //pos尽可能的指向最顶部TASK中最顶部的AppWindowToken所描述的WindowState
                   pos = tokenWindowList.get(NW - 1);
                   break;
               }
           }
           if (tokenNdx >= 0) {
               // found
               break;
           }
       }
   //pos!= null,说明找到了,检查pos描述的窗口上面有没有子窗口,如果有,需要把pos指向最顶部的子窗口
       if (pos != null) {
           // Move in front of any windows attached to this
           // one.
           WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
           if (atoken != null) {
               final int NC = atoken.windows.size();
               if (NC > 0) {
                   WindowState top = atoken.windows.get(NC - 1);
                   if (top.mSubLayer >= 0) {
                       pos = top;
                   }
               }
           }
     //把win插入到pos的后面
           placeWindowAfter(pos, win);
           return tokenWindowsPos;
       }

       // Just search for the start of this layer.
   // 走到这里,说明该WindowState是新启动的一个activity的第一个窗口(新task的第一个WindowState),
   // 因为新启动的窗口是没有可参考的activity窗口,所以需要通过mBaseLayer去插入
       final int myLayer = win.mBaseLayer;
       int i;
       for (i = windows.size() - 1; i >= 0; --i) {
           WindowState w = windows.get(i);
           // Dock divider shares the base layer with application windows, but we want to always
           // keep it above the application windows. The sharing of the base layer is intended
           // for window animations, which need to be above the dock divider for the duration
           // of the animation.
           if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) {
               break;
           }
       }
       if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
               "Based on layer: Adding window " + win + " at " + (i + 1) + " of "
                       + windows.size());
       windows.add(i + 1, win);
       mWindowsChanged = true;
       return tokenWindowsPos;
   }

上面有检查tokenWindowList,tokenWindowList不为null,说明WMS中已经有了和待插入窗口win一样的token,那么就会使用addAppWindowToTokenListLocked来插入,比如当一个Activity窗口上弹出一个Dialog窗口,那么这个待插入的Dialog窗口的token和Activity窗口的token是一样的,都是AppWindowToken,就会走addAppWindowToTokenListLocked这个逻辑。

private int addAppWindowToTokenListLocked(WindowState win, WindowToken token,
           WindowList windows, WindowList tokenWindowList) {
       int tokenWindowsPos;
       // If this application has existing windows, we
       // simply place the new window on top of them... but
       // keep the starting window on top.
   //如果是TYPE_BASE_APPLICATION窗口,则需要插入在该AppWindowToken所有窗口的最底部
       if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
           // Base windows go behind everything else.
           WindowState lowestWindow = tokenWindowList.get(0);
           placeWindowBefore(lowestWindow, win);
           tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
       } else {
           AppWindowToken atoken = win.mAppToken;
           final int windowListPos = tokenWindowList.size();
           WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
       // 如果是starting window,则插入到starting window的下面
           if (atoken != null && lastWindow == atoken.startingWindow) {
               placeWindowBefore(lastWindow, win);
               tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
           } else {
               int newIdx = findIdxBasedOnAppTokens(win);
               //there is a window above this one associated with the same
               //apptoken note that the window could be a floating window
               //that was created later or a window at the top of the list of
               //windows associated with this token.
               if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
                       "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of "
                               + windows.size());
               windows.add(newIdx + 1, win);
               if (newIdx < 0) {
                   // No window from token found on win's display.
                   tokenWindowsPos = 0;
               } else {
                   tokenWindowsPos = indexOfWinInWindowList(
                           windows.get(newIdx), token.windows) + 1;
               }
               mWindowsChanged = true;
           }
       }
       return tokenWindowsPos;
   }

窗口的插入还是比较复杂的,总结而言:
1.非应用窗口依据mBaseLayer插入,越高越靠前,墙纸和输入法会有特殊处理,在下面还会进行调整。
2.应用窗口参考activity的位置插入,通常应该被插入在其activity所在task的顶部或者该activity上面的activity的最后一个窗口的下面
3.子窗口依据mSubLayer插入
最终插入的地方有两个:DisplayContent所持有的记录该屏幕下所有窗口顺序的WindowList,以及新窗口的WindowToken所记录的所有属于它的WindowState中的WindowList列表。

9、窗口已经添加了,调用assignLayersLocked调整一下层值
//参数windows是窗口列表
final void assignLayersLocked(WindowList windows) {
       if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows,
               new RuntimeException("here").fillInStackTrace());

       clear();
       int curBaseLayer = 0;
       int curLayer = 0;
       boolean anyLayerChanged = false;
      //遍历窗口列表,上面通过Z序的计算公式计算出来的Z序值保存在WindowState的变量mBaseLayer
       中,这个循环的意思是,遇到同类型的窗口,后一个窗口在前一个窗口的基础上偏移5。
       for (int i = 0, windowCount = windows.size(); i < windowCount; i++) {
           final WindowState w = windows.get(i);
           boolean layerChanged = false;

           int oldLayer = w.mLayer;
           if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) {
               curLayer += WINDOW_LAYER_MULTIPLIER;
           } else {
               curBaseLayer = curLayer = w.mBaseLayer;
           }
          // 更新该窗口的mAnimLayer,也就是动画显示时,该窗口的层级
           assignAnimLayer(w, curLayer);

           // TODO: Preserved old behavior of code here but not sure comparing
           // oldLayer to mAnimLayer and mLayer makes sense...though the
           // worst case would be unintentionalp layer reassignment.
           if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
               layerChanged = true;
               anyLayerChanged = true;
           }

     // 将当前应用窗口的最高显示层级记录在mHighestApplicationLayer中
           if (w.mAppToken != null) {
               mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
                       w.mWinAnimator.mAnimLayer);
           }
          //  对于分屏等相关的窗口,它们的显示层级需要再次处理
           collectSpecialWindows(w);

           if (layerChanged) {
               w.scheduleAnimationIfDimming();
           }
       }

    // 调整特殊窗口的层级
       adjustSpecialWindows();

       //TODO (multidisplay): Magnification is supported only for the default display.
       if (mService.mAccessibilityController != null && anyLayerChanged
               && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
           mService.mAccessibilityController.onWindowLayersChangedLocked();
       }

       if (DEBUG_LAYERS) logDebugLayers(windows);
   }

addWindow流程大致上分成以上九点,基本可以了解一个窗口是怎么添加到WMS中的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,565评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,021评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,003评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,015评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,020评论 5 370
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,856评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,178评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,824评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,264评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,788评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,913评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,535评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,130评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,102评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,334评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,298评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,622评论 2 343

推荐阅读更多精彩内容