个人认为重要的Android面试总结五

                                                     kotlin

 

kotlin为什么被设计出来?

kotlin被设计出来并被Google推广,主要有以下优势: 

  • 完全兼容Java
  • 更少的空指针异常
  • 更少的代码量,更快的开发速度

kotlin工作原理

首先,我们了解下Java的工作原理:

Java 代码是经过编译才能运行的。首先会编译成class文件,然后通过java虚拟机运行,在Android中也就是ART。

所以,任何语言只要能被编译成符合规格的class文件,就能被java虚拟机运行,也就能运行在我们的Android手机上,kotlin亦是如此。

  •  另外Android studio也提供了一个功能,可以查看kotlin对应的字节码:

Tools -> Kotlin -> Show Kotlin Bytecode   再点击Decomplie还可以反编译成Java文件。

kotlin的空安全

  • java中,我们可以任意初始化一个变量,而不需要赋值,比如String,就有它的默认值null。
String a;

如果要调用对象的参数,必须判空:

if (a!=null){
   Log.d("lz","length="+a.length());
}else{
   Log.d("lz","length=null");
}
  • kotlin中,为了保证减少空指针的问题,不允许直接设置为空,可以通过?=的方式设置可以为空。
val a: String ? = null

1)赋值的时候,可以直接使用?来表示这个对象可能为空,如果为空则表达式结果也为空,而不用进行非空判断。

//如果 b 非空,就返回 b.length,否则返回 null
val length = b?.length
//如果 b 非空,就返回 String类型的b,否则返回 null
val str = b as? String 

也就是通过问号来表示对象为空则整个表达式结果为空,而不会报错空指针。

2)如果需要设定为空的时候返回的表达式值不为空,可以用操作符?:来表示,也叫Elvis操作符。

//b为空则表达式返回-1
val length = b?.length ?: -1

3)如果要将值转换为非空类型,就可以使用 !!来标识非空,但是这种操作符就有可能会抛出空指针异常,如果实际对象为空的话。所以这种操作符相当于去除了空判断。

//如果b为空,空指针异常
val length = b!!.length

val和var

val,全称value,声明一个不可变的变量,这种变量在初始赋值之后就再也不能重新赋值了,所以相当于java中的final变量

var,全称variable(可变的),所以是用来声明一个可变的变量,可以重复赋值。

kotlin中这么设计的原因主要是把不可变变量 这种因素和可变变量拿到同一级来设计,也就是说我们以后编码设计变量的时候,必须要考虑这个变量是不可变还是可变的,养成良好习惯,不是以前在java中需要添加final这种稍微繁琐的举动。

扩展函数(Extension Function)

扩展函数,其实就是扩展类的函数,可以在已有的类中添加新的方法,比继承更加简洁优雅方便。

  • 扩展函数比如:
fun Activity.showToast( msgId:Int){
    Toast.makeText(this,msgId,Toast.LENGTH_SHORT).show()
}

这样任何的Activity里面就可以直接调用showToast方法来展示Toast了。

  • 同样,可以设置扩展属性,比如:
var <T> MutableList<T>.lastData: T
    //获取List中最后一个对象
    get() = this[this.size - 1]
    //设置List中最后一个对象的值
    set(value) {
        this[this.size - 1] = value
    }

用法:

var strs = mutableListOf<String>()
strs.lastData="heihei"
Log.e(TAG,"lastdata= ${strs.lastData}")

这里还涉及到两个知识点:

  • kotlin中,在使用对象的get和set方法,可以直接省略,直接使用属性名即可,会根据表达式的实际功能来添加对应的set或者get方法。
  • kotlin中,对于$符号表示 串模板,就是可计算的代码片段,可以将其计算结果链接到字符串中。

扩展属性原理

kotlin这个扩展功能确实设计的很巧妙,那就一起来研究下它的原理:

按照上面的方法,也就是Tools -> Kotlin -> Show Kotlin Bytecode -> Decomplie, 我们得到showToast扩展函数和使用代码所对应的java代码:

//扩展函数
public final class UtilsKt {
   public static final void showToast(@NotNull Activity $this$showToast, int msgId) {
      Intrinsics.checkParameterIsNotNull($this$showToast, "$this$showToast");
      Toast.makeText((Context)$this$showToast, msgId, 0).show();
   }
}

//使用
UtilsKt.showToast(this, 1900026);

可以看到所谓的扩展函数不过就是自动生成一个带有当前对象的函数,扩展函数的所在类被public final修饰,函数被public static final修饰,然后扩展的那个类被作为方法的一个参数传进去,这样就跟我们用java的时候写的工具类很像。

