Android面试

Android面试(未完)

2019-02-26  本文已影响77人  HongGang

Java基础知识点

集合相关

ArrayList

  1. ArrayList内部实现是数组,且当数组长度不够时,数组的会进行原数组长度的1.5倍扩容。
  2. ArrayList内部元素是可以重复的。且有序的,因为是按照数组一个一个进行添加的。
  3. ArrayList是线程不安全的,因为其内部添加、删除、等操作,没有进行同步操作。
  4. ArrayList增删元素速度较慢,因为内部实现是数组,每次操作都会对数组进行复制操作,复制操作是比较耗时的

LinkedList

  1. LinkedList内部实现是双向链表,且内部有fist与last指针控制数据的增加与删除等操作
  2. LinkedList内部元素是可以重复,且有序的。因为是按照链表进行存储元素的。
  3. LinkedList线程是不安全的,因为其内部添加、删除、等操作,没有进行同步操作。
  4. LinkedList增删元素速度较快。

ArrayList与LinkedList对比

  1. 结构不同,前者内部实现是动态数组,后者内部实现是双向链表
  2. 性能不同,前者由于是动态数组结构,在读取时较快,而后者是链表结构,在删除与添加元素时较快
  3. 线程安全相同,在对数据进行改变时,两者都不是原子性操作,所以都是线程不安全的

HashMap

  1. HashMap的基本介绍
    HashMap的内部是在JDK1.8之前是数组+链表的散列表复合结构,在JDK1.8之后,采用的是数组+链表+红黑树的复合结构,数组被分为一个个的桶(bucket)。哈希值决定了键值对在数组中的寻址。具有相同哈希值的键值对会组成链表。当链表的长度超出预置的阈值,会产生树化,即链表会变成树形结构。
  2. HashMap内部存储原理
    (1)首先有个初识容量为16的桶,桶的每个元素都是链表
    (2)当添加一个元素时,就首先计算元素key的hash值,以此确定插入数组中的位置
    (3)哈希值相同的元素已经被放在桶的相同位置,形成了链表,同一各链表上的哈希值是相同的
    (4)在JDK1.8中,当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
    (5)当链表数组的容量超过初始容量的0.75时,数组会进行等倍扩容
  3. 为什么需要加载因子来进行等倍扩容呢?
    不进行扩容的话,链表就会越来越长,这样查找的效率很低,因为链表的长度很大,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率
  4. HashMap的put()
    (1)通过hash方法获取hash值,根据hash值寻址。
    (2)如果未发生碰撞,直接放到桶中,如果发生碰撞,则以链表形式放在桶后。
    (3)当链表长度大于阈值后会触发树化,将链表转换为红黑树。
    (4)当元素数量达到临界值时,会调用resize方法扩展容量。
  5. HashMap的get()
    (1)通过hash方法获取hash值,根据hash值寻址。
    (2)如果与寻址到桶的key相等,直接返回对应的value。
    (3)如果发生冲突,分两种情况。如果是树,则调用getTreeNode获取value;如果是链表则通过循环遍历查找对应的value。
  6. HashMap的resize()
    (1)由于是等倍扩容,调用此方法时,会将原数组扩展为原来的2倍
    (2)重新计算index索引值,将原节点重新放到新的数组中,此时可以将原先冲突的节点分散到新的桶中。
  7. HashMap的clear()
    (1)clear方法非常简单,就是遍历table然后把每个位置置为null,同时修改元素个数为0
    (2)需要注意的是clear方法只会清除里面的元素,并不会重置capactiy

HashTable与HashMap对比

  1. 内部结构:两者在结构上都是使用拉链结构,即 “数组+链表” 结构。
  2. 线程安全:HashTable内部进行了同步处理,是线程安全的,而HashMap内部没有进行同步处理,线程不安全。
  3. 元素限制:HashTable的key和value都有不能为null,而HashMap的key和value都可以为null
  4. 性能对比:HashTable由于是线程安全的,性能较HashMap要慢一些

LinkedHashMap

LinkedHashMap内部实现是拉链结构,即 “数组+双向链表” 结构,除此之外与HashMap的用法基本相同

