美文网首页面试安卓Java Android 面试题
安卓面试知识点大全(适合中高级)

安卓面试知识点大全(适合中高级)

作者: 小夫哥 | 来源:发表于2019-05-31 17:53 被阅读0次

    JAVA

    8种基本类型

    1.byte(位)8位
    2.short(短整数)16位
    3.int(整数)32位
    4.long(长整数)64位
    5.float(单精度)32位
    6.double(双精度)64位
    7.char(字符)16位
    8.boolean(布尔值)8位

    int跟Integer区别

    1.int是常用数据类型,Integer是int的封装类;
    2.int类的变量初始为0,而Integer的变量则初始化为null;
    3.int和Integer都可以表示某一个数值;
    4.int和Integer不能够互用,因为他们两种不同的数据类型;

    try...catch...finally的运行机制

    1. 只有当try代码块发生异常的时候,才会执行到catch代码块

    2. 不管try中是否发生异常,finally都会执行。
      以下两种情况例外:
      一:try中不发生异常时,try块中有System.exit(0);
      二:try中发生异常时,catch中有System.exit(0);
      说明:System.exit(0) 代码的作用的退出虚拟机;

    3. 若finally块内有return语句,则以finally块内的return为准
      说明:
      如果try 或者 catch内也有return 其实是先执行了try 或者 catch代码块中的return语句的,
      但是由于finally的机制,执行完try或者catch内的代码以后并不会立刻结束函数,还会执行finally块代码,
      若finally也有return语句,则会覆盖try块或者catch块中的return语句
      若finally代码块中有return语句,则屏蔽catch代码块中抛出的异常,否则,异常会在finally之后抛出。

    抽象类和接口的区别

    抽象类(abstract class)

    使用abstract修饰符修饰的类。官方点的定义就是:如果一个类没有包含足够多的信息来描述一个具体的对象,这样的类就是抽象类。但抽象类不能实例化。

    • 只声明,不实现,具体的实现要由子类实现,但是如果子类将抽象方法没有全部实现,就必须把自己也修饰成抽象类,交于继承它的子类来完成实现。
    • 实现抽象类使用extends关键字来继承抽象类。
    • 抽象类可以有构造器
    • 抽象类可以有一些非抽象方法的存在,这些方法被称为默认实现。如果添加一个默认实现方法(不能是抽象方法),就不需要在子类中去实现,所以继承这个抽象类的子类无须改动。

    接口(interface)

    接口在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。接口和抽象类一样不能被实例化。

    • 接口中的方法必须是抽象的(不能实现)
    • 接口可以被实现(使用 implements 关键字)。实现某个接口的类必须在类中实现该接口的全部方法。
    • 接口中除了static、final变量,不能有其他变量
    • 接口支持多继承(一个类可以实现多个接口)
    • 接口中没有构造方式(因为接口不是类)
    • 接口中只能添加抽象方法,当你添加了抽象方法,实现该接口的类就必须实现这个新添加的方法。

    equals()与hashCode()

    equals()

    用来判断两个对象是否相等。

    • 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
    • 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。
    • java对equlas()的要求
    1. 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。
    2. 反射性:x.equals(x)必须返回是"true"。
    3. 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。
    4. 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。
    5. 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。

    equals() 与 == 的区别是什么?

    • == : 它的作用是判断两个对象的地址是不是相等。
    • equals() : 它的作用是判断两个对象的值是否相等。

    hashCode()

    获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
    注意:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

    hashCode() 和 equals() 的关系

    1. 第一种 不会创建“类对应的散列表”
    • 这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。
      在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!
      这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。
    1. 第二种 会创建“类对应的散列表”
    • 这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。
      在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
      1.如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
      2.如果两个对象hashCode()相等,它们并不一定相等。
      因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。
    • 此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。

    算法

    快速排序

    通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    冒泡算法

    冒泡排序其实是基于“交换”。每次从第一个记录开始,一、二两个记录比较,大的往后放,二三两个记录比较...依次类推,这就是一趟冒泡排序。每一趟冒泡排序后,无序序列中值最大的记录冒到序列末尾,所以称之为冒泡排序。

    选择排序算法

    暂定第一个元素为最小元素,往后遍历,逐个与最小元素比较,若发现更小者,与先前的"最小元素"交换位置。达到更新最小元素的目的。
    一趟遍历完成后,能确保刚刚完成的这一趟遍历中,最的小元素已经放置在前方了。然后缩小排序范围,新一趟排序从数组的第二个元素开始。
    在新一轮排序中重复第1、2步骤,直到范围不能缩小为止,排序完成。

    JSON与XML

    JSON

    一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。JSON采用兼容性很高的文本格式。

    XML

    扩展标记语言(Extensible Markup Language),用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。

    优缺点
    1.在可读性方面,JSON和XML的数据可读性基本相同。JSON和XML的可读性可谓不相上下,一边是建议的语法,一边是规范的标签形式,很难分出胜负。 
    2.在可扩展性方面,XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,JSON不能的。 
    3.在编码难度方面,XML有丰富的编码工具,比如Dom4j、JDom等,JSON也有json.org提供的工具,但是JSON的编码明显比XML容易许多,即使不借助工具也能写出JSON的代码,可是要写好XML就不太容易了。 
    4.在解码难度方面,XML的解析得考虑子节点父节点,让人头昏眼花,而JSON的解析难度几乎为0。这一点XML输的真是没话说。 
    5.在流行度方面,XML已经被业界广泛的使用,而JSON才刚刚开始,但是在Ajax这个特定的领域,未来的发展一定是XML让位于JSON。到时Ajax应该变成Ajaj(Asynchronous JavaScript and JSON)了。 
    6.JSON和XML同样拥有丰富的解析手段。 
    7.JSON相对于XML来讲,数据的体积小。
    8.JSON与JavaScript的交互更加方便。
    9.JSON对数据的描述性比XML较差。
    10.JSON的速度要远远快于XML。

    Synchronized关键字

    • synchronized是java中表示同步代码块的关键字。可以放在方法修饰符前,比如private synchronized void test(){},也可以放在方法内部,修饰某一段特定的代码。
    • synchronized有一个地方需要注意,就是在给普通方法加锁与给静态方法加锁机制是不一样的。
    • synchronized在静态方法上表示调用前要获得类的锁,而在非静态方法上表示调用此方法前要获得对象的锁。
    public class StaticSynDemo { 
     
    private static String a="test"; 
     
    //等同于方法print2 
    public synchronized void print1(String b){ //调用前要取得StaticSynDemo实例化后对象的锁 
       System.out.println(b+a); 
    } 
    public void print2(String b){ 
       synchronized (this) {//取得StaticSynDemo实例化后对象的锁 
        System.out.println(b+a); 
       } 
    } 
    //等同于方法print4 
    public synchronized static void print3(String b){//调用前要取得StaticSynDemo.class类的锁 
       System.out.println(b+a); 
    } 
    public static void print4(String b){ 
       synchronized (StaticSynDemo.class) { //取得StaticSynDemo.class类的锁 
        System.out.println(b+a); 
       } 
    

    基础

    Activity启动模式

    activity有四种启动模式,分别为standard,singleTop,singleTask,singleInstance。如果要使用这四种启动模式,必须在manifest文件中<activity>标签中的launchMode属性中配置,如:

    <activity android:name=".app.InterstitialMessageActivity"
                      android:label="@string/interstitial_label"
                      android:theme="@style/Theme.Dialog"
                      android:launchMode="singleTask"
            </activity>
    

    standard 默认模式

    标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A-->A。

    singleTop 栈顶复用模式

    • 如果一个以singleTop模式启动的activity的实例已经存在于任务桟的桟顶,那么再启动这个Activity时,不会创建新的实例,而是重用位于栈顶的那个实例,并且会调用该实例的onNewIntent()方法将Intent对象传递到这个实例中。举例来说,如果A的启动模式为singleTop,并且A的一个实例已经存在于栈顶中,那么再调用startActivity(new Intent(this,A.class))启动A时,不会再次创建A的实例,而是重用原来的实例,并且调用原来实例的onNewIntent()方法。这是任务桟中还是这有一个A的实例。
    • 如果以singleTop模式启动的activity的一个实例已经存在与任务桟中,但是不在桟顶,那么它的行为和standard模式相同,也会创建多个实例。

    singleTask 栈内复用模式

    在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。

    singleInstance 全局唯一模式

    该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

    Activity生命周期

    Activity生命周期图
    • 一个Activity的启动顺序:
      onCreate()——>onStart()——>onResume()
    • 当另一个Activity启动时:
      第一个Activity onPause()——>第二个Activity onCreate()——>onStart()——>onResume()
      ——>第一个Activity onStop()
    • 当返回到第一个Activity时:
      第二个Activity onPause() ——> 第一个Activity onRestart()——>onStart()——>onResume()
      ——>第二个Activity onStop()——>onDestroy()

    Fragment生命周期

    onAttach()

    执行该方法时,Fragment与Activity已经完成绑定,该方法有一个Activity类型的参数,代表绑定的Activity,这时候你可以执行诸如mActivity = activity的操作。

    onCreate()

    初始化Fragment。可通过参数savedInstanceState获取之前保存的值。

    onCreateView()

    初始化Fragment的布局。加载布局和findViewById的操作通常在此函数内完成,但是不建议执行耗时的操作,比如读取数据库数据列表。

    onActivityCreated()

    执行该方法时,与Fragment绑定的Activity的onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,所以在该方法之前Activity的onCreate方法并未执行完成,如果提前进行交互操作,会引发空指针异常。

    onStart()

    执行该方法时,Fragment由不可见变为可见状态。

    onResume()

    执行该方法时,Fragment处于活动状态,用户可与之交互。

    onPause()

    执行该方法时,Fragment处于暂停状态,但依然可见,用户不能与之交互。

    onSaveInstanceState()

    保存当前Fragment的状态。该方法会自动保存Fragment的状态,比如EditText键入的文本,即使Fragment被回收又重新创建,一样能恢复EditText之前键入的文本。

    onStop()

    执行该方法时,Fragment完全不可见。

    onDestroyView()

    销毁与Fragment有关的视图,但未与Activity解除绑定,依然可以通过onCreateView方法重新创建视图。通常在ViewPager+Fragment的方式下会调用此方法。

    onDestroy()

    销毁Fragment。通常按Back键退出或者Fragment被回收时调用此方法。

    onDetach()

    解除与Activity的绑定。在onDestroy方法之后调用。

    setUserVisibleHint()

    设置Fragment可见或者不可见时会调用此方法。在该方法里面可以通过调用getUserVisibleHint()获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。

    Fragment生命周期执行流程

    Fragment创建

    setUserVisibleHint()->onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume();

    Fragment变为不可见状态(锁屏、回到桌面、被Activity完全覆盖):

    onPause()->onSaveInstanceState()->onStop();

    Fragment变为部分可见状态(打开Dialog样式的Activity):

    onPause()->onSaveInstanceState();

    Fragment由不可见变为活动状态

    onStart()->OnResume();

    Fragment由部分可见变为活动状态

    onResume();

    Fragment退出

    onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()(注意退出不会调用onSaveInstanceState方法,因为是人为退出,没有必要再保存数据);

    Fragment被回收又重新创建

    被回收执行onPause()->onSaveInstanceState()->onStop()->onDestroyView()->onDestroy()->onDetach(),重新创建执行onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->setUserVisibleHint();

    横竖屏切换

    与Fragment被回收又重新创建一样。

    Service

    采用start的方式开启服务

    使用这种start方式启动的Service的生命周期如下:

    onCreate()--->onStartCommand()(onStart()方法已过时) ---> onDestory()
    说明:如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStart()和onStartCommand()。
    服务停止的时候调用 onDestory()。服务只会被停止一次。

    特点:一旦服务开启跟调用者(开启者)就没有任何关系了。
    开启者退出了,开启者挂了,服务还在后台长期的运行。
    开启者不能调用服务里面的方法。

    采用bind的方式开启服务

    使用这种start方式启动的Service的生命周期如下:

    onCreate() --->onBind()--->onunbind()--->onDestory()
    注意:绑定服务不会调用onstart()或者onstartcommand()方法

    特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。
    绑定者可以调用服务里面的方法。

    广播

    分类

    • 标准广播
      是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。
      1.同级别接收先后是随机的(无序的),级别低的后接收到广播
      2.接收器不能截断广播的继续传播,也不能处理广播
      3.同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
    • 有序广播
      是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。
      1.同级别接收是随机的(结合下一条)
      2.同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
      3.排序规则为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序
      4.先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。

    注册的方式分类

    • 动态注册广播
      在代码中注册,必须在运行时才能进行,接收广播速度要优于静态注册。
    • 静态注册广播
      主要是在AndroidManifest中进行注册。

    广播的安全性问题

    Android中的广播可以跨进程甚至跨App直接通信,且exported属性在有intent-filter的情况下默认值是true,由此将可能出现的安全隐患如下:

    • 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
    • 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

    增加安全性的方案包括:

    • 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
    • 在广播发送和接收时,都增加上相应的permission,用于权限验证;
    • 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
    • 采用LocalBroadcastManager的方式。使用该机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本地应用程序发出的广播。

    ContentProvider内容提供者

    • ContentProvider(内容提供者)是android中的四大组件之一,它为不同的软件之间数据共享,提供统一的接口。而且ContentProvider是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URI来表示外界需要访问的“数据库”。
    • 也就是说,如果我们想让其他的应用使用我们自己程序内的数据,就可以使用ContentProvider定义一个对外开放的接口,从而使得其他的应用可以使用我们自己应用中的文件、数据库内存储的信息。当然,自己开发的应用需要给其他应用共享信息的需求可能比较少见,但是在Android系统中,很多数据如:联系人信息、短信信息、图片库、音频库等,这些信息在开发中还是经常用到的,这些信息谷歌工程师已经帮我们封装好了,我们可以使用谷歌给我的Uri去直接访问这些数据。
    • 当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式,这也是为什么会有内容提供者的原因。

    强引用、软引用、弱引用、虚引用

    强引用(StrongReference)

    • 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。
    • 当内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
    • 如果强引用对象不使用时,需要弱化从而使GC能够回收,将对象置空则弱化。

    软引用(SoftReference)

    • 如果一个对象只具有软引用,则内存空间充足时,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
    • 软引用可以和一个引用队列(ReferenceQueue)联合使用。如果软引用所引用对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。

    弱引用(WeakReference)

    • 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    • 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    虚引用(PhantomReference)

    • 虚引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
    • 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

    Android各版本新特性

    Android 5.x

    1.Material design,算是Android 系统风格的里程碑,其3D UI风格新颖,贴近人机交互;
    2.改善通知栏,提升可视化、亲近性、可编辑性。同时支持手机在锁屏状态也可接收到通知,用户可以在锁屏状态下,设置接收全部应用的通知或者接收部分应用的通知或者不接收所有应用的通知;
    3.系统由以往的Dalvik模式改为采用ART(Android Runtime)模式,实现ahead-of-time (AOT)静态编译与just-in-time (JIT)动态编译交互进行;
    4.V7中引入CardView和RecycleView等新控件;
    5.支持64位系统;

    Android 6.x

    1.新增运行时权限概念
    Android6.0或以上版本,用户可以完全控制应用权限。当用户安装一个app时,系统默认给app授权部分基础权限,其他敏感权限,需要开发者自己注意,当涉及敏感权限时,开发者需要手动请求系统授予权限,系统这时会弹框给用户,倘若用户拒绝,如果没有保护,app将直接崩溃,倘若有保护,app也无法使用相关功能。
    2.新增瞌睡模式和待机模式
    瞌睡模式:当不碰手机,手机自动关闭屏幕后,过一会,手机将进入瞌睡模式。在瞌睡模式下,设备只会定期的唤醒,然后继续执行等待中的任务接着又进入瞌睡;
    待机模式:假如用户一段时间不触碰手机,设备将进入待机模式。在这个模式下,系统会认为所有app是闲置的,这时系统会关闭网络,并且暂停app之前正在执行的任务。
    3.移除对Apache HTTP client的支持,建议使用HttpURLConnection。
    4.Doze电量管理
    Android 6.0自带Doze电量管理功能,在“Doze”模式下,手机会在一段时间未检测到移动时,让应用休眠清杀后台进程减少功耗,谷歌表示,当屏幕处于关闭状态,平均续航时间提高30%。

    Android 7.x

    1.通知栏快捷回复
    在Android N上,Android对通知栏进行了进一步的优化,其中一个非常大的改变就是让用户可以在通知栏上直接对通知进行回复,这对于一些IM类的App来说,提供了更加友好的回复功能。
    2.加入原生分屏多任务功能,多任务快速切换
    3.VR
    Android N上对VR的支持,实际上是使用了一个新的跨平台图形计算库——Vulkan,Vlukan API提升处理能力,减少GPU处理,从而获得更佳的游戏体验,所以说,如果一个手机支持VR,那么从某种意义上来说,这个手机的性能应该是很赞的!
    4.引入全新的JIT编译器,使得App安装速度快了75%,编译代码的规模减少了50%
    5.安全:更安全的加密模式,可以对单独的文件进行加密,android系统启动加密

    Android 8.x

    画中画
    1.Android O中,谷歌更加强调多任务处理场景中的流畅性,在I/O2017上,谷歌演示了增强功能的画中画模式,为用户带来不同应用程序间的流畅操作体验。例如用户可以在Netflix上观看电影,支持将电影屏幕缩小成悬浮窗口,在看电影的同时进行查看日历、搜索信息等其他工作,这和普通的画中画分屏模式并不相同。
    2.Notification Dots
    在Android O之前,使用安卓手机的用户,想要看到哪些应用程序推送了通知,可能只有在下拉通知中心中看到,但在Android O中,谷歌对安卓的通知功能做出了改进,这就是全新的Notification Dots功能,它是位于应用程序图标之上的小小的循环点,只有当应用出现未读通知时,它才会出现。这时候长按应用程序图标,就会以类似气泡的形式快速预览。而在通知中心中删除这些未读通知,应用图标上的标记点也会消失。
    3.自动填充(Auto-Fill)
    对于用户设备上最常用的应用,Android O将会帮助用户进行快速登录,而不用每次都填写账户名和密码。例如当用户使用一个新设备时,可以从Chrome中提取已经保存的账户名和密码,选择之后,自动填充功能便可以在本地进行,适用于你可能用到的大多数应用程序。开发人员也需要对其应用程序进行优化,让其应用程序能够和自动填充功能更好地兼容。
    4.自适应图标(Adaptive icons)
    Adaptive icons也是一项有趣的新功能,谷歌正在尝试整理Android中不一致的应用程序图标形状,这一功能为应用程序开发人员提供了适应其显示设备的每个图标的多个形状模板。因此,如果你的手机默认应用程序图示形状是圆角正方形,那么所有应用程序的图标都将是这个形状(前提是开发人员使用了这一功能)。也就是说,你将不再看到系统主屏上方形图标和圆形图标混合在一起的现象。
    5.后台进程限制
    谷歌表示一直在优化安卓Android的后台应用限制策略,以最大程度减小后台应用对电池的消耗和对资源的占用。在Android O的更新中,当应用被置入后台后,Android O将自动智能限制后台应用活动,主要会限制应用的广播、后台运行和位置,但应用的整体进程并没有被杀掉。不过,部分层级比较重要的应用可以不受限制,但总的来说,Android O将严格限制后台进程对手机资源的调用。
    6.运行时权限策略变化
    在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
    对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。

    Andorid 9.x

    1.利用 Wi-Fi RTT 进行室内定位
    2.显示屏缺口支持
    3.引入了多个通知增强功能
    4.提升短信体验
    5.广播渠道设置、广播和请勿打扰
    6.多摄像头支持和摄像头更新
    7.用于位图和 drawable 的 ImageDecoder
    8.JobScheduler 中的数据费用敏感度
    9.神经网络 API 1.1
    10.自动填充的改进
    11.用于 NFC 支付和安全交易的 Open Mobile API

    Webview跟原生交互

    Android去调用JS的代码

    1.通过WebView的loadUrl(),特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
    2.通过WebView的evaluateJavascript()
    优点:该方法比第一种方法效率更高、使用更简洁。

    • 因为该方法的执行不会使页面刷新,而第一种方法(loadUrl )的执行则会。
    • Android 4.4 后才可使用
      两种方法混合使用,即Android 4.4以下使用方法1,Android 4.4以上方法2

    JS去调用Android的代码

    1.通过WebView的addJavascriptInterface()进行对象映射
    被JS调用的方法必须加入@JavascriptInterface注解
    // 设置与Js交互的权限
    webSettings.setJavaScriptEnabled(true);
    // 通过addJavascriptInterface()将Java对象映射到JS对象
    mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象
    优点:使用简单
    缺点:4.4版本以下存在严重的漏洞问题,如:

    • 任意代码执行漏洞
      用@JavascriptInterface进行注解从而避免漏洞攻击
    • 密码明文存储漏洞
      关闭密码保存提醒
    WebSettings.setSavePassword(false) 
    
    • 域控制不严格漏洞
    • 对于不需要使用 file 协议的应用,禁用 file 协议
    // 禁用 file 协议;
    setAllowFileAccess(false); 
    setAllowFileAccessFromFileURLs(false);
    setAllowUniversalAccessFromFileURLs(false);
    
    • 对于需要使用 file 协议的应用,禁止 file 协议加载 JavaScript
    // 需要使用 file 协议
    setAllowFileAccess(true); 
    setAllowFileAccessFromFileURLs(false);
    setAllowUniversalAccessFromFileURLs(false);
    
    // 禁止 file 协议加载 JavaScript
    if (url.startsWith("file://") {
        setJavaScriptEnabled(false);
    } else {
        setJavaScriptEnabled(true);
    }
    

    2.通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
    解析该 url 的协议
    如果检测到是预先约定好的协议,就调用相应方法
    优点:不存在方式1的漏洞;
    缺点:JS获取Android方法的返回值复杂。
    3.通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

    RecycleView与ListView区别

    RecycleView

    1.支持线性布局、网格布局、瀑布流布局
    2.简单实现item动画
    3.HeaderView、FooterView、EmptyView这些View需要自行实现
    4.支持局部刷新
    5.需要自定义监听item点击事件

    性能优化

    1.数据处理和视图加载分离。数据的处理逻辑放在异步处理,ViewHolder 就可以简单无压力地做数据与视图的绑定逻辑。
    2.数据优化。分页拉取远端数据,对拉取下来的远端数据进行缓存。
    3.布局优化。减少过渡绘制,减少 xml 文件 inflate 时间,这种 inflate 带来的损耗是相当大的,此时我们可以用代码去生成布局,即 new View() 的方式。
    4.把默认动画关闭来提升效率。
    5.设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程中停止加载的操作。
    6.如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源。

    ListView

    1.仅支持垂直线性布局
    2.实现item动画复杂
    3.有HeaderView、FooterView、EmptyView这些View的Api
    4.不支持局部刷新
    5.拥有监听item点击事件

    性能优化

    1.convertView的复用,因为每次都去加载xml布局会损耗性能。
    2.ViewHolder的使用,findViewById这个方法是从ViewGroup的子View里面循环遍历找id与给出的ID相同的子View,还是比较耗时的。点击事件监听也可以写在这里面去。
    3.图片缓存。我们尽量不要在ListView滑动的时候加载网络图片。
    4.少在getView里面new对象,避免做耗时操作。

    加载大图优化

    1.质量压缩。
    2.大小尺寸压缩。
    3.LruCache缓存。

    Handler,Looper,MessageQueue

    Handler

    • Handler是Android消息机制的上层接口,Handler可以用来更新UI的。Handler的主要用于同一个进程间的线程通信,Handler用于更新UI的时候是"子线程与主线程通信";当然,Handler也可以用于子线程之间通信。
    • Handler的消息机制主要是就指“Handler的运行机制”,Handler的运行机制时需要底层的MessageQueue和Looper支持的。

    MessageQueue

    MessageQueue翻译过来是"消息队列"的意思,实际上它内部的数据结构不是队列,而是单向链表;MessageQueue中储存了大量的消息,由于一个线程同一时间只能处理一条消息,所以我们建了一个链表,将我们需要处理的消息按顺序储存起来,然后一项一项的交给需要的线程处理,这就是MessageQueue存在的价值。

    Looper

    • Looper和MessageQueue的消息就像水泵和井(里边装的是水)的关系一样,我们有了消息(水),但是为了把水从井中抽取出来(循环起来),我们得有一个水泵作为动力,这个动力就是Looper。
    • 如果我们在一个线程中调用Looper.prepare()...Looper.loop(),那么你的线程就成功升级为了一个Looper线程,这意味着你的线程有了一个消息泵(Looper)和一个消息队列(MessageQueue),此时你就可以调用Handler来进行线程间的通信了。
    • 我们应用的UI线程也就是主线程,在应用启动的时候,系统会自动初始化一个Looper,也就是说,我们的UI线程默认是Looper线程。这也就是为什么主线程中直接调用Handler没什么事,但是再子线程中创建Handler需要哦手动调用Looper.prepare()...Looper.loop()的和原因。

    Message

    Message也就是消息,井中的水。一个Message包括了消息类型(what),消息内容(arg1,arg2),发送它的Handler(target),Runnable回调接口等:

     public int what;        //数据类型
        public int arg1;        //简单的整数值
        public int arg2;        //简单的整数值可以直接发送,是一种替代setData(Bundle)的低成本方案,更加省资源
        public Object obj;
        ......
        /*package*/ int flags;
        /*package*/ long when;          //Handler发送一个消息之后,返回此消息的目标交付时间(以毫秒为单位)。
        /*package*/ Bundle data;        //Bundle可以携带更复杂的数据类型
        /*package*/ Handler target;     //哪个Handler发送的消息
        /*package*/ Runnable callback;
    
        //可以看到,Message带了一个指向一下个节点的链,也就是说,MessageQueue内部维护的实际上是一个链表
        /*package*/ Message next;
    
        private static final Object sPoolSync = new Object();
        private static Message sPool;       //消息池
        private static int sPoolSize = 0;
    
        private static final int MAX_POOL_SIZE = 50;    //消息池的最大容量
    

    线程

    Thread/Runnable/Callable

    一般实现线程的方法有两种,一种是类继承Thread,一种是实现接口Runnable。

    FutureTask

    • FutureTask 实现了 Runnable 和 Future,所以兼顾两者优点,既可以在 Thread 中使用,又可以在 ExecutorService 中使用。
    • 使用 FutureTask 的好处是 FutureTask 是为了弥补 Thread 的不足而设计的,它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果。FutureTask 是一种可以取消的异步的计算任务,它的计算是通过 Callable 实现的,它等价于可以携带结果的 Runnable,并且有三个状态:等待、运行和完成。完成包括所有计算以任意的方式结束,包括正常结束、取消和异常。

    AsyncTask

    • AsyncTask是一个轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和结果传递给主线程并且在主线程中更新UI。
    • AsyncTask是一个抽象泛型类,声明:public abstract class AsyncTask<Params, Progress, Result>;

    并且提供了4个核心方法。

    1.参数1,Params,异步任务的入参;
    2.参数2,Progress,执行任务的进度;
    3.参数3,Result,后台任务执行的结果;
    4.方法1, onPreExecute(),在主线程中执行,任务开启前的准备工作;
    5.方法2,doInbackground(Params…params),开启子线程执行后台任务;
    6.方法3,onProgressUpdate(Progress values),在主线程中执行,更新UI进度;
    7.方法4,onPostExecute(Result result),在主线程中执行,异步任务执行完成后执行,它的参数是doInbackground()的返回值。

    HandlerThread

    • HandlerThread继承了Thread,我们都知道如果需要在线程中创建一个可接收消息的Handler,所以HandlerThread实际上是一个允许Handler的特殊线程。
    • 普通线程在run()方法中执行耗时操作,而HandlerThread在run()方法创建了一个消息队列不停地轮询消息,我们可以通过Handler发送消息来告诉线程该执行什么操作。
    • 它在Android中是个很有用的类,它常见的使用场景实在IntentService中。当我们不再需要HandlerThread时,我们通过调用quit/Safely方法来结束线程的轮询并结束该线程。

    IntentService

    • IntentService是一个继承Service的抽象类,所以我们必须实现它的子类再去使用。
    • 在说到HandlerThread时我们提到,HandlerThread的使用场景是在IntentService上,我们可以这样来理解IntentService,它是一个实现了HandlerThread的Service。
    • 那么为什么要这样设计呢?这样设计的好处是Service的优先级比较高,我们可以利用这个特性来保证后台服务的优先正常执行,甚至我们还可以为Service开辟一个新的进程。

    线程池

    • 重用线程池中的线程,避免频繁地创建和销毁线程带来的性能消耗;
    • 有效控制线程的最大并发数量,防止线程过大导致抢占资源造成系统阻塞;
    • 可以对线程进行一定地管理。

    我们来介绍一下不同特性的线程池,它们都直接或者间接通过ThreadPoolExecutor来实现自己的功能

    FixedThreadPool

    通过Executors的newFixedThreadPool()方法创建,它是个线程数量固定的线程池,该线程池的线程全部为核心线程,它们没有超时机制且排队任务队列无限制,因为全都是核心线程,所以响应较快,且不用担心线程会被回收。

    CachedThreadPool

    通过Executors的newCachedThreadPool()方法来创建,它是一个数量无限多的线程池,它所有的线程都是非核心线程,当有新任务来时如果没有空闲的线程则直接创建新的线程不会去排队而直接执行,并且超时时间都是60s,所以此线程池适合执行大量耗时小的任务。由于设置了超时时间为60s,所以当线程空闲一定时间时就会被系统回收,所以理论上该线程池不会有占用系统资源的无用线程。

    ScheduledThreadPool

    通过Executors的newScheduledThreadPool()方法来创建,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,但是它的非核心线程超时时间是0s,所以非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

    SingleThreadExecutor

    通过Executors的newSingleThreadExecutor()方法来创建,它内部只有一个核心线程,它确保所有任务进来都要排队按顺序执行。它的意义在于,统一所有的外界任务到同一线程中,让调用者可以忽略线程同步问题。

    线程池一般用法

    • shutDown(),关闭线程池,需要执行完已提交的任务;
    • shutDownNow(),关闭线程池,并尝试结束已提交的任务;
    • allowCoreThreadTimeOut(boolen),允许核心线程闲置超时回收;
    • execute(),提交任务无返回值;
    • submit(),提交任务有返回值;

    MVC,MVP,MVVM架构的区别

    MVC

    • MVC全名是Model View Controller,如图,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

    • 其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。

    • 视图层(View)
      一般采用XML文件进行界面的描述,这些XML可以理解为AndroidApp的View。使用的时候可以非常方便的引入。同时便于后期界面的修改。逻辑中与界面对应的id不变化则代码不用修改,大大增强了代码的可维护性。

    • 控制层(Controller)
      Android的控制层的重任通常落在了众多的Activity的肩上。这句话也就暗含了不要在Activity中写代码,要通过Activity交割Model业务逻辑层处理,这样做的另外一个原因是Android中的Actiivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

    • 模型层(Model)
      我们针对业务模型,建立的数据结构和相关的类,就可以理解为AndroidApp的Model,Model是与View无关,而与业务相关的。对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算等操作也是必须放在的该层的。就是应用程序中二进制的数据。

    • 缺点
      在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。

    MVP

    在App开发过程中,经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。从实践中看到,这更多的出现在UI部分,也就是Activity里。想象一下,一个2000+行以上基本不带注释的Activity,我的第一反应就是想吐。Activity内容过多的原因其实很好解释,因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。而且现在大部分的Activity还对整个App起到类似IOS中的【ViewController】的作用,这又带入了大量的逻辑代码,造成Activity的臃肿。为了解决这个问题,让我们引入MVP框架。

    • MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
    • MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理,Model提供数据。在MVP模式里通常包含3个要素(加上View interface是4个):
    • View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)
    • Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
    • Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
      *View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

    优点
    1.模型与视图完全分离,我们可以修改视图而不影响模型;
    2.可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
    3.我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
    4.如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

    • 具体到Android App中,一般可以将App根据程序的结构进行纵向划分,根据MVP可以将App分别为模型层(M),UI层(V)和逻辑层(P)。
    • UI层一般包括Activity,Fragment,Adapter等直接和UI相关的类,UI层的Activity在启动之后实例化相应的Presenter,App的控制权后移,由UI转移到Presenter,两者之间的通信通过BroadCast、Handler或者接口完成,只传递事件和结果。
    • 举个简单的例子,UI层通知逻辑层(Presenter)用户点击了一个Button,逻辑层(Presenter)自己决定应该用什么行为进行响应,该找哪个模型(Model)去做这件事,最后逻辑层(Presenter)将完成的结果更新到UI层。

    MVVM

    MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

    动画的实现方式

    视图动画

    补间动画

    • 平移动画(Translate)
    • 缩放动画(scale)
    • 旋转动画(rotate)
    • 透明度动画(alpha)

    逐帧动画

    • 将动画拆分为 帧 的形式,且定义每一帧 = 每一张图片
    • 逐帧动画的本质:按序播放一组预先定义好的图片

    属性动画

    • ValueAnimator 类 & ObjectAnimator 类

    区别

    • 视图动画:无改变动画的属性
      因为视图动画在动画过程中仅对图像进行变换,从而达到了动画效果
    • 属性动画:改变了动画属性
      因属性动画在动画过程中对动态改变了对象属性,从而达到了动画效果
    • 特别注意
      使用视图动画时:无论动画结果在哪,该View的位置不变 & 响应区域都是在原地,不会根据结果而移动;而属性动画 则会通过改变属性 从而使动画移动

    View绘制流程

    View的整个绘制流程可以分为以下三个阶段:

    • measure: 判断是否需要重新计算View的大小,需要的话则计算;
    • layout: 判断是否需要重新计算View的位置,需要的话则计算;
    • draw: 判断是否需要重新绘制View,需要的话则重绘制。

    onMeasure

    对于View的测量,肯定会和MeasureSpec接触,MeasureSpec是两个单词组成,翻译过来“测量规格”或者“测量参数”,很多博客包括官方文档对他的说明基本都是“一个MeasureSpec封装了从父容器传递给子容器的布局要求”,这个MeasureSpec 封装的是父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递” 两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
    MeasureSpec一共有三种模式:

    • UPSPECIFIED : 父容器对于子容器没有任何限制,子容器想要多大就多大。
    • EXACTLY: 父容器已经为子容器设置了尺寸,子容器应当服从这些边界,不论子容器想要多大的空间。
    • AT_MOST:子容器可以是声明大小内的任意大小。

    onLayout

    确定视图的位置,从顶层父View到子View递归调用layout()方法,父View将上一步measure()方法得到的子View的布局大小和布局参数,将子View放在合适的位置上。

    onDraw

    绘制最终的视图,首先ViewRoot创建一个Canvas对象,然后调用onDraw()方法进行绘制。onDraw()方法的绘制流程为: 绘制视图背景-> 绘制画布的图层->绘制View内容->绘制子视图。

    View事件传递分支机制

    ViewGroup接收到事件后进行事件的分派,如果自己需要处理这个事件,则进行拦截;如果不处理,则传递给子View进行处理,然后由子view进行分派,拦截和处理。打个比方:上级接到任务后进行任务分派,如果上级自己处理这个任务,则自己处理;如果不想处理,则把这个任务丢给下级进行处理。

    对于ViewGroup,我们需要重写了以下三个方法:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
       Log.d("KeithXiaoY", "ViewGroupA dispatchTouchEvent" ));
       return super.dispatchTouchEvent(ev);
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
       Log.d("KeithXiaoY", "ViewGroupA onInterceptTouchEvent" );
       return super.onInterceptTouchEvent(ev);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
       Log.d("KeithXiaoY", "ViewGroupA onTouchEvent" );
       return super.onTouchEvent(event);
    }
    

    而对于View来说,我们需要重写了以下两个方法:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
       Log.d("KeithXiaoY", "View onTouchEvent" );
       return super.onTouchEvent(event);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
       Log.d("KeithXiaoY", "View dispatchTouchEvent" );
       return super.dispatchTouchEvent(event);
    }
    
    • dispatchTouchEvent用于事件传递分发。
    • onInterceptTouchEvent用于事件拦截。
    • onTouchEvent用于事件处理。
    • 事件传递的时候,从根布局开始,返回true,则不会向下一层子view传递。
    • 事件处理的时候,返回true,则表示当前view处理了,不会向上传递报告了,若不是底层时,也不会向下传递事件了,若返回false,则会继续向下传递事件,处于底层则是向上传递报告。
    • onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发,即onTouch方法返回true,onTouchEvent则不会调用。

    序列化

    为什么要序列化?
    1.永久性保存对象,保存对象的字节序列到本地文件中;
    2.通过序列化对象在网络中传递对象;
    3.通过序列化在进程间传递对象。

    实现序列化的方法:

    Serializable

    实现Serializable接口非常简单,声明一下就可以了,但它在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

    Parcelable

    • Parcelable是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC)。
    • 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
    • Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。
    • 实现Parcelable步骤
      1.implements Parcelable
      2.重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
      3.重写describeContents方法,内容接口描述,默认返回0就可以
      4.实例化静态内部对象CREATOR实现接口Parcelable.Creator

    进程间通信

    IPC为进程间通信或跨进程通信,是指两个进程进行进程间通信的过程。

    使用Bundle的方式

    利用Bundle进行进程间通信是很容易的,大家应该注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它使用时有一定的局限性。

    使用文件共享的方式

    • 比如进程A把数据写入到文件File中,然后进程B就可以通过读取这个文件来获取这个数据。通过这种方式,除了可以交换简单的文本信息之外,我们还可以序列化一个对象到文件系统中,另一个进程可以通过反序列化恢复这个对象。
    • 通过文件共享的这种方式来共享数据对文件的格式是有具体要求的,比如可以是文本文件,也可以是XML文件,只要读写双方约定数据格式即可。这种方式进行进程间通信虽然方便,可是也是有局限性的,比如并发读/写,这会导致比较严重的问题,如读取的数据不完整或者读取的数据不是最新的。因此通过文件共享的方式适合在数据同步要求不高的进程间通信,并且要妥善处理并发读/写问题。

    使用Messenger的方式

    我们也可以通过Messenger来进行进程间通信,在Messenger中放入我们需要传递的数据,就可以轻松的实现进程之间数据传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

    使用AIDL的方式

    AIDL(Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
    AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类架构,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
    1.定义一个AIDL接口。
    2.为远程服务(Service)实现对应Stub。
    3.将服务“暴露”给客户程序使用。

    使用ContentProvider的方式

    ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作。ContentProvider分为系统的和自定义的,系统的(例如:联系人,图片等数据)。

    使用广播接收者(Broadcast)的方式

    BroadcastReceiver本质上是一个系统级的监听器,它专门监听各个程序发出的Broadcast,因此它拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcastReceivert总会被激发。我们知道,只要注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为是将自己感兴趣的IntentFilter注册到Android系统的AMS(ActivityManagerService)中,里面保存了一个IntentFilter列表。广播发送者将IntentFilter的action行为发送到AMS中,然后遍历AMS中的IntentFilter列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应的IntentFilter或者Service中---也就是说:会调用抽象方法onReceive()方法。其中AMS起到了中间桥梁的作用。
    程序启动BroadcastReceiver只需要两步:
    1.创建需要启动的BroadcastReceivert的intent;
    2.调用Context的sendBroadcast()或者sendOrderBroadcast()方法来启动指定的BroadcastReceivert。
    每当Broadcast事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发onReceiver()方法,onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。
    注意:onReceiver()方法中尽量不要做耗时操作,如果onReceiver()方法不能再10秒之内完成事件的处理,Android会认为该进程无响应,也就弹出我们熟悉的ANR对话框。

    使用Socket的方式

    Socaket也是实现进程间通信的一种方式,Socaket也称为“套接字”,网络通信中的概念,通过Socket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的嘛,但是Socaket主要还是应用在网络通信中。

    热修复

    类加载方案

    • 类加载方案基于Dex分包方案,而Dex方案是为了解决65536方法数限制和LinearAlloc限制。
    • Dex分包方案主要有两种,分别是Google官方方案、Dex自动拆包和动态加载方案。
    • 类加载方案需要重启App后让ClassLoader重新加载新的类,为什么需要重启呢?这是因为类是无法被卸载的,因此要想重新加载新的类就需要重启App,因此采用类加载方案的热修复框架是不能即时生效的。

    底层替换方案

    与类加载方案不同的是,底层替换方案不会再次加载新类,而是直接在Native层修改原有类,由于是在原有类进行修改限制会比较多,不能够增减原有类的方法和字段,如果我们增加了方法数,那么方法索引数也会增加,这样访问方法时会无法通过索引找到正确的方法,同样的字段也是类似的情况。

    Instant Run方案

    • 除了资源修复,代码修复同样也可以借鉴Instant Run的原理, 可以说Instant Run的出现推动了热修复框架的发展。
    • 当我们点击InstantRun时,如果方法没有变化则不做任何处理。如果方法有变化,就生成替换类,然后返回被修改的类的列表,存在列表中则为执行Activity的onCreate方法。

    浏览器输入地址到返回结果发生了什么

    1.DNS解析;
    2.TCP链接;
    3.发送HTTP请求;
    4.服务器处理请求并返回HTTP报文;
    5.浏览器解析渲染界面;
    6.连接结束。

    Rxjava常用操作符

    Observable 的创建

    from()

    转换集合为一个每次发射集合中一个元素的 Observable 对象。
    使用场景:对集合(数组、List 等)进行遍历。

    just()

    转换一个或多个 Object 为依次发射这些 Object 的 Observable 对象。

    使用场景:转换一个或多个普通 Object 为 Observable 对象,如转换数据库查询结果、网络查询结果等。
    just() 方法可传入 1~10 个参数,也就说当元素个数小于等于 10 的时候既可以使用just() 也可以使用 from(),否则只能用 from() 方法。

    create()

    返回一个在被 OnSubscribe 订阅时执行特定方法的 Observable 对象,
    使用场景:不推荐使用,可使用其他操作符替代,如使用 from()操作符完成遍历。

    interval()

    返回一个每隔指定的时间间隔就发射一个序列号的 Observable 对象。这是一个无限循环,可以采用interval + take的方式来实现指定次数循环。
    使用场景:可使用该操作符完成定时、倒计时等功能。

    timer()

    创建一个在指定延迟时间后发射一条数据(固定值:0)的 Observable 对象。

    range()

    创建一个发射指定范围内的连续整数的 Observable 对象。
    使用场景:可使用该操作符完成一个 fori 的循环,如 for(int i=5;i<=7;i++) -> Observable.range(5, 3)。

    error()

    创建不发射任何数据就发出 onError 通知的 Observable 对象。
    使用场景:程序中捕获异常后,可使用该操作符把捕获的异常传递到后面的逻辑中处理。

    重做

    repeat()

    使Observable 对象在发出 onNext() 通知之后重复发射数据。重做结束才会发出 onComplete() 通知,若重做过程中出现异常则会中断并发出 onError() 通知。
    使用场景:可使用该操作符指定一次任务执行完成后立即重复执行上一次的任务,如发送多次网络请求等。

    repeatWhen()

    同上,指定满足一定条件时重复执行一个任务。

    重试

    retry()

    在执行 Observable对象的序列出现异常时,不直接发出 onError() 通知,而是重新订阅该 Observable对象,直到重做过程中未出现异常,则会发出 onNext() 和 onCompleted()通知;若重做过程中也出现异常,则会继续重试,直到达到重试次数上限,超出次数后发出最新的 onError() 通知。

    使用场景:网络等请求异常出错后,可重新发起请求。

    retryWhen()

    有条件的执行重试。
    使用场景:网络等请求异常出错后,若满足一定条件,则重新发起请求。

    变换

    map()

    把源 Observable 发射的元素应用于指定的函数,并发送该函数的结果。

    使用场景:将从网络获取的数据(NetData 对象)转换为数据库相关对象(DBData对象)并使用 Observable 发送。

    flatMap()

    转换源 Observable 对象为另一个 Observable 对象。

    使用场景:从网络获取数据并使用 obsA 对象发射,flatMap() 操作符中可将数据存进数据库并返回一个新的对象 obsB。

    过滤

    filter()

    只发射满足指定谓词的元素。
    使用场景:可使用 filter 代替 if 语句。

    first()

    返回一个仅仅发射源 Observable 发射的第一个[满足指定谓词的]元素的 Observable,如果源 Observable 为空,则会抛出一个 NoSuchElementException。
    使用场景: 顺序发出多条数据,只接收第一条。

    last()

    返回一个仅仅发射源 Observable 发射的倒数第一个[满足指定谓词的]元素的 Observable,如果源 Observable 为空,则会抛出一个 NoSuchElementException。
    使用场景: 顺序发出多条数据,只接收最后一条。

    skip()

    跳过前面指定数量或指定时间内的元素,只发射后面的元素。

    skipLast()

    跳过前面指定数量或指定时间内的元素,只发射后面的元素。指定时间时会延迟源 Observable 发射的任何数据。

    take()

    只发射前面指定数量或指定时间内的元素。

    takeLast()

    只发射后面指定数量或指定时间内的元素。指定时间时会延迟源 Observable 发射的任何数据。

    elementAt()

    只发射指定索引的元素。
    使用场景: 按索引去集合中的元素等。

    elementAtOrDefault()

    只发射指定索引的元素,若该索引对应的元素不存在,则发射默认值。

    ignoreElements()

    不发射任何数据,直接发出 onCompleted() 通知。

    组合操作

    用于将多个Observable组合成一个单一的Observable的操作符

    Join

    数组长度是两个数组的乘积

    Merge(合并)

    把两个数组 合并为一个数组,新的数组长度是原来两个数组长度之和;

    mergeDelayError()

    在Observable中,一旦某一个时间抛出异常,后面的序列将会终止,如果我们希望在序列出错的时候不影响后面的序列,那么可以使用mergeDelayErroe()方法!

    Zip

    合并后的数组长度是两个数组长度最小的那一个数组的长度;而且我们可以自己定义合并的方式

    Leak Canary 原理

    1.通过registerActivityLifecycleCallbacks来监听Activity的生命周期。
    2.监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。
    3.如果Activity泄露了,就抓取内存dump文件(Debug.dumpHprofData)。
    4.接着通过HeapAnalyzer来进行内存泄漏分析。
    5.最后通过DisplayLeakService进行内存泄漏的展示。

    Glide流程简析

    开始---生成图片缓存key---创建缓存对象LruResourceCache---从内存缓存获取图片缓存---开启加载图片线程---从磁盘缓存中获取---从网络获取图片资源---写入磁盘缓存---图片加载完成---写入内存缓存---显示图片---结束

    缓存

    缓存图片资源:

    • 原始图片(Source) :即图片源的图片初始大小 & 分辨率
    • 转换后的图片(Result) :经过 尺寸缩放 和 大小压缩等处理后的图片

    缓存读取顺序:内存缓存 --> 磁盘缓存 --> 网络

    内存缓存

    LruCache(近期最少使用的算法)
    • LruCache的核心思想很好理解,就是要维护一个缓存对象列表,其中对象列表的排列方式是按照访问顺序实现的,即一直没访问的对象,将放在队尾,即将被淘汰。而最近访问的对象将放在队头,最后被淘汰。简单理解成,将最近使用的对象用强引用的方式存储在LinkedHashMap中,当缓存满时,将最近最少使用的对象从内存中移除。
    • 而LinkedHashMap是由数组+双向链表的数据结构来实现的。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的<key,value>对按照一定顺序排列起来。
    弱引用

    弱引用的对象具备更短生命周期,因为 **当JVM进行垃圾回收时,一旦发现弱引用对象,都会进行回收(无论内存充足否),用于存储正在显示的图片数据。

    磁盘缓存

    可缓存原始图片 & 缓存转换过后的图片,用户自行设置,自定义的DiskLruCache算法。

    EventBus简析

    Register流程

    开始---根据订阅者类名查找当前订阅者的所有事件响应函数---循环每个事件的响应函数---根据优先级将当前订阅者信息插入到订阅者队列subscriptionsByEventType中---得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅---若为Sticky事件,则取出事件,post此事件给当前订阅者---结束

    Post流程

    这里的核心是使用了ThreadLocal去做数据存储。而真正执行方法就是通过反射调用了订阅者的订阅函数并把event对象作为参数传入。

    ThreadLocal:是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。其内部原理是通过生成一个它包裹的泛型对象的数组,在不同的线程会有不同的数组索引值,通过这样就可以做到每个线程通过get() 方法获取的时候,取到的只能是自己线程所对应的数据。

    不同的threadMode在不同的线程里invoke()订阅者的方法,ThreadMode共有四类:

    PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;
    MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
    BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
    Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。

    Unregister流程

    最终分别从typesBySubscriber和subscriptions里分别移除订阅者以及相关信息即可。

    总结

    实际上在EventBus里我们可以看到不仅可以使用注解处理器预处理获取订阅信息,EventBus也会将订阅者的方法缓存到METHOD_CACHE里避免重复查找,所以只有在最后invoke()方法的时候会比直接调用多出一些性能损耗,但是这些对于我们移动端来说是完全可以忽略的。

    优化

    Code Review

    团队做Review能有效提高自己的代码质量和功能的稳定性。

    清理操作

    1.是否调用Handler的removeCallbacksAndMessages(null)来清空Handler里的消息;
    2.是否取消了还没完成的请求;
    3.在页面里注册的监听,是否反注册;
    4.假如用了RxJava的话,是否解除订阅;
    5.数据库的游标是否已经关闭
    6.打开过的文件流是否关闭
    7.使用完的Bitmap是否调用recycle()
    8.WebView使用完是否调用了其destory()函数

    优化代码

    1.保存在内存中的图片,是否做过压缩处理再保存在内存里 否则可能由于图片质量太高,导致OOM
    2.Intent传递的数据太大,会导致页面跳转过慢。太大的数据可以通过持久化的形式传递,例如读写文件
    3.频繁地操作同一个文件或者执行同一个数据库操作,是否考虑把它用静态变量或者局部变量的形式缓存在内存里。用空间换时间
    4.放在主页面的控件,是否可以考虑用ViewStub来优化启动速度
    5.注意内存泄露问题
    6.空指针问题

    自测检查

    1.思考某些情况下,某个变量是否会造成空指针问题
    2.把手机横屏,检查布局是否有Bug
    3.在不同分辨率的机型上,检查布局是否有Bug
    4.切换到英文等外文字体下,检查外文是否能完整显示
    5.从低版本升级上来,会不会有问题 比如可能会出现数据库不兼容的问题
    6.按下Home再返回是否正常
    7.熄灭屏幕再打开是否正常
    8.切换成其它应用再切换回来会怎样
    9.利用手机的开发者选项中的 “调试GPU过度绘制” ,“GPU呈现模式分析” 和 “显示FPS和功耗” 功能,看自己的新功能是否会导致过度绘制、是否会掉帧
    10.测试看是否影响启动速度 adb shell am start -W 包名/Activity
    11.对比看APK大小是否有增大
    12.跑1小时Monkey,测试其稳定性

    异形屏适配

    需要适配的情况:
    1.沉浸式风格

    • 方法一:利用fitsSystemWindows属性
    • 方法二:根据状态栏高度手动设置paddingTop
    • 方法三:在布局中添加一个和状态栏高度相同的View
      2.全屏风格,状态栏不可见
      2.1 Android P及以上
      Android P中增加了一个窗口布局参数属性layoutInDisplayCutoutMode,该属性有三个值可以取:
    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT:默认的布局模式,仅当刘海区域完全包含在状态栏之中时,才允许窗口延伸到刘海区域显示,也就是说,如果没有设置为全屏显示模式,就允许窗口延伸到刘海区域,否则不允许。
    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER:永远不允许窗口延伸到刘海区域。
    • LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES:始终允许窗口延伸到屏幕短边上的刘海区域,窗口永远不会延伸到屏幕长边上的刘海区域。
      2.2 Android P以下
      针对Android P以下的手机,我们只能依照各个厂商提供的适配方案来进行适配。

    过度绘制优化

    1.移除Window默认的background:getWindow.setBackgroundDrawable(null);
    2.移除XML布局文件中非必需的Background;
    3.减少布局嵌套;
    4.可以用merge替代LinearLayout、RelativeLayout,这样子把UI元素直接衔接到include位置;
    5.工具:HierarchyView查看视图层级。

    App的冷启动优化

    消除启动时的白屏/黑屏

    在用户点击手机桌面APP的时候,看到的黑屏或者白屏其实是界面渲染前的第一帧,可以将Theme里的windowBackground设置成我们想要让用户看到的画面就可以了,这里有2种做法:

    1. 将背景图设置成我们APP的Logo图,作为APP启动的引导,现在市面上大部分的APP也是这么做的。
     <style name="AppWelcome" parent="AppTheme">
            <item name="android:windowBackground">@mipmap/bg_welcome_start</item>
        </style>
    

    2.将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会"立即"进入APP,而且在桌面上停留一会,其实这时候APP已经是启动的了,只是我们心机的把Theme里的windowBackground的颜色设置成透明的。

      <style name="Appwelcome" parent="android:Theme.Translucent.NoTitleBar.Fullscreen"/>
    

    Application里面优化

    1.不要让Application参与业务的操作。
    2.不要在APPlication进行耗时操作,比如有些开发者会在自己的APP里一系列文件夹或文件(比如我自己),这些I/O操作应该放到"确实该使用的时候再去创建"亦或者是数据库的一些操作。
    3.不要以静态变量的方式在Application中保存数据等。
    4.使用一些库的初始化,可以使用IntentService去初始化。

    ANR

    ANR的全称是application not responding,意思就是程序未响应。
    产生原因
    1.主线程执行了耗时操作,在一定时间内没有响应操作。比如数据库操作或网络编程。
    2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。
    3.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)--主要类型。
    4.BroadcastReceiver在10秒内没有执行完毕。
    5.Service在特定时间内(20秒内)无法处理完成--小概率类型。
    避免
    1.避免在主线程上进行复杂耗时的操作,比如说发送接收网络数据/进行大量计算/操作数据库/读写文件等。这个可以通过使用AsyncTask或者使用多线程来实现。
    2.broadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理。
    3.在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。
    修正
    可以通过查看/data/anr/traces.txt查看ANR信息。根据日志文件的信息提示修改代码。

    OOM

    OOM,全称“Out Of Memory”,翻译为“内存用尽”当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出OOM。
    常见情况
    1.Acitivity没有对栈进行管理,如果开启过多,就容易造成内存溢出。
    2.加载大的图片或者同时数量过多的图片的时候。
    3.程序存在内存泄漏问题,导致系统可用内存越来越小。
    4.递归次数过多,也会导致内存溢出。
    5.频繁的内存抖动,也会造成OOM异常的发生,大量小的对象被频繁的创建,导致内存碎片,从而当需要分配内存的时候,虽然总体上还有内存分配,但是由于这些内存不是连续的,导致无法分配,系统就直接返回OOM了。
    解决方法
    1.减小对象的内存占用,避免OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。
    2.内存对象的重复利用,大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收。
    3.避免对象的内存泄露,内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分 配内存的时候,空闲空间不足而出现OOM,例如File、Bitmap、数据库、Cursor等。
    4.内存使用策略优化。在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。

    内存泄漏

    内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
    常见情况
    1.单例造成的内存泄漏
    由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
    2.非静态内部类创建静态实例造成的内存泄漏,因为非静态内部类会持有外部对象强引用。
    3.Handler造成的内存泄漏
    当Activity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。
    4.线程造成的内存泄漏
    若线程持有Activity的引用,且线程处于后台执行状态,当Activity销毁但线程仍然存活且持有Activity的引用,则会引起内存泄漏。
    5.资源未关闭造成的内存泄漏
    对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
    6.使用ListView时造成的内存泄漏
    初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。构造Adapter时,没有使用缓存的convertView。
    7.集合容器中的内存泄露
    我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
    8.WebView造成的泄露
    当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
    防止内存泄漏的措施
    1.在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。
    2.对于需要在静态内部类中使用非静态外部成员变量(例如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
    3.对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
    4.保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
    5.对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以采用以下方式避免内存泄漏:
    ① 将内部类改为静态内部类
    ② 静态内部类中使用弱引用来引用外部类的成员变量

    内存泄漏和内存溢出区别?

    内存泄漏:主要是因为程序存在BUG 导致内存没有释放 。
    内存溢出:是指内存不够用了,导致不够用的原因很多,内存泄漏是导致内存溢出原因一种。

    进程保活

    1.开启后台服务播放循环播放一段无声音乐。
    2.点击home键使app长时间停留在后台,内存不足被kill
    处理这种情况前提是你的app至少运行了一个service,然后通过Service.startForeground() 设置为前台服务,可以将oom_adj的数值由4降低到1,大大提高存活率。
    3.在大多数国产手机下,进入锁屏状态一段时间,省电机制会kill后台进程
    注册广播监听锁屏和解锁事件, 锁屏后启动一个1像素的透明Activity,这样直接把进程的oom_adj数值降低到0,0是android进程的最高优先级。 解锁后销毁这个透明Activity。
    4.在app的设置界面加一个选项,提示用户自己去勾选自启动,设置为白名单,设置电池管理 。

    代码加密防止反编译

    1.代码混淆技术(ProGuard) 该技术主要是进行代码混淆,降低代码逆向编译后的可读性,但该技术无法防止加壳技术进行加壳(加入吸费、广告、病毒等代码),而且只要是细心的人,依然可以对代码依然可以对代码进行逆向分析,所以该技术并没有从根本解决破解问题,只是增加了破解难度。
    2.签名比对技术。该技术主要防止加壳技术进行加壳,但代码逆向分析风险依然存在。而且该技术并不能根本解决被加壳问题,如果破解者将签名比对代码注释掉,再编译回来,该技术就被破解了。
    3.NDK .so动态库技术,该技术实现是将重要核心代码全部放在C文件中,利用NDK技术,将核心代码编译成.so动态库,再用JNI进行调用。该技术虽然能将核心代码保护起来,但被加壳风险依然存在。

    1. 动态加载技术,该技术可以有效的防止逆向分析、被破解、被加壳等问题,动态加载技术分为以下几步:
    • 将核心代码编译成dex文件的Jar包
    • 对jar包进行加密处理
    • 在程序主入口利用NDK进行解密
    • 再利用ClassLoader将jar包进行动态加载
    • 利用反射技术将ClassLoader 设置成系统的ClassLoader

    Apk瘦身

    1.classes.dex:通过代码混淆,删掉不必要的jar包和代码实现该文件的优化;
    2.资源文件:通过Lint工具扫描代码中没有使用到的静态资源;
    3.图片资源:使用tinypng和webP;
    4.SO文件将不用的去掉,目前主流app一般只放一个arm的so包。

    链接

    以上资料有部分收集于各位大神的分析文章中,感谢以下大神的付出!(如有侵权,联删)

    相关文章

      网友评论

        本文标题:安卓面试知识点大全(适合中高级)

        本文链接:https://www.haomeiwen.com/subject/umpytctx.html