dialog后新建线程 ios_ios风格 dialog - CSDN
  • 下面的一段转载自:http://blog.sina.com.cn/s/blog_71dbc27f01017mnj.html下面是快速创建一个新线程的方法:第一种:直接创建子线程并启动 new Thread() {@Overridepublic ... 第二种:先创建子线程,然后启动 ...

    下面的一段转载自:http://blog.sina.com.cn/s/blog_71dbc27f01017mnj.html

    下面是快速创建一个新线程的方法:

    第一种:直接创建子线程并启动
          new Thread() {
    @Override
    public void run() {
         //这里写入子线程需要做的工作
            }
       }.start();
       
    第二种:先创建子线程,然后启动
            private Thread newThread; //声明一个子线程
    newThread = new Thread(new Runnable() {
        @Override
                public void run() {
                //这里写入子线程需要做的工作
                }
            });

        newThread.start(); //启动线程

    -------------------------------------------------------------------------------------------------------------------------------------------分     割    线------------------------------------------------------------------------

    下面的文章转载自:http://android.jobbole.com/82440/

    下面是android中线程的正确使用:

    线程是程序员进阶的一道重要门槛。对于移动开发者来说,“将耗时的任务放到子线程去执行,以保证UI线程的流畅性”是线程编程的第一金科玉律,但这条铁则往往也是UI线程不怎么流畅的主因。我们在督促自己更多的使用线程的同时,还需要时刻提醒自己怎么避免线程失控。除了了解各类开线程的API之外,更需要理解线程本身到底是个什么样的存在,并行是否真的高效?系统是怎么样去调度线程的?开线程的方式那么多,什么样的姿势才正确?

    多线程编程之所以复杂原因之一在于其并行的特性,人脑的工作方式更符合单线程串行的特点。一个接着一个的处理任务是大脑最舒服的状态,频繁的在任务之间切换会产生“头痛”这类系统异常。人脑的多任务和计算机的多任务性能差异太大导致我们在设计并行的业务逻辑之时,很容易犯错。

    另一个复杂点在于线程所带来的副作用,这些副作用包括但不限于:多线程数据安全,死锁,内存消耗,对象的生命周期管理,UI的卡顿等。每一个新开的线程就像扔进湖面的石子,在你忽视的远处产生涟漪。

    把抽象的东西具像化是我们认知世界的主要方式。线程作为操作系统世界的“公民”之一,是如何被调度获取到CPU和内存资源的,又怎么样去和其他“公民”互通有无进而实现效益最大化?把这些实体和行为具像到大脑,像操作系统一样开“上帝视角”,才能正确掌控线程这头强大的野兽。

    进程优先级(Process Priority)

    线程寄宿在进程当中,线程的生命周期直接被进程所影响,而进程的存活又和其优先级直接相关。在处理进程优先级的时候,大部分人靠直觉都能知道前台进程(Foreground Process)优先级要高于后台进程(Background Process)。但这种粗糙的划分无法满足操作系统高精度调度的需求。无论Android还是iOS,系统对于Foreground,Background进程有进一步的细化。

    Foreground Process

    Foreground一般意味着用户双眼可见,可见却不一定是active。在Android的世界里,一个Activity处于前台之时,如果能采集用户的input事件,就可以判定为active,如果中途弹出一个Dialog,Dialog变成新的active实体,直接面对用户的操作。被部分遮挡的activity尽管依然可见,但状态却变为inactive。不能正确的区分visible和active是很多初级程序员会犯的错误。

    Background Process

    后台进程同样有更细的划分。所谓的Background可以理解为不可见(invisible)。对于不可见的任务,Android也有重要性的区分。重要的后台任务定义为Service,如果一个进程包含Service(称为Service Process),那么在“重要性”上就会被系统区别对待,其优先级自然会高于不包含Service的进程(称为Background Process),最后还剩一类空进程(Empty Process)。Empty Process初看有些费解,一个Process如果什么都不做,还有什么存在的必要。其实Empty Process并不Empty,还存在不少的内存占用。

    在iOS的世界里,Memory被分为Clean Memory和Dirty Memory,Clean Memory是App启动被加载到内存之后原始占用的那一部分内存,一般包括初始的stack, heap, text, data等segment,Dirty Memory是由于用户操作所改变的那部分内存,也就是App的状态值。系统在出现Low Memory Warning的时候会首先清掉Dirty Memory,对于用户来说,操作的进度就全部丢失了,即使再次点击App图标,也是一切从头开始。但由于Clean Memory没有被清除,避免了从磁盘重新读取app数据的io损耗,启动会变快。这也是为什么很多人会感觉手机重启后,app打开的速度都比较慢。

    同理Android世界当中的Empty Process还保存有App相关的Clean Memory,这部分Memory对于提升App的启动速度大有帮助。显而易见Empty Process的优先级是最低的。

    综上所述,我们可以把Android世界的Process按优先级分为如下几类:

          

    进程的优先级从高到低依次分为五类,越往下,在内存紧张的时候越有可能被系统杀掉。简而言之,越是容易被用户感知到的进程,其优先级必定更高。

    线程调度(Thread Scheduling)

    Android系统基于精简过后的linux内核,其线程的调度受时间片轮转和优先级控制等诸多因素影响。不少初学者会认为某个线程分配到的time slice多少是按照其优先级与其它线程优先级对比所决定的,这并不完全正确。

    Linux系统的调度器在分配time slice的时候,采用的CFS(completely fair scheduler)策略。这种策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的time slice数量,如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。显然使用这种调度策略的话,优先级高的线程并不一定能在争取time slice上有绝对的优势,所以Android系统在线程调度上使用了cgroups的概念,cgroups能更好的凸显某些线程的重要性,使得优先级更高的线程明确的获取到更多的time slice。

    Android将线程分为多个group,其中两类group尤其重要。一类是default group,UI线程属于这一类。另一类是background group,工作线程应该归属到这一类。background group当中所有的线程加起来总共也只能分配到5~10%的time slice,剩下的全部分配给default group,这样设计显然能保证UI线程绘制UI的流畅性。

          

    有不少人吐槽Android系统之所以不如iOS流畅,是因为UI线程的优先级和普通工作线程一致导致的。这其实是个误会,Android的设计者实际上提供了background group的概念来降低工作线程的CPU资源消耗,只不过与iOS不同的是,Android开发者需要显式的将工作线程归于background group

    [java] view plain copy
    1. new Thread(new Runnable() {  
    2.   @Override  
    3.   public void run() {  
    4.     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
    5.   }  
    6. }).start();  

    所以在我们决定新启一个线程执行任务的时候,首先要问自己这个任务在完成时间上是否重要到要和UI线程争夺CPU资源。如果不是,降低线程优先级将其归于background group,如果是,则需要进一步的profile看这个线程是否造成UI线程的卡顿。

    虽说Android系统在任务调度上是以线程为基础单位,设置单个thread的优先级也可以改变其所属的control groups,从而影响CPU time slice的分配。但进程的属性变化也会影响到线程的调度,当一个App进入后台的时候,该App所属的整个进程都将进入background group,以确保处于foreground,用户可见的新进程能获取到尽可能多的CPU资源。用adb可以查看不同进程的当前调度策略。

    [java] view plain copy
    1. $ adb shell ps -P  

    当你的App重新被用户切换到前台的时候,进程当中所属的线程又会回归的原来的group。在这些用户频繁切换的过程当中,thread的优先级并不会发生变化,但系统在time slice的分配上却在不停的调整。

    是否真的需要新线程?

    开线程并不是提升App性能,解决UI卡顿的万金油。每一个新启的线程会消耗至少64KB的内存,系统在不同的线程之间switch context也会带来额外的开销。如果随意开启新线程,随着业务的膨胀,很容易在App运行的某个时间点发现几十个线程同时在运行。后果是原本想解决UI流畅性,却反而导致了偶现的不可控的卡顿。

    移动端App新启线程一般都是为了保证UI的流畅性,增加App用户操作的响应度。但是否需要将任务放入工作线程需要先了解任务的瓶颈在哪,是i/o,gpu还是cpu?UI出现卡顿并不一定是UI线程出现了费时的计算,有可能是其它原因,比如layout层级太深。

    尽量重用已有的工作线程(使用线程池)可以避免出现大量同时活跃的线程,比如对HTTP请求设置最大并发数。或者将任务放入某个串行的队列(HandlerThread)按顺序执行,工作线程任务队列适合处理大量耗时较短的任务,避免出现单个任务阻塞整个队列的情况。

    用什么姿势开线程?

    new Thread()

    这是Android系统里开线程最简单的方式,也只能应用于最简单的场景,简单的好处却伴随不少的隐患。

    [java] view plain copy
    1. new Thread(new Runnable() {  
    2.            @Override  
    3.            public void run() {  
    4.   
    5.            }  
    6.        }).start();  

    这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消

    Runnable作为匿名内部类还持有了外部类的引用,在线程退出之前,该引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。

    没有线程切换的接口,要传递处理结果到UI线程的话,需要写额外的线程切换代码。

    如果从UI线程启动,则该线程优先级默认为Default,归于default cgroup,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得:

    [java] view plain copy
    1. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  

    虽说处于background group的线程总共只能争取到5~10%的CPU资源,但这对绝大部分的后台任务处理都绰绰有余了,1ms和10ms对用户来说,都是快到无法感知,所以我们一般都偏向于在background group当中执行工作线程任务。

    AsyncTask

    一个典型的AsyncTask实现如下:

    [java] view plain copy
    1. public class MyAsyncTask extends AsyncTask {  
    2.   
    3.     @Override  
    4.     protected Object doInBackground(Object[] params) {  
    5.         return null;  
    6.     }  
    7.   
    8.     @Override  
    9.     protected void onPreExecute() {  
    10.         super.onPreExecute();  
    11.     }  
    12.   
    13.     @Override  
    14.     protected void onPostExecute(Object o) {  
    15.         super.onPostExecute(o);  
    16.     }  
    17. }  

    和使用Thread()不同的是,多了几处API回调来严格规范工作线程与UI线程之间的交互。我们大部分的业务场景几乎都符合这种规范,比如去磁盘读取图片,缩放处理需要在工作线程执行,最后绘制到ImageView控件需要切换到UI线程。

    AsyncTask的几处回调都给了我们机会去中断任务,在任务状态的管理上较之Thread()方式更为灵活。值得注意的是AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。

    AsyncTask也有隐式的持有外部类对象引用的问题,需要特别注意防止出现意外的内存泄漏。

    AsyncTask由于在不同的系统版本上串行与并行的执行行为不一致,被不少开发者所诟病,这确实是硬伤,绝大部分的多线程场景都需要明确任务是串行还是并行。

    线程优先级为background,对UI线程的执行影响极小

    HandlerThread

    在需要对多任务做更精细控制,线程切换更频繁的场景之下,Thread()和AsyncTask都会显得力不从心。HandlerThread却能胜任这些需求甚至更多

    HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。

    HandlerThread背后只有一个线程,所以任务是串行执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。

    HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。

    HandlerThread较之Thread(),AsyncTask需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。

    ThreadPoolExecutor

    Thread(), AsyncTask适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务之时,ThreadPoolExecutor是更好的选择。

    [java] view plain copy
    1. public static Executor THREAD_POOL_EXECUTOR  
    2.        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
    3.        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);  

    线程池可以避免线程的频繁创建和销毁,显然性能更好,但线程池并发的特性往往也是疑难杂症的源头,是代码降级和失控的开始。多线程并行导致的bug往往是偶现的,不方便调试,一旦出现就会耗掉大量的开发精力。

    ThreadPool较之HandlerThread在处理多任务上有更高的灵活性,但也带来了更大的复杂度和不确定性。

    IntentService

    不得不说Android在API设计上粒度很细,同一样工作可以通过各种不同的类来完成。IntentService又是另一种开工作线程的方式,从名字就可以看出这个工作线程会带有service的属性。和AsyncTask不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue,从IntentService的onCreate方法可以看出:

    [java] view plain copy
    1. @Override  
    2. public void onCreate() {  
    3.     // TODO: It would be nice to have an option to hold a partial wakelock  
    4.     // during processing, and to have a static startService(Context, Intent)  
    5.     // method that would launch the service & hand off a wakelock.  
    6.   
    7.     super.onCreate();  
    8.     HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
    9.     thread.start();  
    10.   
    11.     mServiceLooper = thread.getLooper();  
    12.     mServiceHandler = new ServiceHandler(mServiceLooper);  
    13. }  

    只不过在所有的Message处理完毕之后,工作线程会自动结束。所以可以把IntentService看做是Service和HandlerThread的结合体,适合需要在工作线程处理UI无关任务的场景

    结束语

    Android开线程的方式虽然五花八门,但归根到底最后还是映射到linux下的pthread,业务的设计还是脱不了和线程相关的基础概念范畴:线程的执行顺序,调度策略,生命周期,串行还是并行,同步还是异步等等。摸清楚各类API下线程的行为特点,在设计具体业务的线程模型的时候自然轻车熟路了,线程模型的设计要有整个app视角的广度,切忌各业务模块各玩各的。




    展开全文
  • 1 背景之所以写这一篇博客的原因是因为之前有写过一篇《Android应用setContentView与LayoutInflater加载解析机制源码分析》,然后有人在文章下面评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow...

    【工匠若水 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.LayoutParams
                implements 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 level
            requestLayout();
            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类型成员变量mWindowManager
            mWindowManager = 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类型成员mWindowManager
            mWindowManager = ((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实例
        @Override
        public 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方法,传入的是mDecorView
                wm.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中最顶层的mDecor
            mGlobal.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为null
            if (parentWindow != null) {
                //依据当前Activity的Window调节sub Window的LayoutParams
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
            } else {
                ......
            }
    
            ViewRootImpl root;
            ......
            synchronized (mLock) {
                ......
                //为当前Window创建ViewRoot
                root = 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 things
            try {
                //把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.LayoutParams
                implements 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:信息窗口,用于显示Toast
            public 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之后的其他window
            public 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_SC
            public 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:当锁屏的时候,显示该window
            public 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 touch
            public 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的mask
            public 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的mask
            public 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)到了你的window
            public 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.PixelFormat
            public 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 {
        @Override
        protected 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;
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            //创建悬浮窗
            createFloatWindow();
        }
    
        private void createFloatWindow() {
            //这里的参数设置上面刚刚讲过,不再说明
            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
            mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
            //设置window的type
            layoutParams.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);
            //添加到Window
            mWindowManager.addView(mImageView, layoutParams);
            //设置监听
            mImageView.setOnTouchListener(touchListener);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            if (mImageView != null) {
                //讲WindowManager时说过,add,remove成对出现,所以需要remove
                mWindowManager.removeView(mImageView);
            }
        }
    
        private View.OnTouchListener touchListener = new View.OnTouchListener() {
            @Override
            public 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为true
            if (createContextThemeWrapper) {
                //默认构造函数的theme为0
                if (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创建新的Window
            Window w = PolicyManager.makeNewWindow(mContext);
            mWindow = w;
            //Dialog能够接受到按键事件的原因
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            //关联WindowManager与新Window,特别注意第二个参数token为null,也就是说Dialog没有自己的token
            //一个Window属于Dialog的话,那么该Window的mAppToken对象是null
            w.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方法,我们看下源码,如下:

        @Override
        public 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的WindowManager
                return 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处理已经完毕,很简单,所以接下来我们继续看看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_APPLICATION
            WindowManager.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初学者容易犯的几个错误。

    实现在一个Activity中显示一个Dialog,如下代码:

    public class MainActivity extends AppCompatActivity {
        @Override
        protected 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 {
        @Override
        protected 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 application
                at 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 {
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public 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 application
                at 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.LayoutParams
            WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
            //第二步
            preparePopup(p);
            ......
            //第三步
            invokePopup(p);
        }

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

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

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

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

    可以看见preparePopup方法的作用就是判断设置View,如果有背景则会在传入的contentView外面包一层PopupViewContainer(实质是一个重写了事件处理的FrameLayout)之后作为mPopupView,如果没有背景则直接用contentView作为mPopupView。我们再来看下这里的PopupViewContainer类,如下源码:

        private class PopupViewContainer extends FrameLayout {
            ......
            @Override
            protected int[] onCreateDrawableState(int extraSpace) {
                ......
            }
    
            @Override
            public boolean dispatchKeyEvent(KeyEvent event) {
                ......
            }
    
            @Override
            public boolean dispatchTouchEvent(MotionEvent ev) {
                if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {
                    return true;
                }
                return super.dispatchTouchEvent(ev);
            }
    
            @Override
            public boolean onTouchEvent(MotionEvent event) {
                ......
                if(xxx) {
                    dismiss();
                }
                ......
            }
    
            @Override
            public 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,相对来说比较简单容易记理解。

    再来看一个开发技巧:

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

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

    5 Android应用Toast窗口添加显示机制源码

    5-1 基础知识准备

    在开始分析这几个窗口之前需要脑补一点东东,我们从应用层开发来直观脑补,这样下面分析源码时就不蛋疼了。如下是一个我们写的两个应用实现Service跨进程调用服务ADIL的例子,客户端调运远程Service的start与stop方法控制远程Service的操作。

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

    先看下在Android Studio中AIDL开发的工程目录结构,如下:

    这里写图片描述

    由于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)
            {
                ......
            }
            ......
            //远程服务的本地代理,也会继承自ITestService
            private static class Proxy implements io.github.yanbober.myapplication.ITestService
            {
                ......
                @Override
                public void start(int id) throws android.os.RemoteException
                {
                    ......
                }
    
                @Override
                public 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 {
    
            @Override
            public void start(int id) throws RemoteException {
                Log.i(null, "Server Service is start!");
            }
    
            @Override
            public void stop(int id) throws RemoteException {
                Log.i(null, "Server Service is stop!");
            }
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            //返回Binder
            return mTestBinder;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            //实例化Binder
            mTestBinder = new TestBinder();
        }
    }

    现在服务端App的代码已经OK,我们来看下客户端的代码。客户端首先也要像上面的工程结构一样,把AIDL文件放好,接着在客户端使用远程服务端的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() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //获得另一个进程中的Service传递过来的IBinder对象
                //用IMyService.Stub.asInterface方法转换该对象
                mBinder = ITestService.Stub.asInterface(service);
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        };
    
        @Override
        protected 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);
            //绑定远程跨进程Service
            bindService(new Intent(REMOT_SERVICE_ACTION), connection, BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            //取消绑定远程跨进程Service
            unbindService(connection);
        }
    
        private View.OnClickListener clickListener = new View.OnClickListener() {
            @Override
            public 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);
            //获取前面有篇文章分析的LayoutInflater
            LayoutInflater 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);
            //取出布局中的TextView
            TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
            //把我们的文字设置到TextView上
            tv.setText(text);
            //设置一些属性
            result.mNextView = v;
            result.mDuration = duration;
            //返回新建的Toast
            return result;
        }

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

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

        public void show() {
            ......
            //通过AIDL(Binder)通信拿到NotificationManagerService的服务访问接口,当前Toast类相当于上面例子的客户端!!!相当重要!!!
            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内部类Binder
        private static class TN extends ITransientNotification.Stub {
            ......
            //实现了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);
            }
            ......
        }

    看见没有,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又充当客户端,又充当服务端的样子,实质就是一个回调过程而已。

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

        private final IBinder mService = new INotificationManager.Stub() {
            // Toasts
            // ============================================================================
    
            @Override
            public 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.
                        //注释说了,已经存在则直接取出update
                        if (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中队列最前面的ToastRecord
            ToastRecord 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) {
                ......
            }
            //从队列移除当前显示的Toast
            mToastQueue.remove(index);
            keepProcessAliveLocked(record.pid);
            if (mToastQueue.size() > 0) {
                //如果当前的Toast显示完毕队列里还有其他的Toast则显示其他的Toast
                showNextToastLocked();
            }
        }

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

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

    ```java
        private 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.
                    //注释说得很清楚了,不解释,就是remove
                    if (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 转载烦请注明出处,尊重劳动成果】

    这里写图片描述

    展开全文
  • 什么是iOSiOS是由苹果开发的移动操作系统。 苹果台式机操作系统? MAC OS 开发环境? Mac OS + xcode。 常用命令? Command + R = 运行 Command + N = 新建 Swift语言是苹果公司于2014年6月的WWDC大会上...
    什么是iOS?

    iOS是由苹果开发的移动操作系统。

    苹果台式机操作系统?

    MAC OS

    开发环境?

    Mac OS + xcode。

    常用命令?

    Command + R = 运行
    Command + N = 新建

    Swift语言是苹果公司于2014年6月的WWDC大会上发布的一种新的语言,由苹果公司完全开发,未来将替代objective-c。
    作为开发者首先得有一个AppleID(开发者账号)
    iOS开发,仅在模拟器上运行是免费的
    什么是模拟器?

    Xcode安装好后,就默认安装了iOS 模拟器,可以方便地运行自己开发的App。值得一提的是,与一般人对模拟器的印象不同iOs模拟器的运行速度非常快,有时候甚至比真机还快,-点都不卡,启动速度也很快,使用起来令人感觉愉快

    Singe view application 能跳转页面
    主页两种框架:

    Main storyboard(拖控件)
    launchscreen (纯代码)
    默认为(main storyboard)

    AppDelegate 程序代理入口

    AppDelegate (处理应用程序生命周期的各个事件响应)
    ViewController(视图控制器)
    rootViewController (根视图控制器),没有设置背景默认为黑色

    创建UILabel对象

    UILabel *label = [UILabel new ]
    UILabel *label =[[UILabel alloc] init]

    异步执行

    有个概念叫异步执行 与之 相对的是同步执行。同步执行就是一 步步按部就班的执行,当前任务为未完成,则当前线程陷入阻塞状态,直到任务完成才运行下一个任务。而异步执行则灵活得多,指定一个任务运行后,不管任务有没有完成,马上就返回执行下一个任务。之前的任务完成后,框架会自动调用写好的回调函数来处理善后。

    UIViewController管理着视图的生命周期:

    (1)视图加载完毕
    (2)视图将显示
    (3)视图已显示
    (4)视图将消失
    (5)视图已消失

    什么是协议?

    协议与代理是Objective C语言的概念,与其他的语言的名词差别很大。协议可以看作Java等语言中的接口,或者抽象类即只有方法定义,却不实现方法。

    代理是实现了协议的对象,可以看作Java等语言中实现了某接口或抽象类的对象

    协议 == 接口 || 抽象类

    代理 == 接口的实现类 || 抽象类实现类

    target 与 selector?

    target : 用来指定事件由某个对象处理
    selector:用来指定某个方法作为执行响应事件

    NSNotification消息中心?

    有些事件不是由硬件中断产生。比如键盘收起事件等。。。。这些事件一般通过Cocoa的通知中心机制来广播。

    viewDidLoad方法中创建视图、控件、以及指定响应方法。
    UIView基本属性

    CGRect类型的结构体:
    包含了位置+大小
    (CGPoint+CGSize)==(X,Y+W,H)

    (1)frame:规定视图大小和位置
    (X,Y,W,H)其中X,Y可以变化 ,代表相对于其父view 的位置

    (2)bounds:规定视图大小和位置
    (X,Y,W,H)其中X,Y永远为 (0,0)

    CGPoint类型的结构体:

    (3)center:定义视图中心点在父视图的坐标,CGPoint类型;

    UIContro

    UIContro默认可以接受触摸事件,并且对接受的触摸事件做了很详细的区分,可以很方便地定制需要响应的事件。

    什么是控件?

    UIContro是所有控件的父类,所谓控件就是能够接受用户的触摸操作,并对其做出响应的UI组件

    按钮、开关、分段控件、进度条等
    
    4个不同事件的响应方法
    (1)按下(TouchDown)
    

    (2)在按钮内部松开手指(TouchUpInside)
    

    (3)在按钮外部松开手指(TouchUpOutside)
    

    (4)按下不放移动手指直到手指离开按钮边界(TouchDragExit)
    
    制作计算器

    alloc = 指针
    CGRectMake:所有控件都通过其画出

    result = [[UILabel alloc] initWithFrame: CGRectMake(0,44.0,width,labelHeight - 44.0)];
    
    添加响应事件

    self:在父视图执行
    @selector()指明方法

    [btn[16]	addTarget:self action:@selector(onPress:)	forControllerEvents:UIControllerEventTouchUpInside];
    
    各按钮的响应方法都用同一个onPress 通过各个按钮的tag值来区分
    开关控件:UISwitch
    滑块控件:UISlider 范围为0-1
    分段控件:UISegmentefControl

    通过UISegmentefControl对象的selectedSegmentIndex属性得知当前所选的index,从而做出响应。

    NSLog();控制台输出
    UITextField

    为了控制其输入过程中的诸多事件,实现UITextFieldDelegate协议

    用的比较多的是textFieldDidEditing
    法,用于在用户结束编辑时的处理工作。

    UITextView

    用于展示大段文本(多行)
    有一个BOOL类型属性editable来控制是否可编辑
    添加 tv控件 到父视图上
    [self.view addSubview:tv];

    日期选择器 UIDatePicker

    (1)UIDatePickerModeTime --仅显示和选择时|分


    (2)UIDatePickerModeDate --仅显示和选择 年|月|日 》》》》 重点关注


    (3)UIDatePickerModeDateAndTime --仅显示和选择年|月|日|时|分等


    (4)UIdateModeCountDownTimer 用以计时,选择小时数和分钟数: 小时|分钟

    NSDateFormatter类

    常用的用法如:

    NSDateFormatter *df =[[NSDateFormatter alloc] init];
    df.dateFormat = @"yyyy-MM-dd HH:mm:ss";  
    NSString *dateString = [df	stringFromDate:[NSDate date]];
    NSDate *date = [df dateFromString:@"2015-12-01 20:05:23"]; 
    
    自定义选择器UIPickerView

    UIPickerView 的代理有两个与其对应的协议

    (1)dataSource    其协议(实现类)UIPickerViewDateSource   用来提供显示的内容。
    

    (2)delegate	其协议(实现类)UIPickerViewDelegate   用来处理选择器的事件。
    

    指定多少列

    -(NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView
    {
        return 1; // 返回1表明该控件只包含1列
    }
    
    

    指定多少行的

    - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
    {
    	if ( component == 0 )
    	{
    		return(province.count);
    	}else if ( component == 1 )
    	{
    		return([cities[curProvince] count]);
    	}else{
    		return([areas[curProvince] [curCity] count]);
    	}
    	return(_teams.count);
    }
    

    设置要显示的标题

    - (NSString *)pickerView:(UIPickerView *)pickerView
           titleForRow:(NSInteger)row forComponent:(NSInteger)component
    {
    	if ( component == 0 )
    	{
    		return([provinces:row]);
    	}else if ( component == 1 )
    	{
    		return(cities[curProvince][row]);
    	}else{
    		return(areas[curProvince][curCity][row]);
    	}
    }
    

    当省份或城市改变时,后面的列的显示需要相应的更新

    - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
    {
    	if ( component == 0 )
    	{
    		curProvince = row;
    	}else if ( component == 1 )
    	{
    		curCity = row;
    	}else{
    		curArea = row;
    	}
    	[pickerView reloadAllComponents];
    }
    
    网页控件 UIWebView

    web.delegate = self; //代理 = 本身

    在UITextField中触发一次URL请求

    NSURL *url = [NSURL URLWithString:textField.text];
    NsRequest *req = [NSURLRequest requestWithURL:url];
    [web loadRequest:req];
    
    导航栏一定在屏幕最上方
    工具栏一般在屏幕最下方

    创建一个工具栏

    UIToolBar toolBar =[ [UIToolBar alloc] iniWithFrame:CGRectMake(0,self.view.frame.size.height - 44,self.view.frame.size.width,44)];
    tooBar.items = @[item1,space,item2,space,item3];//按钮组加入工具栏
    
    导航栏UINavigationBar

    _windows.rootViewController = nav;

    导航控制器的本质:

    (1)维护了一个控制器的栈
    (2)栈结构是先进后出的结构
    (3)栈的好处,在于可以记忆最近做的事
    (4)新切入一个页面时,栈会新压入一个控制器
    (5)退出当前页面时,栈也相应地弹出当前控制器

    常用属性:

    navigationItem  用来定义顶部导航栏的标题与左右侧按钮、图片的
    

    navigationController  可直接访问导航控制器本身(比如推入新页面或退出当前页)
    
    Storyboard 可视化编辑界面

    去掉 Use Auto LayoutUse Size Classes 就可以往界面添加按钮(真的这样??)

    UIStoryborad 类

    需用到identifier 加载UIviewController对象

    Navigation Controller带有2个界面,左边是NavigationController,右边是 root view controller

    Modal 连线中间设置一个segue 也就是跳转

    navigation controller 不能选择Push跳转, 但可以选Modal

    警告框 UIAlertController控制器

    警告框是一个模态对话框(modal dialog)会暂时中断其他操作。

    iOS8以前是有专门的视图类,不过后面被抛弃了
    

    UIAlertController 有两种样式(但它两不是同一种控件)
    (1)警告框
    (2)操作表

    UIAlertActionStyleDestructive 破坏性操作   红色
    
    UIAlertActionStyleDefault  默认项   黑色
    
    活动指示器 UIActivityIndicatorView

    用于某些时间较长的操作,如网络获取数据等。。。

    UITableView由很多节(section)构成,每一节有头有尾,中间由一个个单元格(cell)构成
    UITableViewCell的四种基本样式
    1. UITableViewCellStyleDefault

    左边一个显示图片的imageView,一个标题textLabel,没有detailTextLabel。

    1. UITableViewCellStyleSubtitle

    左边一个显示图片的imageView,上边一个主标题textLabel,一个副标题detailTextLabel。主标题字体大且加黑,副标题字体小在主标题下边。

    1. UITableViewCellStyleValue1

    左边一个显示图片的imageView,左边一个主标题textLabel,右边一个副标题detailTextLabel,主标题字体比较黑。

    1. UITableViewCellStyleValue2

    左边一个主标题textLabel字体偏小,挨着右边一个副标题detailTextLabel,字体大且加黑。

    1. 自定义样式

    编写UITableViewCell类的子类,再定义需要的子视图

    表视图项目的APPDelegate.m
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    ViewController *vc = [[ViewController alloc] init];
    
    self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:vc];
    
    [self.window makeKeyAndVisible];
    
    UISearchController
    1. 变量属性
    NSArray *data;
    NSArary *dataBackup;
    NSArray *header;
    UISearchBar *bar;
    UISearchController *searchCon;
    
    1. viewDidLoad增加几行
    dataBackup = data;
    searchCon = [[UISearchController alloc] initWithSearchResultsController:nil];
    bar = searchCon.searchBar;
    [bar sizeToFit];
    searcherCon.searchResultsUpdater = self;
    self.tableView.tableHeaderView = bar;
    
    1. 搜索事件响应方法实现
    NSMutableArray *array = [NSMutableArray new];
    for ( NSArray *arr in dataBackup )
    {
    	for ( NSString *name in arr )
    	{
    		if ( [bar.text isEqualToString:@""] || [name containsString:bar.text] )
    		{
    			[array addObject:name];
    		}
    	}
    }
    data = array;
    if ( !searchCon.isActive )
    {
    	data = dataBackup;
    }
    [self.tableView reloadData];
    
    判断是否在搜索状态
    if ( searchCon.isActive )
    {
    	return(1);
    }else{
    	return(data.count);
    }
    
    实现删除
    [tableView deleteRowAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
    
    增加单元格

    static NSstring *reuse=@“cells”; 存放单元格的池子

    static NSstring *reuse	= @"cells";
    UITableViewcell *cell	= [tableView requeueReusableCellWithldentifier:reuse];
    
    if ( cell = nil )
    {
    	cell = [UITableViewCell alloc] initWithStyle : UITableViewCellStyleDefault reuseIdentifier : reuse];
    }
    if ( searchCon.isActive )
    {
    	cell.textLabel.text = data [indexPath.row];
    }else{
    	if ( indexPath.row < [data [indexPath.section] count] )
    	{
    		cell.textLabel.text = data[indexPath.section] [indexPath.row];
    	}else                                                                 {
    		cell.textLabel.text = @"添加";
    	}
    }
    
    Model类

    新建Cocoa Touch Class类文件 note 的父类应该为NSObject

    顺便增加一个协议

    @interface ViewController:UITableViewController <UITextViewDalegate>
    

    textViewDidEndEditing方法判断是否编辑完成

    沙盒机制

    相当于一个文件系统监狱,APP被限制在这个监狱中,对监狱外的事物也就是文件一无所知。


    沙盒中含有3个文件夹:
    Document、Library、tmp

    其中:

    NSArray * NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory,NSSearchPathDomainMask domainMask,BOOL expandTilde);
    

    返回的是数组,选第一个作为主路径


    写入数组
    [array writeToFile:path atomically:YES];

    sqlite3数据库

    使用的话,需加入libsqlite3.tbd的库
    并引入SQLite头文件
    #import <sqlite3.h>
    sqlite3接口函数:
    1.sqlite3_open()
    2.sqlite3_close()
    3.sqlite3_exec()

    手势识别器
    抽三个
    
    1. UITapGestureRecognizer
      (轻拍手势识别器)

    2. UILongPressGestureRecognizer
      长按手势识别器)

    3. UIPinchGestureRecognizer
      捏拉缩放手势识别器)

    4. UIRotationGestureRecognizer
      旋转手势识别器)

    5. UIPan=GestureRecognizer
      拖曳手势识别器)(关注一下:滑动类手势)

    Taps 和 Touches 默认都是1
    意思是1个手指,轻拍一次,

    Taps代表连续轻拍的次数
    Touches代表用几个手指轻拍

    UIView有个属性

    userInteractionEnable 默认为NO:
    不接受用户交互,需要设置为YES
    才能响应手势识别操作

    图片放在Assets.xcassets中
    像素比例

    1x 2x 3x 这种为像素比例x越大,就越来越小,

    UIGestureRecognizerStateChanged用来写代码的。
    模拟器操作手势,按住option键,单击鼠标不放,即可
    Scale为捏拉缩放的比例,velocity为捏拉过程的速度。
    解决手势冲突设置代理:UIgestureRecognizerDelegate

    实现 should RecognizerSimultaneouslyWithGestureRecognizer

    translation 表示位移,单位是CGPoint
    teanslationInView 是指定相对哪一个?(父容器)的位移
    展开全文
  • 最近在做与H5的交互中发现,通过@javascriptInterface提供的接口是运行在子线程中,但是在子线程中却能够操作View,这个有点颠覆了我学Android以来的认知,子线程不能更新view 于是写了demo做了几个实现,demo使用...

    问题点:

    最近在做与H5的交互中发现,通过@javascriptInterface提供的接口是运行在子线程中,但是在子线程中却能够操作View,这个有点颠覆了我学Android以来的认知,子线程不能更新view

    于是写了demo做了几个实现,demo使用的sdk,目前测试过android-21,android-25和android-27

    demo很简单,就是新建一个子线程,分别在里面作如下操作,来观察程序运行情况

    一、API25和27的情况

    view操作 android-25,android-27上程序是否崩溃 崩溃原因 修复方法
    TextView,Button:setText
    Toast:show 子线程未初始化looper showToast前后增加Looper.prepare()和Looper.loop()
    Dialog:show 子线程未初始化looper(Can't create handler inside thread that has not called Looper.prepare()) showDialog前后增加Looper.prepare()和Looper.loop()
    Dialog:dismiss 子线程不能操作view(Only the original thread that created a view hierarchy can touch its views)

     

    一、API21的情况

    view操作 android-21是否会崩溃   崩溃原因 修复方法
    TextView,Button:setText 子线程不能操作view(Only the original thread that created a view hierarchy can touch its views)

    由表格可以发现:

    1、API21上肯定会崩溃,这个符合我们对android子线程不能更新view的认知。

    2、在API25和27上,我们常用的TextView或者是button是可以在子线程中更新view的但是dialog却不能dismiss,很奇怪,我仔细分析了一下Dialog.dimiss和TextView.setText的源码:

     

    一、setText

    setText的时候会调用View的invalidate()方法,跟进源码之后发现:最终会调用到ViewGroup中的这句话

    该段代码就是表示在父view中更新子view,由于我demo的父view是RelativeLayout,RelativeLayout继承ViewGroup,所以走到了ViewGroup里面的更新view的方法,这里面一系列方法没有对线程的判断

    二、Dialog.dismiss

    Dialog dismiss时会调用windowManager的removeViewImmediate()方法

    继续跟进windowManager的实现类windowManagerImpl,发现会走到ViewRootImpl类里面的这个方法,然后这里面有一个checkThread方法,这个就会导致抛出子线程不能更新view的错误。

    继续研究源码,然后发现ViewRootImpl类里面也有一个ViewGroup类似的方法,viewGroup里面虽然有这个checkThread,但是更新view的时候没有调用到这个方法,所以ViewGroup里面更新view不会报错。

    至此终于找到了罪魁祸首,综合以上,理论上来说,由于指观察了API21,25,27目前最起码25以上的API只要父view是继承ViewGroup就不会崩溃。

     

    但是为什么dialog和toastshow的时候为什么只要初始化一下looper依然能在更新view呢?,下面对他们的show方法读了一下源码,

     

    三、Dialog show和Toast show

    这两个方法只要在子线程初始化一下Looper(Looper.prepare(),Looper.loop())就可以show出来。

    这时候为什么可以在子线程更新View呢,还是分析源码,dialog和toast类似,会走到ViewrootImpl的checkThread方法,但是这时候确没有报异常,这时候观察源码

    发现每次show的时候都会重新初始化这个ViewrootImpl,初始化的时候会给ViewrootImpl中的mThread赋值

    就是当前线程,此时在子线程中,更新View的时候还是这个子线程,所有无论怎么判断,这两个线程都是同一个线程,所以不会报错

    展开全文
  • DSBridge for IOS 三端易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数. [外链图片转存失败(img-u0mL0POl-1565862440332)...

    DSBridge for IOS

    三端易用的现代跨平台 Javascript bridge, 通过它,你可以在Javascript和原生之间同步或异步的调用彼此的函数.
    [外链图片转存失败(img-u0mL0POl-1565862440332)(https://github.com/wendux/DSBridge-IOS)]

    特性

    1. Android、IOS、Javascript 三端易用,轻量且强大、安全且健壮。
    2. 同时支持同步调用和异步调用
    3. 支持以类的方式集中统一管理API
    4. 支持API命名空间
    5. 支持调试模式
    6. 支持API存在性检测
    7. 支持进度回调:一次调用,多次返回
    8. 支持Javascript关闭页面事件回调
    9. 支持Javascript 模态/非模态对话框
    10. Android端支持腾讯X5内核

    安装

    pod "dsBridge"
    

    使用

    1. 新建一个类,实现API JS调用原生方法

      #import "dsbridge.h" 
      ...
      @implementation JsApiTest
      //同步API 
      - (NSString *) testSyn:(NSString *) msg
      {
          return [msg stringByAppendingString:@"[ syn call]"];
      }
      //异步API
      - (void) testAsyn:(NSString *) msg :(JSCallback)completionHandler
      {
          completionHandler([msg stringByAppendingString:@" [ asyn call]"],YES);
      }
      @end 
      
      
      // MARK: - 测试同步方法
      //MUST use "_" to ignore the first argument name explicitly。
      @objc func testSyn( _ arg:String) -> String {
           print("js调用了原生的testSyn方法")
           return String(format:"%@[Swift sync call:%@]", arg, "test")
      }
      // MARK: - 测试异步有回调
      @objc func testAsyn( _ arg:String, handler: JSCallback) {
           print("js调用了原生的testAsyn方法")
           handler(String(format:"%@[Swift async call:%@]", arg, "test"), true)
      }
      
      

      可以看到,DSBridge正式通过API类的方式集中、统一地管理API。

    2. 添加API类实例到 DWKWebView

      DWKWebView * dwebview=[[DWKWebView alloc] initWithFrame:bounds];
      // register api object without namespace
      [dwebview addJavascriptObject:[[JsApiTest alloc] init] namespace:nil];
      
       //添加原生方法类
       webview.addJavascriptObject(JsApiTestSwift.init(), namespace: nil)
       webview.addJavascriptObject(JsApiTestSwift.init(), namespace: "swift")//增加命名空间, JS在调用的时候可以用 swift.methodName 方便管理功能模块可增强阅读
      
    3. 在Javascript中调用原生 (Java/Object-c/swift) API ,并注册一个 javascript API供原生调用.

      • 初始化 dsBridge

        //cdn方式引入初始化代码(中国地区慢,建议下载到本地工程)
        //<script src="https://cdn.jsdelivr.net/npm/dsbridge@3.1.4/dist/dsbridge.js"> //</script>
        //npm方式安装初始化代码
        //npm install dsbridge@3.1.4
        var dsBridge=require("dsbridge")
        
      • 调用原生API ,并注册一个 javascript API供原生调用.

        
        //同步调用
        var str=dsBridge.call("testSyn","testSyn");
        
        //异步调用
        dsBridge.call("testAsyn","testAsyn", function (v) {
          alert(v);
        })
        
        //注册 javascript API 
         dsBridge.register('addValue',function(l,r){
             return l+r;
         })
        
    4. 调用Javascript API

         [dwebview callHandler:@"addValue" arguments:@[@3,@4] completionHandler:^(NSNumber* value){
                NSLog(@"%@",value);
         }];
      
          //原生调用js的addValue方法, 参数是[3, 4], 返回值是value
          webview.callHandler("addValue", arguments: [3, 4]) { (value) in
          print(value ?? "")
      }
      

    在Swift中使用

    在 Swift中,你应该按照如下方式声明APIs:

    //必须给第一个参数前添加下划线"_"来显式忽略参数名。
    @objc func testSyn( _ arg:String) -> String {
    	return String(format:"%@[Swift sync call:%@]", arg, "test")
    }
    
    @objc func testAsyn( _ arg:String, handler: (String, Bool)->Void) {
    	handler(String(format:"%@[Swift async call:%@]", arg, "test"), true)
    }
    

    有两点必须注意:

    • 必须给Swift API添加 “@objc” 标注。
    • 必须给第一个参数前添加下划线"_"来显式忽略参数名

    完整的示例在 这里 .

    命名空间

    命名空间可以帮助你更好的管理API,这在API数量多的时候非常实用,比如在混合应用中。DSBridge (>= v3.0.0) 支持你通过命名空间将API分类管理,并且命名空间支持多级的,不同级之间只需用’.’ 分隔即可。

    调试模式

    在调试模式时,发生一些错误时,将会以弹窗形式提示,并且原生API如果触发异常将不会被自动捕获,因为在调试阶段应该将问题暴露出来。如果调试模式关闭,错误将不会弹窗,并且会自动捕获API触发的异常,防止crash。强烈建议在开发阶段开启调试模式,可以通过如下代码开启调试模式:

    // open debug mode
    [dwebview setDebugMode:true];
    

    进度回调

    通常情况下,调用一个方法结束后会返回一个结果,是一一对应的。但是有时会遇到一次调用需要多次返回的场景,比如在javascript钟调用端上的一个下载文件功能,端上在下载过程中会多次通知javascript进度, 然后javascript将进度信息展示在h5页面上,这是一个典型的一次调用,多次返回的场景,如果使用其它Javascript bridge, 你将会发现要实现这个功能会比较麻烦,而DSBridge本省支持进度回调,你可以非常简单方便的实现一次调用需要多次返回的场景,下面我们实现一个倒计时的例子:

    In Object-c

    - ( void )callProgress:(NSDictionary *) args :(JSCallback)completionHandler
    {
        value=10;
        hanlder=completionHandler;
        timer =  [NSTimer scheduledTimerWithTimeInterval:1.0
                                                  target:self
                                                selector:@selector(onTimer:)
                                                userInfo:nil
                                                 repeats:YES];
    }
    -(void)onTimer:t{
        if(value!=-1){
            hanlder([NSNumber numberWithInt:value--],NO);
        }else{
            hanlder(@"",YES);
            [timer invalidate];
        }
    }
    

    In javascript

    dsBridge.call("callProgress", function (value) {
    	document.getElementById("progress").innerText = value
    })
    

    完整的示例代码请参考demo工程。

    Javascript 弹出框

    DSBridge已经实现了 Javascript的弹出框函数(alert/confirm/prompt),这些对话框按钮、标签文字默认都是中文的,如果你想自定义这些文本可以参考 customJavascriptDialogLabelTitles API,如果你不想使用DSBridge实现的对话框,你可以通过设置DSUIDelegate 属性(是WKUIDelegate的代理属性)完全自定义。

    另外注意,DSBridge实现的弹出框都是模态的,这会阻塞UI线程,如果你需要非模态的对话框,请参考disableJavascriptDialogBlock API.

    WKUIDelegate

    DWKWebView 中,请使用DSUIDelegate 代替 UIDelegate , 因为在DWKWebView 内部 UIDelegate已经设置过了,而 DSUIDelegate 正是 UIDelegate 的一个代理。

    API 列表

    Object-C API

    在Object-c中我们把实现了供 javascript调用的 API类的实例 成为 Object-c API object.

    addJavascriptObject:(id) object namespace:(NSString *) namespace

    添加一个 Object-c API object 到DWKWebView,并为它指定一个命名空间. 然后,在 javascript 中就可以通过bridge.call("namespace.api",...)来调用Object-c API object中的原生API了。

    如果命名空间是空(nil或空字符串), 那么这个添加的 Object-c API object就没有命名空间。在 javascript 通过 bridge.call("api",...)调用。

    示例:

    In Object-c

    @implementation JsEchoApi
    - (id) syn:(id) arg
    {
        return arg;
    }
    - (void) asyn: (id) arg :(JSCallback)completionHandler
    {
        completionHandler(arg,YES);
    }
    @end
    // register api object with namespace "echo"
    [dwebview addJavascriptObject:[[JsEchoApi alloc] init] namespace:@"echo"];
    

    In Javascript

    // call echo.syn
    var ret=dsBridge.call("echo.syn",{msg:" I am echoSyn call", tag:1})
    alert(JSON.stringify(ret))  
    // call echo.asyn
    dsBridge.call("echo.asyn",{msg:" I am echoAsyn call",tag:2},function (ret) {
          alert(JSON.stringify(ret));
    })
    
    removeJavascriptObject:(NSString *) namespace

    通过命名空间名称移除相应的 Object-c API object.

    callHandler:(NSString *) methodName arguments:(NSArray *) args
    callHandler:(NSString *) methodName completionHandler:(void (^)(id value))completionHandler
    callHandler:(NSString *) methodName arguments:(NSArray *) args completionHandler:(void (^ )(id value))completionHandler

    调用 javascript API.methodName 为javascript API 的名称,可以包含命名空间;参数以数组传递,argumentss数组中的元素依次对应javascript API的形参; completionHandler 用于接收javascript API的返回值,注意: completionHandler将在主线程中被执行

    示例:

    [dwebview callHandler:@"append" arguments:@[@"I",@"love",@"you"]
      completionHandler:^(NSString * _Nullable value) {
           NSLog(@"call succeed, append string is: %@",value);
    }];
    // call with namespace 'syn', More details to see the Demo project                    
    [dwebview callHandler:@"syn.getInfo" completionHandler:^(NSDictionary * _Nullable value) {
            NSLog(@"Namespace syn.getInfo: %@",value);
    }];
    
    disableJavascriptDialogBlock:(bool) disable

    小心使用. 如果你再javascript中调用弹窗函数(alert,confirm, 或 prompt), 那么APP将会挂起,因为这些弹窗都是模态的,会阻塞APP主线程,此时javascript执行流也会阻塞。如果你想避免阻塞,可以通过此API禁止,禁止后,一旦 javascript中调用了这些弹窗函数,APP将弹出非模态对话框,并立即返回,( confirm 会返回 true, prompt 返回空字符串)。

    如:

    [dwebview disableJavascriptDialogBlock: true]
    

    如果你想恢复模态对话框,传 false 调用即可.

    setJavascriptCloseWindowListener:(void(^_Nullable)(void))callback

    当 Javascript中调用window.close时,DWKWebView会触发此监听器:

    Example:

    [dwebview setJavascriptCloseWindowListener:^{
            NSLog(@"window.close called");
    }];
    
    hasJavascriptMethod:(NSString*) handlerName methodExistCallback:(void(^)(bool exist))callback

    检测是否存在指定的 javascript API,handlerName可以包含命名空间.

    Example:

    // test if javascript method exists.
    [dwebview hasJavascriptMethod:@"addValue" methodExistCallback:^(bool exist) {
          NSLog(@"method 'addValue' exist : %d",exist);
    }];
    
    setDebugMode:(bool) debug

    设置调试模式。在调试模式时,发生一些错误时,将会以弹窗形式提示,并且原生API如果触发异常将不会被自动捕获,因为在调试阶段应该将问题暴露出来。如果调试模式关闭,错误将不会弹窗,并且会自动捕获API触发的异常,防止crash。强烈建议在开发阶段开启调试模式。

    customJavascriptDialogLabelTitles:(NSDictionary*) dic

    custom the label text of javascript dialog that includes alert/confirm/prompt, the default text language is Chinese.

    自定义 javascript对话框上按钮、标签的文本,默认的文本语言是中文,你可以自定义英文,如:

    [dwebview customJavascriptDialogLabelTitles:@{
     @"alertTitle":@"Notification",
     @"alertBtn":@"OK",
     @"confirmTitle":@"",
     @"confirmCancelBtn":@"CANCEL",
     @"confirmOkBtn":@"OK",
     @"promptCancelBtn":@"CANCEL",
     @"promptOkBtn":@"OK"
    }];
    

    Javascript API

    dsBridge

    “dsBridge” 在初始化之后可用 .

    dsBridge.call(method,[arg,callback])

    同步或异步的调用Java API。

    method: Java API 名称, 可以包含命名空间。

    arg:传递给Java API 的参数。只能传一个,如果需要多个参数时,可以合并成一个json对象参数。

    callback(String returnValue): 处理Java API的返回结果. 可选参数,只有异步调用时才需要提供.

    dsBridge.register(methodName|namespace,function|synApiObject)
    dsBridge.registerAsyn(methodName|namespace,function|asyApiObject)

    注册同步/异步的Javascript API. 这两个方法都有两种调用形式:

    1. 注册一个普通的方法,如:

      In Javascript

      dsBridge.register('addValue',function(l,r){
           return l+r;
      })
      dsBridge.registerAsyn('append',function(arg1,arg2,arg3,responseCallback){
           responseCallback(arg1+" "+arg2+" "+arg3);
      })
      

      In Object-c

      // call javascript method
      [dwebview callHandler:@"addValue" arguments:@[@3,@4] completionHandler:^(NSNumber * value){
            NSLog(@"%@",value);
      }];
      
      [dwebview callHandler:@"append" arguments:@[@"I",@"love",@"you"] completionHandler:^(NSString * _Nullable value) {
           NSLog(@"call succeed, append string is: %@",value);
      }];
      

    2. 注册一个对象,指定一个命名空间:

      In Javascript

      //namespace test for synchronous
      dsBridge.register("test",{
        tag:"test",
        test1:function(){
      	return this.tag+"1"
        },
        test2:function(){
      	return this.tag+"2"
        }
      })
        
      //namespace test1 for asynchronous calls  
      dsBridge.registerAsyn("test1",{
        tag:"test1",
        test1:function(responseCallback){
      	return responseCallback(this.tag+"1")
        },
        test2:function(responseCallback){
      	return responseCallback(this.tag+"2")
        }
      })
      
    dsBridge.hasNativeMethod(handlerName,[type])

    检测Java中是否存在名为handlerName的API, handlerName 可以包含命名空间.

    type: 可选参数,["all"|"syn"|"asyn" ], 默认是 “all”.

    //检测是否存在一个名为'testAsyn'的API(无论同步还是异步)
    dsBridge.hasNativeMethod('testAsyn') 
    //检测test命名空间下是否存在一个’testAsyn’的API
    dsBridge.hasNativeMethod('test.testAsyn')
    // 检测是否存在一个名为"testSyn"的异步API
    dsBridge.hasNativeMethod('testSyn','asyn') //false
    
    展开全文
  • MNProgressHUD 项目地址:maning0303/MNProgressHUD 简介: 一个常用的自定义弹框封装(适配 AndroidX),加载 ProgressDialog,状态显示的 StatusDialog 和自定义 Toast,全部支持背景颜色,圆角,边框和文字的自定义,...
  • 之所以写这一篇博客的原因是因为之前有写过一篇《Android应用setContentView与LayoutInflater加载解析机制源码分析》,然后有人在文章下面评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow加载显示...
  • ...尊重劳动成果】 1 背景 ...之所以写这一篇博客的原因是由于之前有写过一篇《Android应用setContentView与LayoutInflater载入...然后有人在文章以下评论和微博私信中问我关于Android应用Activity、Dialog、PopWi...
  • 一、概述 相信大家都遇到过这种...如果用户点击了“立即更新”,会将应用开发商或运营商服务器上最新的应用版本更新到手机中,如果用户没有选择立即更新,则不会更新手机应用程序。 二、实现原理 服务端存放有一个
  • 文件夹 PATH 列表 卷序列号为 000A-8F50 E:. ...│ 使用说明.txt │ 免费下载更多源码.url │ 目录列表.txt │ ├─android web应用 │ jqmDemo_static.zip │ jqmMobileDemo-master.zip │ jqmMo
  • 下载地址 最后更新共计113个分类5177套源码29.2 GB。 卷 新加卷 的文件夹 PATH 列表 卷序列号为 00000200 5E7A:7F30 F:. ├─前台界面 │ ├─3D标签云卡片热门 │ │ Android TagCloudView云标签的灵活运用.rar ...
  • Android源码大放送(实战开发必备)文件夹 PATH 列表│ javaapk.com文件列表生成工具.bat│ 使用说明.txt│ 免费下载更多源码.url│ 目录列表.txt│ ├─android web应用│ jqmDemo_static.zip│ jqmMobileDemo-...
  • ——写在前面的话 在要不要写这篇文章的纠结中挣扎了好久,就我个人而已,我接触windows编程,已经六七个年头了,尤其是在我读研的三年内,基本心思都是花在学习和研究windows程序上了。我很庆幸我当初学习windows...
  • (1)在commonJS规范中,require,exports,module,__filename,__dirname都是通过动态编译添加到模块的头部的,这样就不存在全局变量污染的问题  但是他们传入的require,exports,module都是一个空对象。而且必须弄...
  • 浏览器从服务端获取网页会根据文档的DOCTYPE定义显示网页,如果文档正确定义了DOCTYPE浏览器则会进入标准模式(Standards Mode),否则浏览器会进入怪异模式或混杂模式(Quirks mode)。
1 2 3 4 5 ... 15
收藏数 293
精华内容 117
关键字:

dialog后新建线程 ios