ArrayMap、SparseArray与HashMap的对比

  1. SparseArray与ArrayMap的结构基本相同,只是SparseArray的Key的数据类型只能是Integer,Long等基本类型
  2. 内部结构:HashMap使用的是散列表结构,而ArrayMap使用的是双数组结构,Hash数组用来存储哈希值,Arrays数组用来存储key(偶)与value(奇)
  3. 内存空间:HashMap的内部数组长度是2的N次幂,而ArrayMap没有限制
  4. 查找方式:前者是通过哈希值来计算下标,后者是通过二分法进行查找,所以在数据量较大的时候,前者性能更好,反之后者性能更好。
  5. 扩容方式:HashMap是等倍扩容,并且会重新计算hash值(rehash),ArrayMap则是使用的数组复制(copy)。

集合的线程安全性

  1. 线程安全:vector、HashTable、StringBuffer
  2. 线程不安全:ArrayList、LinkedList、HashMap、LinkedHashMap、TreeMap、HashSet、TreeSet、StringBuilder

线程相关

线程的启动和终止

  1. 线程启动
    (1)继承Thread类,此类实现了Runnable接口,调用start()启动线程。
    (2)实现Runnable接口,然后调用Thread类的start()启动线程。
    (3)实现Callable接口,通过FutureTask包装器来创建Thread线程,通常使用这种方式来在线程执行的过程中返回

  2. 线程终止
    (1)可以调用线程的interrupt()方法进行线程终止
    (2)可以调用Thread.interrupted()来检测线程是否中断,此方法是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
    (3)可以调用thread.isInterrupted()来判断是否被中断,此方式是成员方法,但是不会重置当前线程的中断状态

线程间通信

  1. 共享内存机制:使用同步关键字synchronized,线程通过对数据加锁,使其他线程不能访问加锁数据,实现通信,除此之外,还可以使用while循环操作,访问线程共享数据进行通信
  2. 消息通信机制:wait/notify机制,当线程调用wait方法时,进入了线程阻塞状态,当有线程调用notify方法时,会唤醒正在阻塞的线程,被唤醒的线程会接着执行。

sleep与wait对比

  1. sleep方法是Thread类中的静态方法,wait是Object类中的方法
  2. sleep并不会释放同步锁,而wait会释放同步锁
  3. sleep可以在任何地方使用,而wait只能在同步方法或者同步代码块中使用
  4. sleep中必须传入时间,而当wait不传时间的话只有notify或者notifyAll才能唤醒,传时间的话在时间之后会自动唤醒

线程池相关

使用线程池的原因

  1. 工作线程执行时间短,频繁的创建与销毁线程会浪费大量的性能与效率
  2. 有的工作线程通常都是执行相同的任务,线程池可以对线程进行集中管理和复用

线程池中重要的参数

  1. 构造方法

    public class ThreadPoolExecutor extends AbstractExecutorService {
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue);
    
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
    
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    }
    
  2. 参数意义
    (1)corePoolSize:核心线程池的大小,在创建了线程池后,线程池中的线程数默认为0,当有任务来之后,就会创建线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
    (2)maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程
    (3)keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。当线程池中的线程数大于corePoolSize时,如果线程空闲的时间达到此值,线程就会终止,直到线程池中的线程数小于corePoolSize
    (4)unit:参数keepAliveTime的时间单位(天、时、分、秒、毫秒、微秒、纳秒)
    (5)workQueue:阻塞队列,用来存储等待执行的任务
    (6)threadFactory:线程工厂,主要用来创建线程
    (7)handler:表示当拒绝处理任务时的策略

线程池中几种常见的工作队列

  1. ArrayBlockingQueue 数组型阻塞队列
    (1)有界队列,如果容量满无法继续添加元素直至有元素被移除
    (2)初始化一定容量的数组
    (3)使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥
    (4)使用时开辟连续的内存,如果初始化容量过大容易造成资源浪费,过小易添加失败
  2. LinkedBlockingQueue 链表型阻塞队列
    (1)有界队列,在默认构造方法中容量是Integer.MAX_VALUE
    (2)内部使用节点关联,会产生多一点内存占用
    (3)使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待
    (4)非连续性内存空间
  3. DelayQueue 延时队列
    (1)无界队列,添加不阻塞,移除阻塞
    (2)元素都有一个过期时间,只有当元素过期时,才会被取出
  4. PriorityBlockingQueue 优先阻塞队列
    (1)无界队列,但容量实际是依靠系统资源影响
    (2)当队列中元素数量超过1,会出现优先级排序
  5. SynchronousQueue 同步队列
    (1)内部容量是0,此队列本身不会存储元素
    (2)每次删除操作都要等待插入操作,每次插入操作都要等待删除操作,双方存在就会配对成功,否则进入阻塞状态
    (3)性能较好,在多任务队列,是最快的处理任务方式。