然后使用的时候就跟我们使用工具类一样调用工具类的方法即可。

可以定义同名的扩展方法吗?

在同一个包名下,是不可以定义相同类相同方法名的扩展方法的。但是,在不同包名下,是可以定义的。

比如我在不同的包名下定义了相同的扩展方法:

//Utils2.kt
package com.example.studynote.kotlin

fun Activity.showToast(msg:String){
    Toast.makeText(this,msg,Toast.LENGTH_LONG).show()
}



//Utils.kt
package com.example.studynote

fun Activity.showToast(msg:String){
    Toast.makeText(this,msg,Toast.LENGTH_SHORT).show()
}

具体会用哪个呢?就要看你导入的包是哪个了~

扩展方法可以覆盖掉某个类的已有方法吗?

肯定是不能的,如果一个类的扩展方法和它已有方法同名,是可以编译过的。

但是调用的时候会优先调用类中本来就有的方法,而不是扩展方法。

kotlin中有没有用到的时候

kotlin中一般会把;省略,但是有两种情况还是会用到:

  • 枚举中,如果有方法的情况,必须用;来分割枚举常量列表和方法
enum class Color {
    RED,
    BLACK,
    BLUE,
    GREEN,
    WHITE;

    fun getTopColor():Color {
        return BLACK
    }
}
  • 两个表达式在一行的时候,当然这种有点累赘,为啥要写成一行呢是吧:
var test="nihao" ; var test2="heihei"

let、apply、with、run

  • let 默认当前这个对象作为闭包的it参数,返回值为函数最后一行,或者return
fun getInt():Int{
        "jimu".let {
            println(it.length)
            return 0
        }
    }
  • apply 在apply函数范围内,可以任意调用该对象的任意方法,并返回该对象
fun getInt(): Int {
        return ArrayList<String>().apply {
            add("jimu")
        }.size
    }
  • with 返回值是最后一行,这点类似let。可以直接调用对象的方法,这点类似apply。