几种常见的线程池及使用场景

在开发的过程中,不提倡使用ThreadPoolExcutor来创建线程池,而是提倡使用下面的静态方法来创建

// 创建固定容量大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, 0L,
        TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
// 创建一个大小的线程池
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L,
        TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
// 创建最大容量的线程池
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
        TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
// 创建定时执行任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

Jvm相关

Java内存结构及分区

  1. 内存结构
    (1)线程私有的区域:程序计数器、虚拟机栈、本地方法栈
    (2)线程共有的区域:堆、方法区、运行时常量池
  2. 具体介绍
    (1)程序计数器:每个线程有有一个私有的程序计数器,任何时间一个线程都只会有一个方法正在执行,也就是所谓的当前方法,程序计数器存放的就是这个当前方法的JVM指令地址。
    (2)虚拟机栈:创建线程的时候会创建线程内的虚拟机栈,栈中存放着一个个的栈帧,对应着一个个方法的调用。虚拟机栈有两种操作,分别是进栈和出栈。栈帧中存放着局部变量表、方法返回值和方法的正常或异常退出的定义等等。
    (3)本地方法栈:跟JVM虚拟机栈比较类似,只不过它支持的是Native方法。
    (4)堆:堆是内存管理的核心区域,用来存放对象实例。几乎所有创建的对象实例都会直接分配到堆上。所以堆也是GC的主要区域,垃圾收集器会对堆有着更细的划分,最常见的就是把堆划分为新生代和老年代。
    (5)方法区:方法区主要存放类的结构信息,比如静态属性和方法等等。
    (6)运行时常量池:运行时常量池位于方法区中,主要存放各种常量信息。
  3. 发生OOM的区域
    (1)除了程序计数器,其他的部分都会发生OOM,但通常发生的OOM都会发生在堆,最常见的可能导致OOM的原因就是内存泄漏。
    (2)虚拟机栈和本地方法栈:当我们写一个递归方法,这个递归方法没有循环终止条件,最终会导致StackOverflow的错误。

Java虚拟机中的垃圾回收机制

  1. 找到垃圾
    (1)引用计数法:当一个对象被引用时,它的引用计数器会加一,垃圾回收时会清理掉引用计数为0的对象。但这种方法有一个问题,比方说有两个对象A和B,A引用了B,B又引用了A,除此之外没有别的对象引用A和B,那么A和B在我们看来已经是垃圾对象,需要被回收,但它们的引用计数不为0,没有达到回收的条件。正因为这个循环引用的问题,Java并没有采用引用计数法。
    (2)可达性分析法:我们把Java中对象引用的关系看做一张图,从根级对象不可达的对象会被垃圾收集器清除。根级对象一般包括Java虚拟机栈中的对象、本地方法栈中的对象、方法区中的静态对象和常量池中的常量。
  2. 回收垃圾算法
    (1)标记清除算法:顾名思义分为两步,标记和清除。首先标记到需要回收的垃圾对象,然后回收掉这些垃圾对象。标记清除算法的缺点是清除垃圾对象后会造成内存的碎片化。
    (2)复制算法:复制算法是将存活的对象复制到另一块内存区域中,并做相应的内存整理工作。复制算法的优点是可以避免内存碎片化,缺点也显而易见,它需要两倍的内存。
    (3)标记整理算法:标记整理算法也是分两步,先标记后整理。它会标记需要回收的垃圾对象,清除掉垃圾对象后会将存活的对象压缩,避免了内存的碎片化。
    (4)分代算法:分代算法将对象分为新生代和老年代对象,在Java运行中会产生大量对象,这些对象的生命周期会有很大的不同,针对不同生命周期的对象采用不同的回收策略,这样可以提高GC的效率。
  3. 具体回收机制过程:
    (1)新生代对象分为三个区域:Eden、Survivor1、Survivor2。新创建的对象都放在Eden区,当Eden区的内存达到阈值之后会触发GC,这时会将存活的对象复制到一个Survivor区中,这些存活对象的生命存活计数会加一。
    (2)这时Eden区会闲置,当再一次达到阈值触发GC时,会将Eden区和之前一个Survivor区中存活的对象复制到另一个Survivor区中,采用的是之前提到的复制算法,同时它们的生命存活计数也会加一。这个过程会持续很多遍。
    (3)当对象的存活计数达到一定的阈值后会触发一个叫做晋升的现象:新生代的这个对象会被放置到老年代中。老年代中的对象都是经过多次GC依然存活的生命周期很长的Java对象。当老年代的内存达到阈值后会触发GC,采用的是标记整理算法。
    (4)永久代中的对象在程序结束之前无法被回收,一般是用于静态对象。

Java类加载过程

  1. 类加载是将字节码数据从不同的数据源读取到JVM内存,并映射为JVM认可的数据结构Class对象的过程。数据源可以是Jar文件、class文件等。
  2. 链接是类加载的核心部分,这一步分为3个步骤:验证、准备、解析。
    (1)验证是保证JVM安全的重要步骤。JVM需要校验字节信息是否符合规范,避免恶意信息和不规范数据危害JVM运行安全。如果验证出错,则会报VerifyError错误。
    (2)准备主要是创建静态变量,并为静态变量开辟内存空间。
    (3)解析主要是将符号引用替换为直接引用。
  3. 初始化会为静态变量赋值,并执行静态代码块中的逻辑。

Java类加载器(双亲委派模型)

  1. 类加载器大致分为3类:启动类加载器、扩展类加载器、应用程序类加载器。
    (1)启动类加载器主要加载 jre/lib下的jar文件。
    (2)扩展类加载器主要加载 jre/lib/ext 下的jar文件。
    (3)应用程序类加载器主要加载 classpath下的文件。
  2. 所谓的双亲委派模型就是当加载某个类时,会优先使用父类加载器加载,当父类加载器无法加载时才会使用子类加载器去加载。这么做的目的是为了避免类的重复加载。

并发相关

IO相关

网络知识点

TCP与UDP相关

HTTP相关

设计模式知识点

算法知识点

常见的排序算法

冒泡排序

插入排序

选择排序

快速排序

归并排序

Android基础知识点

Activity

典型状况下的生命周期

异常状况下的生命周期

异常情况下的数据保存

Activity的启动模式及应用场景

  1. Standard:标准模式,也是系统的默认模式,每次启动Activity时,就会创建新的Activity实例,此Activity的启动Activity所在的任务栈即为该Activity所在的任务栈。若是没有任务栈的context启动Activity时,会出现异常,可以给新的Activity设置flag为FLAG_ACTIVITY_NEW_TASK,创建一个新的任务栈即可。

  2. SingleTop:栈顶复用模式,在此模式下,当需要启动的Activity位于任务栈的栈顶时,不会重新创建Activity实例,而是会复用栈顶的Activity实例,除此之外,与标准模式完全相同。

  3. SingleTask:栈内复用模式,此模式属于栈内单例模式,在此模式下,若是栈内存在需要启动的Activity实例时,不会重新创建Activity实例,而是会复用栈内的Activity实例,并且会把位于此实例顶部的Activity实例全部清除。

  4. SingleInstance:全局单例模式,此模式除了具有SingleTask所有特性之外,还会将创建的实例放入到单独的任务栈中,此任务栈只存在一个Activity实例。

设置Activity的启动模式

  1. 在AndroidManifest中对Activity指定launchMode属性

  2. 在Intent跳转时,对Intent的flag属性进行设置

选项 含义
FLAG_ACTIVITY_NEW_TASK 对应SingleTask模式
FLAG_ACTIVITY_SINGLE_TOP 对应SingleTop模式
FLAG_ACTIVITY_CLEAR_TOP 清除顶部所有实例,配合FLAG_ACTIVITY_NEW_TASK使用
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 不会出现在历史记录中

注意:两种方式都可以进行设置,但是后者优于前者