fun getInt(): Int {
        return with(ArrayList<String>()){
            add("jimu")
            size
        }
  • run run和with很像,可以调用对象的任意函数,返回值是最后一行
fun getInt(): Int {
        return ArrayList<String>().run{
            add("jimu")
            size
        }
    }

lateinit和by lazy

上篇说过,Kotlin有空限制,所以有些变量如果不想设置为空的时候初始化该怎么做呢?这就用到延迟初始化了,lateinit和by lazy都能实现。

  • lateinit

lateinit用于修饰var变量,它会让编译器暂时忽略初始化这个事情,到后面用的时候我们在进行初始化,但是不能用到基本数据类型,比如int,double这种。

lateinit var test: String
  • by lazy

by lazy用于val类型的变量,它会暂时不进行初始化,并且在第一次使用的时候自动调用我们设置好的表达式进行初始化。

val str by lazy {
        println("Init lazy")
        "Hello World"
    }

 Kotlin中的构造函数

kotlin中构造函数分为主构造函数和次构造函数。

  • 主构造函数

主构造函数没有函数体,直接定义在类名后。每个类都会默认带一个不带参数的构造函数,也可以直接定义参数,如果需要在构造函数中进行初始化工作,可以用init关键字:

class Student {
}

class Student(var name: String) {
    init {
        Log.e(TAG,"name=$name")
    }
}

class Student constructor(var name: String) {
    init {
        Log.e(TAG,"name=$name")
    }
}
  • 次构造函数

除了类名后这种主构造函数,其他的构造函数方法就是通过constructor关键字来定义次构造函数,一个类可以定义多个次构造函数。如果主构造函数和次构造函数同时存在的时候,次构造函数必须调用主构造函数。

class Student{
    private val username: String
    constructor(username: String){
        this.username = username
    }
}


class Student(username: String) {
    private var username: String
    private var age: Int

    init {
        this.username = username
        this.age = 10
    }

    constructor(username: String, age: Int) : this(username) {
        this.age = age
    }
}

协程

Kotlin协程是对线程的一种封装,同样是用来解决并发任务(异步任务)的方案,可以理解为一种线程框架,特点是挂起时不需要阻塞线程,更好的解决了线程切换,魔鬼调用的问题。

简单举个例子,具体的说明大家可以翻翻以前的文章——协程三问。

GlobalScope.launch(Dispatchers.Main) {
    var name = ioTask()
    updateUI(name)
    var name1 = ioTask()
    updateUI(name1)
    var name2 = ioTask()
    updateUI(name2)
}

private suspend fun ioTask(): String {
    var name = ""
    withContext(Dispatchers.IO) {
        //耗时操作,比如网络接口访问
        name = "jimu"
    }
    return name
}
  • GlobalScope.launch去开启一个协程
  • Dispatchers.Main表示运行在主线程
  • suspend关键字用于标记挂起函数的关键字
  • withContext函数用来构建一个协程作用域,可以标明作用线程,比如这里的Dispatchers.IO。这个函数必须在挂起函数或者协程中执行

                             说说插值器和估值器

  • 插值器

一般指时间插值器TimeInterpolator,是设置 属性值 从初始值过渡到结束值 的变化规律,比如匀速,加速,减速等等。可以通过xml属性和java代码设置。

系统默认的插值器是AccelerateDecelerateInterpolator,即先加速后减速。

//匀速插值器设置
android:interpolator="@android:anim/linear_interpolator"

alphaAnimation.setInterpolator(new LinearInterpolator());

属性动画中,插值器的含义就是要设置时间和属性的变化关系,也就是根据动画的进度(0%-100%)通过逻辑计算 计算出当前属性值改变的百分比。比如匀速关系就是动画进度和属性值改变的进度保持一致,50%时间进度就完成了属性值50%的变化。

//自定义匀速插值器
public class MyLinearInterpolator implements TimeInterpolator {

    @Override
    public float getInterpolation(float input) {
        return input;
    }
  • 估值器

又叫类型估值算法TypeEvaluator,用来设置 属性值 从初始值过渡到结束值 的变化具体数值,刚才介绍的插值器是指变化规律,而这个估值器是决定具体的变化数值,是用来协助插值器完成动画设置。

比如属性动画设置:

ObjectAnimator anim = ObjectAnimator.ofObject(view, "scale", new IntEvaluator(),1,10);

//系统估值器类型
IntEvaluator:针对整型属性 
FloatEvaluator:针对浮点型属性 
ArgbEvaluator:针对Color属性

可以看看IntEvaluator源码,其实就是根据三个参数—估值小数(fraction),开始值(startValue)和 结束值(endValue)然后计算具体属性变化的值:

public class IntEvaluator implements TypeEvaluator<Integer> {

    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

所以要实现一个完整的属性动画,需要估值器和插值器进行协同工作:

  • 首先由TimeInterpolator(插值器)根据时间流逝的百分比计算出当前属性值改变的百分比,并且 插值器 将这个百分比返回,这个时候 插值器 的工作就完成了。
  • 比如 插值器 返回的值是0.5,很显然我们要的不是0.5,而是当前属性的值,即当前属性变成了什么值,这就需要 估值器根据当前属性改变的百分比来计算改变后的属性值,根据这个属性值,我们就可以设置当前属性的值了。

                            使用动画的注意事项

  • OOM问题:这个问题主要出现在帧动画中,当图片数量较多且图片较大时就极易出现OOM,这个在实际开发中要尤其注意,尽量避免使用帧动画。
  • 内存泄露:在属性动画中有一类无限循环的动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄露,通过验证后发现View动画并不存在此问题。
  • 兼容性问题:动画在3.0以下的系统有兼容性问题,在某些特殊场景可能无法正常工作,因此要做好适配工作。
  • View动画的问题:View动画是对View的影像做动画,并不是真正改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GOEN)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决问题。
  • 不要使用px:在进行动画的过程中,要尽量使用dp,使用px会导致在不用的设备上有不用的效果。
  • 动画元素的交互:从3.0开始,将view移动(平移)后,属性动画的单击事件触发位置为移动后的位置,但是View动画仍然在原位置。在Android3.0以前的系统中,不管是View动画还是属性动画,新位置都无法触发单击事件同时,老位置仍然能触发单击事件(因为属性动画在Android3.0以前是没有的,是通过兼容包实现的,底层也是调用View动画)。
  • 硬件加速:使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。

关于硬件加速,直观上说就是依赖GPU实现图形绘制加速,同软硬件加速的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制

                            RxJava的订阅关系

Observable.create(new ObservableOnSubscribe<Integer>() {
        @Override
        public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Throwable {
            emitter.onNext(1);
            emitter.onComplete();
        }
    }).subscribe(new Observer<Integer>() {
    @Override
    public void onNext(Integer integer) {
        Log.d(TAG, "onNext: " + integer);
    }

    @Override
    public void onCompleted() {
    }

    @Override
    public void onError(Throwable e) {
        Toast.makeText(activity, "Error!", Toast.LENGTH_SHORT).show();
    }
});

代码中主要有三个角色:

  • 被订阅者Observable,是整个事件的来源,可以发射数据给订阅者。
  • 订阅者Observer,通过subscribe方法和被订阅者产生关系,也就是开始订阅,同时可以接受被订阅者发送的消息。
  • 发射器Subscriber/Emitter,在Rxjava2之后,发射器改为了Emitter,他的作用主要是用来发射一系列事件的,比如next事件,complete事件等等。

有了这三个角色,一个完整的订阅关系也就生成了。

                           Observer处理完onComplete后会还能onNext吗?

要弄清楚这个问题,得去看看onComplete,onNext方法到底做了什么。

@Override
    public void onComplete() {
        if (!isDisposed()) {
            try {
                 observer.onComplete();
            } finally {
                 dispose();
            }
      }
    }


    @Override
    public void onNext(T t) {
        if (t == null) {
              onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
             return;
        }
        if (!isDisposed()) {
             observer.onNext(t);
       }
    }


    public static boolean isDisposed(Disposable d) {
        return d == DISPOSED;
    }

    public static boolean dispose(AtomicReference<Disposable> field) {
        Disposable current = field.get();
        Disposable d = DISPOSED;
        if (current != d) {
            current = field.getAndSet(d);
            if (current != d) {
                if (current != null) {
                    current.dispose();
                }
                return true;
            }
        }
        return false;
    }

源码还是比较清晰明了,无论是onComplete还是onNext,都会判断当前订阅是否被取消,也就是Disposable类型的变量的引用是否等于DISPOSED,如果等于则代表该订阅已经被取消,起点和终点已经断开联系。而在onComplete方法的结尾调用了dispose方法,将原子引用类中的 Disposable 对象设置为 DisposableHelper 内的 DISPOSED 枚举实例,即断开订阅关系,所以在这之后所有的onNext,onComplete,onError方法中的isDisposed判断都不会通过,也就不会执行后续的数据发送等处理了

                            RxJava中的操作符

  • concatMap
  • flatMap

这两个操作符的功能是一样的,都是将一个发射数据的Observable变换为多个Observables,然后将它们发射的数据放进一个单独的Observable。区别在于concatMap是有序的,flatMap是无序的,concatMap最终输出的顺序与原序列保持一致,而flatMap则不一定,有可能出现交错。

举个例子,发送数字01234,通过操作符对他们进行+1处理,发送2的时候进行一个延时:

Observable.fromArray(1,2,3,4,5)
                .flatMap(new Function<Integer, ObservableSource<Integer>>() {
                    @Override
                    public ObservableSource<Integer> apply(@NonNull Integer integer) throws Exception {

                        int delay = 0;
                        if(integer == 2){
                            delay = 500;//延迟发射
                        }
                        return Observable.just(integer*10).delay(delay, TimeUnit.MILLISECONDS);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<Integer>() {
            @Override
            public void accept(@NonNull Integer integer) throws Exception {
                Log.e("jimu","accept:"+integer);
            }
        });

如上述操作,最终打印结果为:10,20,40,50,30。因为发送数字2的时候,进行了延时。

但是如果flatMap操作符改成concatMap,打印结果就是10,20,30,40,50了,这是因为concatMap是有序的,会按照原序列的顺序进行变换输出。

  • merge、concat、zip,合并

这几个操作符是用作合并发射物的,可以将多个Obserable和并成一个Obserable:

Observable<Integer> odds=Observable.just(1,2,3,4);
Observable<Integer> events=Observable.just(5,6,7,8);
Observable.merge(odds,events).subscribe(i->Log.d("TAG","merge->"+i));

区别在于concat操作符是在合并后按顺序串行执行,merge操作符是在合并后按时间线并行执行,如果出现某个数据进行延时发射,那么结果序列就会发生变化。

而zip操作符的特点是合并之后并行执行,发射事件和最少的一个相同,什么意思呢?比如一个发送两个数据的Obserable和一个发射4条数据的Obserable进行zip合并,那么最终只会有两条数据被发射出来,看个例子:

Observable
   .zip(Observable.just(1,2),Observable.just(3,4,5,6),new BiFunction<Integer, Integer, Integer>() {
            @Override
            public Integer apply(@NonNull Integer response, @NonNull Integer response2) throws Exception {
                //将两个发射器的结果相加
                return response+response2;
            }
        })
        .subscribe(new Consumer<Integer>() {
                    @Override
                    public void accept(@NonNull Integer s) throws Exception {
                        Log.e("lz","accept+"+s);
                    }
                });

结果只会有两条数据:4,6。第二个发射器发射的后面两条数据会被抛弃。

  • interval,周期执行

这个操作符主要用作定时周期任务,比如我需要每100ms发送一次数据:

Observable.interval(100, TimeUnit.MILLISECONDS)
                  .subscribe(new Observer<Long>() {
                    @Override
                    public void onCompleted() {
                    }

                    @Override
                    public void onError(Throwable e) {
                    }

                    @Override
                    public void onNext(Long aLong) {
                    }
                });
  • timer,delay延迟发送数据

这两个操作符都是用作延时发送数据,不同在于timer是创建型操作符,而delay是辅助型操作符。意思就是timer操作符是可以直接创建一个Observable,然后在订阅之后延时发送数据项,看例子:

Observable
  .timer(1000,TimeUnit.MILLISECONDS)
  .subscribeOn(Schedulers.io())
  .subscribe(disposableObserver);

而delay是当原始的Observable发送数据后,启动一个定时器,然后延时将这个数据发送,所以它相当于是处在上游与下游之间的一个辅助项,用作延时发送,它的作用对象必须是个创建好的Observable:

Observable
  .just(0L)
  .doOnNext(new Consumer<Long>() {
            @Override
            public void accept(Long aLong) throws Exception {
            }
        }
  .timer(1000,TimeUnit.MILLISECONDS)
  .subscribeOn(Schedulers.io())
  .subscribe(disposableObserver);

                           Drawable、Canvas、Bitmap

Drawable表示的是一种可以在Canvas上进行绘制的抽象的概念,种类很多,最常见的颜色和图片都可以是一个Drawable。

所以他是一种抽象的概念,是表示一种图像,但是又不全是图片,也可以表示颜色,一般被用作View的背景或者填充图。

到这里有的小伙伴可能又要问了,Canvas又是什么呢?

Canvas一个矩形区域, 没有边界, 我们可以在上面调用任意drawXXX开头的方法绘制到引用的Bitmap上. 同时提供对图形的处理, 比如裁剪, 缩放, 旋转(需要Matrix对象配合使用). 所以Canvas与其说是画布, 不如说是一个绘制工具,它实际操作和存储的像素数据都在它的私有成员 Bitmap 对象中,所谓的画到Canvas画布上,其实就是画到其Bitmap,存储到其Bitmap变量中

说到这,又要提下Bitmap了:

Bitmap是一个存储格式, 存储了一个矩形区域内各像素点的信息. 这种格式适合显示, 但是存储效率低.可以理解为int[] buffer,用来保存每个像素的信息。

所以这三者的关系简单点就是:

Drawable表示一个可以被绘制的图像,是一个抽象概念,需要通过Canvas绘制到Bitmap上,作为一个图像的存储。所以Bitmap是Drawable存在的一种实体。

                           Drawable分类

Drawable的种类很多,这里列举几个比较常用的,具体代码使用方式可以看往期文章这次来把Drawable翻了个遍:

https://mp.weixin.qq.com/s?__biz=MzU0MTYwMTIzMw==&mid=2247484547&idx=1&sn=63992f7a05331ceedcae8059ff322ea7&scene=21#wechat_redirect

  • BitmapDrawablw 最常用的Drawable,表示的就是一张图片,一张带规则的图片,可以设置一些规则,比较适用于对图片有限制的情况,比如背景图。
  • NinePatchDrawable 和BitmapDrawablw类似,使用时src传.9图片即可。实际上BitmapDrawablw也可以直接用.9图片,所以这个NinePatchDrawable没有太大的实际作用。
  • ShapeDrawable 这个很常见,一般纯颜色的图形就用这个画出来。
  • LayerDrawable

这是一种层次化的Drawable,相当于一种Drawable的布局嵌套,或者说集合,通过不同的Drawable放置到不同的层上达到一种叠加后的效果。

  • StateListDrawble

一般用作点击效果,对应标签是selector,可以设置按下,点击后,不点击时候的各种状态。

  • LevelListDrawable

和LayerDrawable类似,也是一个Drawable的集合,但是他是有等级的概念的,也就是可以通过设置不同的等级来展示对应的drawable,有点像一个有多种样式的Drawable,可以通过代码来展示哪一个样式。可以通过ImageView的setImageLevel方法来切换Drawable。

  • TransitionDrawable

该Drawable可以实现两个Drawable之间的淡入淡出,对应的标签是transition。

  • InsetDrawable

可以将其他的Drawable内嵌到自己当中,当一个View希望自己的背景比自己的实际区域小的时候,就可以采用这个,通过LayerDrawable也可以实现。一般用作加大点击区域。

  • ScaleDrawable

用于缩放,并且和它的等级Level有关,等级为0则不可见。

  • ClipDrawable

用作裁剪,会根据自己的等级来裁剪一个Drawable,等级0表示完全裁剪,即整个Drawable都不可见,等级10000表示不裁剪,所以主要是通过控制drawable的等级来完成裁剪功能

                           Window是什么?在Android中都用到了哪些地方?

  • 首先,它是一个窗口,是Android中唯一的展示视图的中介,所有的视图都是通过Window来呈现的,无论是Activity,Dialog或Toast,他们的视图都是附加到WIndow上的,所以Window是View的直接管理者。
  • Window是一个抽象类,他的具体实现就是PhoneWindow。
  • Window的具体实现在WindowManagerService中,但是创建Window或者访问Window的操作都需要WindowManager。所以这就需要WindowManager和WindowManagerService进行交互,交互的方式就是通过IPC,具体涉及的参数就是token。
  • 每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl建立联系,所以Window并不是实际存在的,而是以View的形式存在。

涉及到Window的地方:

  • 事件分发机制。界面上事件分发机制的开始都是这样一个过程:DecorView——>Activity——>PhoneWindow——>DecorView——>ViewGroup。之前看过一个比较有趣的问题:事件到底是先到DecorView还是先到Window的?,其实是先到DecorView的,具体逻辑可以自己翻下源码,有机会也可以出篇文章讲讲~
  • 各种视图的显示。比如Activity的setContentView,Dialog,Toast的显示视图等等都是通过Window完成的。

                        Window的分层和类别?

  • 由于界面上有不止一个的Window,所以就有了分层的概念。每个Window都有自己对应的Window层级—z-ordered,层级大的会覆盖到层级小的上面,类似HTML中的z-index。

Window主要分为三个类别:

  • 应用Window。对应着一个Activity,Window层级为1-99,在视图最下层。
  • 子Window。不能单独存在,需要附属在特定的父Window之中(如Dialog就是子Window),Window层级为1000~1999。
  • 系统Window。需要声明权限才能创建的Window,比如Toast和系统状态栏,Window层级为2000~2999,处在视图最上层。

                      Window的内部机制—添加、删除、更新。

  • 之前说过,Window的操作都是通过WindowManager来完成的,而WindowManager是一个接口,他的实现类是WindowManagerImpl,并且全部交给WindowManagerGlobal来处理。下面具体说下addView,updateViewLayout,和removeView。

1) addView

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      //...
        ViewRootImpl root;
        View panelParentView = null;

            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

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

            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 首先,通过add方法修改了WindowManagerGlobal中的一些参数,比如mViews—存储了所有Window所对应的View,mRoots——所有Window所对应的ViewRootImpl,mParams—所有Window对应的布局参数。
  • 其次,setView方法主要完成了两件事,一是通过requestLayout方法完成异步刷新界面的请求,进行完整的view绘制流程。其次,会通过WindowSession进行一次IPC调用,交给到WMS来实现Window的添加。

 2)updateViewLayout

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

这里更新了WindowManager.LayoutParams和ViewRootImpl.LayoutParams,然后在ViewRootImpl内部同样会重新对View进行绘制,最后通过IPC通信,调用到WMS的relayoutWindow完成更新。

3)removeView

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }


    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

该方法中,通过view找到mRoots中的对应索引,然后同样走到ViewRootImpl中进行View删除工作,通过die方法,最终走到dispatchDetachedFromWindow()方法中,主要做了以下几件事:

  • 回调onDetachedFromeWindow。
  • 垃圾回收相关操作;
  • 通过Session的remove()在WMS中删除Window;
  • 通过Choreographer移除监听器

 

                       Window中的Token是什么?

public abstract class Window {
    private IBinder mAppToken;
}
  • 是Window类中的一个变量,是一个Binder对象。在Window中主要是实现WindowManagerService和应用所在的进程通信,也就是上文说到的WindowManager和WindowManagerService进行交互。
  • 是一个添加view的权限标识。拥有token的context可以创建界面、进行UI操作,而没有token的context如service、Application,是不允许添加view到屏幕上的。所以它存在的意义就是为了保护window的创建,也是为了防止Application或Service来做进行view或者UI相关的一些操作。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值