Activity的启动流程

  1. 过程简述

    • 启动流程简述:启动Activity需要调用startActivity,然后会将启动工作交给Instrumentation,与AMS进行进程间通信(在api25之前使用远程代理,在api26后使用AIDL来进行通信

    • 当AMS接收到消息后,会依次:检测权限、安全性检测,随后执行一系列方法,然后将启动的Activity切换到onPause状态,使用WindowManager将窗口隐藏掉

    • 最后通过传入的UID与包名判断需要打开的Activity所属进程是否已经开启,若未开启,则执行AMS的startProcessLocked方法创建进程,再开启Activity。

  2. 方法调用

    • 执行Activity的startActivity方法

    • 执行Activity的startActivityForResult方法

    • 执行Instrumentation的execStartActivity方法,Instrumentation可以认为是管理Activity生命周期的类,主要作用是通过与AMS的通信来对Activity的生命周期进行调控,然后此时进行了进程间通信,进入到系统服务进程(在api25之前使用远程代理,在api26后使用AIDL来进行通信

    • 执行AMS的startActivity方法

    • 执行AMS的startActivityAsUser方法,首先会调用enforceNotIsolatedCaller方法,进行安全检查,判断当前用户是否有权限启动activity,随后执行mUserController.handleIncomingUser来对传入的userId进行安全性检测和转换。

      • 执行ActivityStarter的一系列相关方法,ActivityStarter可以看作ActivityTask的管理类

      • 执行ActivityStarter的startActivityMayWait方法

      • 执行ActivityStarter的startActivityLocked方法

      • 执行ActivityStarter的startActivity方法

      • 执行ActivityStarter的doPendingActivityLaunchesLocked方法

      • 执行ActivityStarter的startActivity方法(此方法为重载方法)

      • 执行ActivityStarter的startActivityUnchecked方法,执行resumeTargetStackIfNeeded将所有ActivityStack栈顶的Activity切换到resume状态。

    • 执行ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法

    • 执行ActivityStack的resumeTopActivityUncheckedLocked方法

    • 执行ActivityStack的resumeTopActivityInnerLocked方法,将启动的Activity切换到onPause状态,使用WindowManager将窗口隐藏掉,此时之前的所有进程的Activity都进入不可见状态

    • 执行ActivityStackSupervisor的startSpecificActivityLocked方法,首先会通过包名和UID取得ProcessRecord,判断它是否创建。

    • 当被启动的进程已创建,执行ActivityStackSupervisor的realStartActivityLocked方法(此过程为应用内启动

    • 当被启动的进程未创建,执行AMS的startProcessLocked方法创建进程(此过程为冷启动

      • 首先判断当前需要创建的进程是否为隔离进程,不是的话再调用newProcessRecordLocked来重新创建新的进程,最后调用startProcessLocked来创建新线程,然后调用start方法

      • 创建新线程的过程是通过Zygote进程来实现的。Zygote进程是Linux系统所有进程的母进程,其他所有进程都是通过Zygote进程fork出来的

      • 在Zygote进程创建完成之后,会直接调用新线程的main方法来进行一系列初始化操作 ,比如:创建主线程的Handler,然后调用Looper.loop,初始化Environment等

      • 在Main函数中会调用ActivityThread的attach(false),此方法将ActivityThread的内部类ApplicationThread的对象传递给AMS,此时也是进行了进程间通信的过程(AIDL)

      • 然后调用attachApplication方法、attachApplicationLocked方法,发送message给主线程的Handler,对此message的处理有初始化操作,设置进程名,创建进程的Instrumentation、Application、装载Provider等,最后调用mInstrumentation.callApplicationOnCreate(app),也就是Application的onCreate方法,此时新进程已经完全创建成功。

      • 随后进入ActivityStatckSupervisor的attachApplicationLocked方法,该方法遍历mActivityDislays列表得到当前所有ActivityStack,然后取得前台得ActivityStack栈顶ActivityRecord,不为空则调用realStartActivityLockd()方法。

    • 最后在realStartActivityLockd方法中会执行app.thread.scheduleLaunchActivity(此Thread为ActivityThread的内部类ApplicationThread)

    • 此时Activity已经启动成功,在scheduleLaunchActivity中会发送H.LAUNCH_ACTIVITY的Message,然后会调用handleLaunchActivity方法与performLaunchActivity方法,在performLaunchActivity方法中会使用反射来得到Activity的相关方法。

Service

Service的定义及作用

Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件来启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信。

Service两种启动方式区别及生命周期

  1. 启动服务:该服务在其他组件调用 startService() 时创建,然后运行,且必须通过调用 stopSelf() 来自行停止运行。此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。

  2. 绑定服务:该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。(服务不必自行停止运行。)

Service绑定服务的三种实现方式

  1. 使用IBinder实现类

    在创建IBinder的实现类,然后在实现类中定义返回Service实例的方法,在Activity中绑定服务,在Conn中可以通过Binder来获取到Service的实例,然后通过实例来调用Service的方法,实现进程间通信。

  2. 使用Mesenger信使

    与第一种方法基本相同,因为Messenger信使也是实现了IBinder,所以也是可以进行进程间通信的,但是不同的是,Messenger可以携带Message数据。

  3. 使用AIDL(Android接口定义语言)

    详细见进程间通信

关于启动服务与绑定服务间的转换问题 、

  1. 先绑定服务后启动服务:如果当前Service实例先以绑定状态运行,然后再以启动状态运行,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,服务还是会一直运行下去,指定收到调用停止服务或者内存不足时才会销毁该服务。

  2. 先启动服务后绑定服务:如果当前Service实例先以启动状态运行,然后再以绑定状态运行,当前启动服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到有Context调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。

Android 5.0 以上的隐式启动问题及其解决方案

将隐式意图转换成显示意图,通过读取设备内部所有应用注册的服务信息,然后ACTION与所有的服务信息作匹配,匹配成功后,通过ComponentName来开启Service。

如何保证服务不被杀死

  1. onStartCommand方法,返回START_STICKY
  2. 在清单文件中提升Service优先级
  3. 在清单文件中提升service进程优先级,将Service设置为前台进程
  4. 在onDestroy方法里重启service
  5. 监听系统广播判断Service状态
  6. ROOT之后将app放到system/app变成系统级应用
  7. 设置单个像素的界面在前台

IntentService的使用及原理

BroadcastReceiver相关

Fragment相关

序列化相关

进程间通信

View事件机制相关

View绘制相关

View动画相关

Handler相关

Handler机制之Looper、Handler、消息队列如何理解

  1. 简单来说,Handler 的异步消息分发机制,就是通过 Handler 将创建好的消息放到消息队列中,然后使用 Looper 来将消息进行循环,使用 Handler 取出消息并对其进行处理的机制。

  2. MessageQueue 消息队列作用是用来存储消息,虽然称为消息队列,但是MessageQueue其实是采用单链表的数据结构来进行消息的存储,通常包含两个操作:插入(enqueueMessage) 和 读取(next),插入是将消息添加进消息队列,next是一个无限循环的方法,从消息列表中读取某条消息,并将其从消息队列中移除,这些操作都是对单链表的操作,如果消息列表里没有消息,那么会一直阻塞在这里。

  3. Handler 的主要用途是将创建好的消息发送到消息队列中,然后按照需求对消息队列中的消息取出处理。当消息创建完毕之后,通过 Handler 将其发送到消息队列,发送方法有很多,但是最终都是通过 sendMessage() 进行发送。sendMessage() 会依次调用 sendMessageDelayed()、sendMessageAtTime()、enqueueMessage(),将消息通过 enqueueMessage() 添加到消息队列中,Looper.loop() 调用消息队里的 next() 获取消息,如果没有消息,next() 会一直阻塞在那里,导致 loop() 也会阻塞在那里。如果 next() 返回了新的消息,Looper 接收到消息之后会通过 msg.target.dispatchMessage(msg) 分发给 Handler 处理。msg.target 是 Message 中维护的一个 Handler 对象,在 dispatchMessage() 中,会调用 handleMessage() 方法,此方法是创建 Handler 时实现的方法。

Handler机制之Message的发送与取出

Handler机制之Message及Message的回收机制

Handler机制之循环消息队列的退出

Handler机制之内存泄漏

AsyncTask相关

Bitmap相关

ListView与RecyclerView相关

数据存储相关

性能优化相关

打包签名相关

架构相关

Android不同版本特性知识点

Android框架知识

OkHttp相关

Retrofit相关

RxJava相关

Glide相关

ButterKnife相关

EventBus相关

Android高阶知识点

组件化、插件化

热修复、热更新

AndroidX

JetPack

上一篇 下一篇

猜你喜欢

热点阅读