当前位置: 首页 > news >正文

自己做网站要会什么软件下载技术支持 贵阳贵阳网站建设

自己做网站要会什么软件下载,技术支持 贵阳贵阳网站建设,签合网站是哪个,公司运营策划方案Android 高级面试-3#xff1a;语言相关 1、Java 相关 1.1 缓存相关 问题#xff1a;LruCache 的原理#xff1f; 问题#xff1a;DiskLruCache 的原理#xff1f; LruCache 用来实现基于内存的缓存#xff0c;LRU 就是最近最少使用的意思#xff0c;LruCache 基于L…Android 高级面试-3语言相关 1、Java 相关 1.1 缓存相关 问题LruCache 的原理 问题DiskLruCache 的原理 LruCache 用来实现基于内存的缓存LRU 就是最近最少使用的意思LruCache 基于LinkedHashMap实现。LinkedHashMap 是在 HashMap 的基础之上进行了封装除了具有哈希功能还将数据插入到双向链表中维护。每次读取的数据会被移动到链表的尾部当达到了缓存的最大的容量的时候就将链表的首部移出。使用 LruCache 的时候需要注意的是单位的问题因为该 API 并不清楚要存储的数据是如何计算大小的所以它提供了方法供我们实现大小的计算方式。 DiskLruCache 与 LruCache 类似也是用来实现缓存的并且也是基于 LinkedHashMap 实现的。不同的是它是基于磁盘缓存的LruCache 是基于内存缓存的。所以DiskLruCache 能够存储的空间更大但是读写的速率也更慢。使用 DiskLruCache 的时候需要到 Github 上面去下载。OkHttp 和 Glide 的磁盘缓存都是基于 DiskLruCache 开发的。DiskLruCahce 内部维护了一个日志文件记录了读写的记录的信息。其他的基本都是基础的磁盘 IO 操作。 1.2 List 相关 问题ArrayList 与 LinkedList 区别 ArrayList 是基于动态数组底层使用System.arrayCopy()实现数组扩容查找值的复杂度为O(1)增删的时候可能扩容复杂度也比 LinkedList 高如果能够大概估出列表的长度可以通过在 new 出实例的时候指定一个大小来指定数组的初始大小以减少扩容的次数适合应用到查找多于增删的情形比如作为 Adapter 的数据的容器。LinkedList 是基于双向链表增删的复杂度为O(1)查找的复杂度为O(n)适合应用到增删比较多的情形。两种列表都不是线程安全的Vector是线程安全的但是它的线程安全的实现方式是通过对每个方法进行加锁所以性能比较低。 问题如何实现线程间安全地操作 List 具体使用哪种方式可以根据具体的业务逻辑进行选择。通常有以下几种方式 操作 List 的时候使用sychronized进行控制。我们可以在我们自己的业务方法上面进行加锁来保证线程安全。使用Collections.synchronizedList()进行包装。这个方法内部使用了私有锁来实现线程安全就是通过对一个全局变量进行加锁。调用我们的 List 的方法之前需要先获取该私有锁。私有锁可以降低锁粒度。使用并发包中的类比如在读多写少的情况下为了提升效率可以使用CopyOnWriteArrayList代替 ArrayList使用ConcurrentLinkedQueue代替 LinkedList. 并发容器中的 CopyOnWriteArrayList 在读的时候不加锁写的时候使用 Lock 加锁。ConcurrentLinkedQueue 则是基于 CAS在增删数据之前会先进行比较。 1.3 Map 相关 问题SparseArray 的原理 SparseArray 主要用来替换 Java 中的 HashMap因为 HashMap 将整数类型的键默认装箱成 Integer (效率比较低). 而 SparseArray 通过内部维护两个数组来进行映射并且使用二分查找寻找指定的键所以它的键对应的数组无需是包装类型。SparseArray 用于当 HashMap 的键是 Integer 的情况它会在内部维护一个 int 类型的数组来存储键。同理还有 LongSparseArray, BooleanSparseArray 等都是用来通过减少装箱操作来节省内存空间的。但是因为它内部使用二分查找寻找键所以其效率不如 HashMap 高所以当要存储的键值对的数量比较大的时候考虑使用 HashMap. 问题HashMap、ConcurrentHashMap 以及 HashTable? 问题hashmap 如何 put 数据从 hashmap 源码角度讲解掌握 put 元素的逻辑 HashMap (下称 HM) 是哈希表ConcurrentHashMap (下称 CHM) 也是哈希表它们之间的区别是 HM 不是线程安全的CHM 线程安全并且对锁进行了优化。对应 HM 的还有 HashTable (下称 HT)它通过对内部的每个方法加锁来实现线程安全效率较低。 HashMap 的实现原理HashMap 使用拉链法来解决哈希冲突即当两个元素的哈希值相等的时候它们会被方进一个桶当中。当一个桶中的数据量比较多的时候此时 HashMap 会采取两个措施要么扩容要么将桶中元素的数据结构从链表转换成红黑树。因此存在几个常量会决定 HashMap 的表现。在默认的情况下当 HashMap 中的已经被占用的桶的数量达到了 3/4 的时候会对 HashMap 进行扩容。当一个桶中的元素的数量达到了 8 个的时候如果桶的数量达到了 64 个那么会将该桶中的元素的数据结构从链表转换成红黑树。如果桶的数量还没有达到 64 个那么此时会对 HashMap 进行扩容而不是转换数据结构。 从数据结构上HashMap 中的桶中的元素的数据结构从链表转换成红黑树的时候仍然可以保留其链表关系。因为 HashMap 中的 TreeNode 继承了 LinkedHashMap 中的 Entry因此它存在两种数据结构。 HashMap 在实现的时候对性能进行了很多的优化获取对象的哈希码的时候会先使用哈希值的高 16 位与低 16 进行异或运算来提升哈希值的随机性。然后截取后面几位而不是取余的方式计算元素在数组中的索引。保证按照 2 的整数次幂进行扩容这样截取后面的几位就可以得到桶的索引。 因为每个桶的元素的数据结构有两种可能因此当对 HashMap 进行增删该查的时候都会根据结点的类型分成两种情况来进行处理。当数据结构是链表的时候处理起来都非常容易使用一个循环对链表进行遍历即可。当数据结构是红黑树的时候处理起来比较复杂。红黑树的查找可以沿用二叉树的查找的逻辑。 问题集合 Set 实现 Hash 怎么防止碰撞 问题HashSet 与 HashMap 怎么判断集合元素重复 问题HashMap 的实现与 HashSet 的区别 问题TreeMap 具体实现 HashSet 内部通过 HashMap 实现: HashMap 解决哈希冲突使用的是拉链法碰撞的元素会放进链表中链表长度超过 8并且桶的数量大于 64 的时候会将桶的数据结构从链表转换成红黑树。HashMap 在求得每个结点在数组中的索引的时候会使用对象的哈希码的高八位和低八位求异或来增加哈希码的随机性。当我们通过 put() 方法将一个键值对添加到哈希表当中的时候会根据哈希值和键是否相等两个条件进行判断只有当两者完全相等的时候才认为元素发生了重复。 HashSet 不允许列表中存在重复的元素HashSet 内部使用的是 HashMap 实现的。在我们向 HashSet 中添加一个元素的时候会将该元素作为键一个默认的对象作为值构成一个键值对插入到内部的 HashMap 中。 TreeMap 是基于红黑树实现的它要求用户要么在创建 TreeMap 的时候传入比较器要么键本身是 Comparable 的。当向树中插入一个键值对的时候它会根据键和已有键的比较的情况为新插入的键值对寻找一个合适的位置。 1.4 注解相关 问题对 Java 注解的理解 Java 注解在 Android 中比较常见的使用方式有 3 种 第一种方式是基于反射的。因为反射本身的性能问题所以它通常用来做一些简单的工作比如为类、类的字段和方法等添加额外的信息然后通过反射来获取这些信息。 第二种方式是基于 AnnotationProcessor 的也就是在编译期间动态生成样板代码然后通过反射触发生成的方法。比如 ButterKnife 就使用注解处理在编译的时候 find 使用了注解的控件并为其绑定值。然后当调用 bind() 的时候直接反射调用生成的方法。Room 也是在编译期间为使用注解的方法生成数据库方法的。在开发这种第三方库的时候还可能使用到 Javapoet 来帮助我们生成 Java 文件。 最后一种比较常用的方式是使用注解来取代枚举。因为枚举相比于常量有额外的内存开销所以开发的时候通常使用常量来取代枚举。但是如果只使用常量我们无法对传入的常量的范围进行限制因此我们可以使用注解来限制取值的范围。以整型为例我们会在定义注解的时候使用注解 IntDef({/*各种枚举值*/}) 来指定整型的取值范围。然后使用注解修饰我们要方法的参数即可。这样 IDE 会给出一个提示信息提示我们只能使用指定范围的值。 1.5 Object 相关 问题Object 类的 equal() 和 hashcode() 方法重写 这两个方法都具有决定一个对象身份功能所以两者的行为必须一致覆写这两个方法需要遵循一定的原则。可以从业务的角度考虑使用对象的唯一特征比如 ID 等或者使用它的全部字段来进行计算得到一个整数的哈希值。一般我不会直接覆写该方法除非业务特征非常明显。因为一旦修改之后它的作用范围将是全局的。我们还可以通过 IDEA 的 generate 直接生成该方法。 问题Object 都有哪些方法 wait() notify(), 用来对线程进行控制以让当前线程等待直到其他线程调用了 notify()/notifyAll() 方法。wait() 发生等待的前提是当前线程获取了对象的锁监视器。调用该方法之后当前线程会释放获取到的锁然后让出 CPU进入等待状态。notify/notifyAll() 的执行只是唤醒沉睡的线程而不会立即释放锁锁的释放要看代码块的具体执行情况。clone() 与对象克隆相关的方法深拷贝 浅拷贝的问题finilize()toString()equal() hashCode() 1.6 字符串相关 问题StringBuffer 与 StringBuilder 的区别 前者是线程安全的每个方法上面都使用 synchronized 关键字进行了加锁后者是非线程安全的。一般情况下使用 StringBuilder 即可因为非多线程环境进行加锁是一种没有必要的开销。 问题对 Java 中 String 的了解 String 不是基本数据类型。String 是不可变的JVM 使用字符串池来存储所有的字符串对象。使用 new 创建字符串这种方式创建的字符串对象不存储于字符串池。我们可以调用intern() 方法将该字符串对象存储在字符串池如果字符串池已经有了同样值的字符串则返回引用。使用双引号直接创建字符串的时候JVM 先去字符串池找有没有值相等字符串如果有则返回找到的字符串引用否则创建一个新的字符串对象并存储在字符串池。 问题String 为什么要设计成不可变的 线程安全由于 String 是不可变类所以在多线程中使用是安全的我们不需要做任何其他同步操作。String 是不可变的它的值也不能被改变所以用来存储数据密码很安全。复用/节省堆空间实际在 Java 的开发当中 String 是使用最为频繁的类之一通过 dump 的堆可以看出它经常占用很大的堆内存。因为 java 字符串是不可变的可以在 java 运行时节省大量 java 堆空间。不同的字符串变量可以引用池中的相同的字符串。如果字符串是可变得话任何一个变量的值改变就会反射到其他变量那字符串池也就没有任何意义了。 问题常见编码方式有哪些 Utf-8, Unicode, ASCII 问题Utf-8 编码中的中文占几个字节 UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1-6 个字节常用的英文字母被编码成 1 个字节汉字通常是 3 个字节只有很生僻的字符才会被编码成 4-6 个字节。 1.7 线程控制 问题开启线程的三种方式run() 和 start() 方法区别 问题如何保证线程安全太泛synchronized, voliate, Lock, 线程安全集合 问题如何保证多线程读写文件的安全 线程启动的三种方式 // 方式 1Thread 覆写 run() 方法private class MyThread extends Thread {Overridepublic void run() {// 业务逻辑}}// 方式 2Thread Runnablenew Thread(new Runnable() {public void run() {// 业务逻辑}}).start();// 方式 3ExectorService CallableExecutorService executor Executors.newFixedThreadPool(5);ListFutureInteger results new ArrayListFutureInteger();for (int i0; i5; i) {results.add(executor.submit(new CallableTask(i, i)));}线程启动run() 和 start() 方法区别 start() 会调用 native 的 start() 方法然后 run() 方法会被回调此时 run() 异步执行如果直接调用 run()它会使用默认的实现除非覆写了并且会在当前线程中执行此时 Thread 如同一个普通的类。Thread 的 run() 方法的默认实现如下 private Runnable target;public void run() {if (target ! null) target.run();}问题线程如何关闭以及如何防止线程的内存泄漏 有两种方式可以用来关闭线程。一种是使用中断标志位进行判断。当需要停止线程的时候调用线程的 interupt() 方法即可。这种情况下需要注意的地方是当线程处于阻塞状态的时候调用了中断方法此时会抛出一个异常并将中断标志位复位。此时我们是无法退出线程的。所以我们需要同时考虑一般情况和线程处于阻塞时中断两种情况。另一个方案是使用一个 volatile 类型的布尔变量使用该变量来判断是否应该结束线程。两种方案的示例代码如下 // 方式 1使用中断标志位Overridepublic void run() {try {while (!isInterrupted()) {// do something}} catch (InterruptedException ie) { // 线程因为阻塞时被中断而结束了循环}}private static class MyRunnable2 implements Runnable {// 注意使用 volatile 修饰private volatile boolean canceled false;Overridepublic void run() {while (!canceled) {// do something}}public void cancel() {canceled true;}}防止线程内存泄漏 在 Activity 等中使用线程的时候将线程定义成静态内部类非静态内部类会持有外部类的匿名引用当需要在线程中调用 Activity 的方法的时候使用 WeakReference 引用 Activity或者当 Activity 需要结束的时候在 onDestroy() 方法中终止线程。 问题Java 线程池、线程池的几个核心参数的意义 问题多线程怎么用、有什么问题要注意Android 线程上限然后提到线程池的上限 Android 中并没有明确规定可以创建的线程的数量但是每个进程的资源是有限的线程本身会占有一定的资源所以受内存大小的限制会有数量的上限。通常我们在使用线程或者线程池的时候不会创建太多的线程。线程池的大小经验值应该这样设置其中 N 为 CPU 的核数 如果是 CPU 密集型应用则线程池大小设置为 N 1(大部分时间在计算)如果是 IO 密集型应用则线程池大小设置为 2N 1(大部分时间在读写Android) 下面是 Android 中的 AysncTask 中创建线程池的代码创建线程池的核心参数的说明已经家在了注释中 // CPU 的数量private static final int CPU_COUNT Runtime.getRuntime().availableProcessors();// 核心线程的数量只有提交任务的时候才会创建线程当当前线程数量小于核心线程数量新添加任务的时候会创建新线程来执行任务private static final int CORE_POOL_SIZE Math.max(2, Math.min(CPU_COUNT - 1, 4));// 线程池允许创建的最大线程数量当任务队列满了并且当前线程数量小于最大线程数量则会创建新线程来执行任务private static final int MAXIMUM_POOL_SIZE CPU_COUNT * 2 1;// 非核心线程的闲置的超市时间超过这个时间线程将被回收如果任务多且执行时间短应设置一个较大的值private static final int KEEP_ALIVE_SECONDS 30;// 线程工厂自定义创建线程的策略比如定义一个名字private static final ThreadFactory sThreadFactory new ThreadFactory() {private final AtomicInteger mCount new AtomicInteger(1);public Thread newThread(Runnable r) {return new Thread(r, AsyncTask # mCount.getAndIncrement());}};// 任务队列如果当前线程的数量大于核心线程数量就将任务添加到这个队列中private static final BlockingQueueRunnable sPoolWorkQueue new LinkedBlockingQueueRunnable(128);public static final Executor THREAD_POOL_EXECUTOR;static {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(/*corePoolSize*/ CORE_POOL_SIZE,/*maximumPoolSize*/ MAXIMUM_POOL_SIZE, /*keepAliveTime*/ KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,/*workQueue*/ sPoolWorkQueue, /*threadFactory*/ sThreadFactory/*handler*/ defaultHandler); // 饱和策略AysncTask 没有这个参数threadPoolExecutor.allowCoreThreadTimeOut(true);THREAD_POOL_EXECUTOR threadPoolExecutor;}饱和策略任务队列和线程池都满了的时候执行的逻辑Java 提供了 4 种实现 当调用了线程池的 prestartAllcoreThread() 方法的时候线程池会提前启动并创建所有核心线程来等待任务当调用了线程池的 allowCoreThreadTimeOut() 方法的时候超时时间到了之后闲置的核心线程也会被移除。 问题wait() 与 sleep() 的区别 wait()、notify() 和 notifyAll() 方法是 Object 的 final 方法无法被重写。wait() 使当前线程阻塞直到接到通知或被中断为止。前提是必须先获得锁一般配合 synchronized 关键字使用在 synchronized 同步代码块里使用 wait()、notify() 和 notifyAll() 方法。如果调用 wait() 或者 notify() 方法时线程并未获取到锁的话则会抛出 IllegalMonitorStateException 异常。再次获取到锁当前线程才能从 wait() 方法处成功返回。由于 wait()、notify() 和 notifyAll() 在 synchronized 代码块执行说明当前线程一定是获取了锁的。当线程执行 wait() 方法时候会释放当前的锁然后让出 CPU进入等待状态。只有当 notify()/notifyAll() 被执行时候才会唤醒一个或多个正处于等待状态的线程然后继续往下执行直到执行完 synchronized 代码块或是中途遇到 wait()再次释放锁。也就是说notify()/notifyAll() 的执行只是唤醒沉睡的线程而不会立即释放锁锁的释放要看代码块的具体执行情况。所以在编程中尽量在使用了 notify()/notifyAll() 后立即退出临界区以唤醒其他线程。wait() 需要被 try catch 包围中断也可以使 wait 等待的线程唤醒。notify() 和 wait() 的顺序不能错如果 A 线程先执行 notify() 方法B 线程再执行 wait() 方法那么 B 线程是无法被唤醒的。notify() 和 notifyAll() 的区别notify() 方法只唤醒一个等待对象的线程并使该线程开始执行。所以如果有多个线程等待一个对象这个方法只会唤醒其中一个线程选择哪个线程取决于操作系统对多线程管理的实现。notifyAll() 会唤醒所有等待 (对象的) 线程尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒推荐使用 notifyAll() 方法。比如在生产者-消费者里面的使用每次都需要唤醒所有的消费者或是生产者以判断程序是否可以继续往下执行。 对于 sleep() 和 wait() 方法之间的区别总结如下 所属类不同sleep() 方法是 Thread 的静态方法而 wait() 是 Object 实例方法。调用位置不同wait() 方法必须要在同步方法或者同步块中调用也就是必须已经获得对象锁。而 sleep() 方法没有这个限制可以在任何地方种使用。锁释放不同wait() 方法会释放占有的对象锁使得该线程进入等待池中等待下一次获取资源。而 sleep() 方法只是会让出 CPU 并不会释放掉对象锁线程唤醒不同sleep() 方法在休眠时间达到后如果再次获得 CPU 时间片就会继续执行而 wait() 方法必须等待 Object.notift()/Object.notifyAll() 通知后才会离开等待池并且再次获得 CPU 时间片才会继续执行。 问题线程的状态 新建 (NEW)新创建了一个线程对象。可运行 (RUNNABLE)线程对象创建后其他线程(比如 main 线程调用了该对象的 start() 方法。该状态的线程位于可运行线程池中等待被线程调度选中获取 CPU 的使用权 。运行 (RUNNING)RUNNABLE 状态的线程获得了 CPU 时间片timeslice 执行程序代码。阻塞 (BLOCKED)阻塞状态是指线程因为某种原因放弃了 CPU 使用权也即让出了 CPU timeslice暂时停止运行。直到线程进入 RUNNABLE 状态才有机会再次获得 CPU timeslice 转到 RUNNING 状态。阻塞的情况分三种 等待阻塞RUNNING 的线程执行 o.wait() 方法JVM 会把该线程放入等待队列 (waitting queue) 中。同步阻塞RUNNING 的线程在获取对象的同步锁时若该同步锁被别的线程占用则 JVM 会把该线程放入锁池 (lock pool) 中。其他阻塞RUNNING 的线程执行 Thread.sleep(long) 或 t.join() 方法或者发出了 I/O 请求时JVM 会把该线程置为阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时线程重新转入 RUNNABLE 状态。 死亡 (DEAD)线程 run()、main() 方法执行结束或者因异常退出了 run() 方法则该线程结束生命周期。死亡的线程不可再次复生。 问题死锁线程死锁的 4 个条件 问题死锁的概念怎么避免死锁 当两个线程彼此占有对方需要的资源同时彼此又无法释放自己占有的资源的时候就发生了死锁。发生死锁需要满足下面四个条件 互斥某种资源一次只允许一个进程访问即该资源一旦分配给某个进程其他进程就不能再访问直到该进程访问结束。一个筷子只能被一个人拿占有且等待一个进程本身占有资源一种或多种同时还有资源未得到满足正在等待其他进程释放该资源。每个人拿了一个筷子还要等其他人放弃筷子不可抢占别人已经占有了某项资源你不能因为自己也需要该资源就去把别人的资源抢过来。别人手里的筷子你不能去抢循环等待存在一个进程链使得每个进程都占有下一个进程所需的至少一种资源。每个人都在等相邻的下一个人放弃自己的筷子 产生死锁需要四个条件那么只要这四个条件中至少有一个条件得不到满足就不可能发生死锁了。由于互斥条件是非共享资源所必须的不仅不能改变还应加以保证所以主要是破坏产生死锁的其他三个条件。 破坏占有且等待的问题允许进程只获得运行初期需要的资源便开始运行在运行过程中逐步释放掉分配到的已经使用完毕的资源然后再去请求新的资源。破坏不可抢占条件当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时它必须释放已经保持的所有资源待以后需要使用的时候再重新申请。释放已经保持的资源很有可能会导致进程之前的工作实效等反复的申请和释放资源会导致进程的执行被无限的推迟这不仅会延长进程的周转周期还会影响系统的吞吐量。破坏循环等待条件可以通过定义资源类型的线性顺序来预防可将每个资源编号当一个进程占有编号为i的资源时那么它下一次申请资源只能申请编号大于 i 的资源。 问题synchronized 的实现原理 问题如何实现线程同步synchronized, lock, 无锁同步, voliate, 并发集合同步集合 sychronized 代码块的同步原理Java 虚拟机中的同步基于进入和退出管程 (Monitor) 对象实现无论是显式同步 (有明确的 monitorenter 和 monitorexit 指令即同步代码块)还是隐式同步都是如此。进入 monitorenter 时 monitor 中的计数器 count 加 1释放当前持有的 monitorcount 自减 1. 反编译代码之后经常看到两个 monitorexit 指令对应一个 monitorenter这是用来防止程序执行过程中出现异常的。虚拟机需要保证即使程序允许中途出了异常锁也一样可以被释放执行第二个 monitorexit。 sychronized 方法的同步原理对同步方法JVM 可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当调用方法时调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置如果设置了执行线程将先持有 monitor然后再执行方法最后再方法完成 (无论是正常完成还是非正常完成) 时释放 monitor. 在方法执行期间其他任何线程都无法再获得同一个 monitor. 如果一个同步方法执行期间抛出了异常并且在方法内部无法处理此异常那这个同步方法所持有的 monitor 将在异常抛到同步方法之外时自动释放。 sychronized 原理的底层实现原理在 Java 对象的对象头中有一块区域叫做 MarkWord其中存储了重量级锁 sychronized 的标志位其指针指向的是 monitor 对象。每个对象都存在着一个 monitor 与之关联。在 monitor 的数据结构中定义了两个队列_WaitSet 和 _EntryList. 当多个线程同时访问一段同步代码时首先会进入 _EntryList 集合当线程获取到对象的 monitor 后进入 _Owner 区域并把 monitor 中的 owner 变量设置为当前线程同时 monitor 中的计数器 count 加 1若线程调用 wait() 方法将释放当前持有的 monitorowner 变量恢复为 nullcount 自减 1同时该线程进入 _WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放 monitor (锁)并复位变量的值以便其他线程进入获取 monitor (锁)。 由此看来monitor 对象存在于每个 Java 对象的对象头中(存储的指针的指向)synchronized 锁便是通过这种方式获取锁的也是为什么 Java 中任意对象可以作为锁的原因同时也是 notify()/notifyAll()/wait() 等方法存在于顶级对象 Object 中的原因。 当然从 MarkWord 的结构中也可以看出 Java 对 sychronized 的优化Java 6 之后为了减少获得锁和释放锁所带来的性能消耗引入了轻量级锁和偏向锁锁效率也得到了优化。 问题synchronized 与 Lock 的区别 等待可中断对于 Lock当持有锁的线程长期不释放锁的时候正在等待的线程可以选择放弃等待两种方式获取锁的时候都会使计数1但是方式不同所以重入锁可以终端公平锁当多个线程等待同一个锁时公平锁会按照申请锁的时间顺序来依次获得锁而非公平锁当锁被释放时任何在等待的线程都可以获得锁不论时间尝试获取的时间先后。sychronized 只支持非公平锁Lock 可以通过构造方法指定使用公平锁还是非公平锁。锁可以绑定多个条件ReentrantLock 可以绑定多个 Condition 对象而 sychronized 要与多个条件关联就不得不加一个锁ReentrantLock 只要多次调用 newCondition 即可。 问题ReentrantLock 的内部实现 问题CAS 介绍 问题Lock 的实现原理 ReentrantLock 的实现是基于 AQS同步器同步器设计的思想是 CAS. 同步器中维护了一个链表借助 CAS 的思想向链表中增删数据。其底层使用的是 sun.misc.Unsafe 类中的方法来完成 CAS 操作的。在 ReentrantLock 中实现两个 AQS 的子类分别是 NonfairSync 和 FairSync. 也就是用来实现公平锁和非公平锁的关键。当我们使用构造方法获取 ReentrantLock 实例的时候可以通过一个布尔类型的参数指定使用公平锁还是非公平锁。在实现上 NonfairSync 和 FairSync 的区别仅仅是在当前线程获取到锁之前是否会从上述队列中判断是否存在比自己更早申请锁的线程。对于公平锁当存在这么一个线程的话那么当前线程获取锁失败。当当前线程获取到锁的时候也会使用一个 CAS 操作将锁获取次数 1. 当线程再次获取锁的时候会根据线程来进行判断如果当前持有锁的线程是申请锁的线程那么允许它再次获取锁以此来实现锁的可重入。 所谓 CAS 就是 Compare-And-Swape类似于乐观加锁。但与我们熟知的乐观锁不同的是它在判断的时候会涉及到 3 个值“新值”、“旧值” 和 “内存中的值”在实现的时候会使用一个无限循环每次拿 “旧值” 与 “内存中的值” 进行比较如果两个值一样就说明 “内存中的值” 没有被其他线程修改过否则就被修改过需要重新读取内存中的值为 “旧值”再拿 “旧值” 与 “内存中的值” 进行判断。直到 “旧值” 与 “内存中的值” 一样就把 “新值” 更新到内存当中。 这里要注意上面的 CAS 操作是分 3 个步骤的但是这 3 个步骤必须一次性完成因为不然的话当判断 “内存中的值” 与 “旧值” 相等之后向内存写入 “新值” 之间被其他线程修改就可能会得到错误的结果。JDK 中的 sun.misc.Unsafe 中的 compareAndSwapInt 等一系列方法 Native 就是用来完成这种操作的。另外还要注意上面的 CAS 操作存在一些问题 ABA 的问题也就是说当内存中的值被一个线程修改了又改了回去此时当前线程看到的值与期望的一样但实际上已经被其他线程修改过了。想要解决 ABA 的问题则可以使用传统的互斥同步策略。CAS 还有一个问题就是可能会自旋时间过长。因为 CAS 是非阻塞同步的虽然不会将线程挂起但会自旋无非就是一个死循环进行下一次尝试如果这里自旋时间过长对性能是很大的消耗。CAS 只能保证一个共享变量的原子性当存在多个变量的时候就无法保证。一种解决的方案是将多个共享变量打包成一个也就是将它们整体定义成一个对象并用 CAS 保证这个整体的原子性比如 AtomicReference。 问题volatile 原理和用法 voliate 关键字的两个作用 保证变量的可见性当一个被 voliate 关键字修饰的变量被一个线程修改的时候其他线程可以立刻得到修改之后的结果。当写一个 volatile 变量时JMM 会把该线程对应的工作内存中的共享变量值刷新到主内存中当读取一个 volatile 变量时JMM 会把该线程对应的工作内存置为无效那么该线程将只能从主内存中重新读取共享变量。屏蔽指令重排序指令重排序是编译器和处理器为了高效对程序进行优化的手段它只能保证程序执行的结果时正确的但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题但是在多线程中就会出现问题。非常经典的例子是在单例方法中同时对字段加入 voliate就是为了防止指令重排序。 volatile 是通过内存屏障(Memory Barrier 来实现其在 JMM 中的语义的。内存屏障又称内存栅栏是一个 CPU 指令它的作用有两个一是保证特定操作的执行顺序二是保证某些变量的内存可见性。如果在指令间插入一条内存屏障则会告诉编译器和 CPU不管什么指令都不能和这条 Memory Barrier 指令重排序。Memory Barrier 的另外一个作用是强制刷出各种 CPU 的缓存数据因此任何 CPU 上的线程都能读取到这些数据的最新版本。 问题手写生产者/消费者模式见文末 1.8 并发包 问题ThreadLocal 的实现原理 ThreadLocal 通过将每个线程自己的局部变量存在自己的内部来实现线程安全。使用它的时候会定义它的静态变量每个线程看似是从 TL 中获取数据而实际上 TL 只起到了键值对的键的作用实际的数据会以哈希表的形式存储在 Thread 实例的 Map 类型局部变量中。当调用 TL 的 get() 方法的时候会使用 Thread.currentThread() 获取当前 Thread 实例然后从该实例的 Map 局部变量中使用 TL 作为键来获取存储的值。Thread 内部的 Map 使用线性数组解决哈希冲突。 问题并发集合了解哪些 ConcurrentHashMap线程安全的 HashMap对桶进行加锁降低锁粒度提升性能。ConcurrentSkipListMap跳表自行了解给跪了……ConCurrentSkipListSet借助 ConcurrentSkipListMap 实现CopyOnWriteArrayList读多写少的 ArrayList写的时候加锁CopyOnWriteArraySet借助 CopyOnWriteArrayList 实现的……ConcurrentLinkedQueue无界且线程安全的 Queue其 poll() 和 add() 等方法借助 CAS 思想实现。锁比较轻量。 1.9 输入输出 问题NIO 问题多线程断点续传原理 断点续传和断点下载都是用的用的都是 RandomAccessFile它可以从指定的位置开始读取数据。断点续传是由服务器给客户端一个已经上传的位置标记position然后客户端再将文件指针移动到相应的 position通过输入流将文件剩余部分读出来传输给服务器。 如果要使用多线程来实现断点续传那么可以给每个线程分配固定的字节的文件分别去读然后分别上传到服务器。 2、Kotlin 相关 问题对 Kotlin 协程的了解 协程实际上就是极大程度的复用线程通过让线程满载运行达到最大程度的利用 CPU进而提升应用性能。相比于线程协程不需要进行线程切换和多线程比线程数量越多协程的性能优势就越明显。第二大优势就是不需要多线程的锁机制因为只有一个线程也不存在同时写变量冲突在协程中控制共享资源不加锁只需要判断状态就好了所以执行效率比多线程高很多。 协程和线程都能用来实现异步调用但是这两者之间是有本质区别的 协程是编译器级别的线程是系统级别的。协程的切换是由程序来控制的线程的切换是由操作系统来控制的。协程是协作式的线程是抢占式的。协程是由程序来控制什么时候进行切换的而线程是有操作系统来决定线程之间的切换的。一个线程可以包含多个协程。Java 中多线程可以充分利用多核 cpu协程是在一个线程中执行。4. 协程适合 IO 密集型 的程序多线程适合 计算密集型 的程序(适用于多核 CPU 的情况)。当你的程序大部分是文件读写操作或者网络请求操作的时候这时你应该首选协程而不是多线程首先这些操作大部分不是利用 CPU 进行计算而是等待数据的读写其次因为协程执行效率较高子程序切换不是线程切换是由程序自身控制因此没有线程切换的开销和多线程比线程数量越多协程的性能优势就越明显。使用协程可以顺序调用异步代码避免回调地狱。 问题Kotlin 跟 Java 比kotlin 具有哪些优势 Kotlin 是一门基于 JVM 的语言它提供了非常多便利的语法特性。如果说 Kotlin 为什么那么优秀的话那只能说是因为它站在了 Java 的肩膀上。学习了一段时间之后你会发现它的许多语法的设计非常符合我们实际开发中的使用习惯。 默认无法被继承比如对于一个类通常我们不会去覆写它。尤其是 Java Web 方向很多的类用来作为 Java Bean它们没有特别多的继承关系。而 Kotlin 中的类默认就是不允许继承的想允许自己的类被继承你还必须显式地使用 open 关键字指定。 省略 setter 和 getter对于 Java Bean作为一个业务对象它会有许多的字段。按照 Java 中的处理方式我们要为它们声明一系列的 setter 和 getter 方法。然后获取属性的时候必须使用 setter 和 getter 方法。导致我们的代码中出现非常多的括号。而使用 Kotlin 则可以直接对属性进行赋值显得优雅地多。IDEA 插件 Lombok 就是用来实现类似的功能。 条件默认 break再比如 Java 中使用 switch 的时候我们通常会在每个 case 后面加上 break而 kotlin 默认帮助我们 break这样就节省了很多的代码量。 空指针的处理另外 Kotlin 非常优秀的地方在于对 NPE 的控制。在 Android 开发中我们可以使用 NoneNull 和 Nullable 注解来标明某个字段是否可能为空。在 Java 中默认字段是空的并且没有任何提示。你一个不留神可能就导致了 NPE但 Kotlin 中就默认变量是非空的你想让它为空必须单独声明。这样对于可能为空的变量就给了我们提示的作用我们知道它可能为空就会去特意对其进行处理。对于可能为空的类Kotlin 定义了如下的规则使得我们处理起来 NPE 也变得非常简单 使用 ? 在类型的后面则说明这个变量是可空的安全调用运算符 ?.以 a?.method() 为例当 a 不为 null 则整个表达式的结果是 a.method() 否则是 nullElvis 运算符 ?:以 a ?: A 为例当 a 不为 null 则整个表达式的结果是 a否则是 “A”安全转换运算符 as?以 foo as? Type 为例当 foo 是 Type 类型则将 foo 转换成 Type 类型的实例否则返回 null非空断言 !!用在某个变量后面表示断言其非空如 a!!let 表示对调用 let 的实例进行某种运算如 val b AA.let { it A } 返回 “AAA” 诸如此类很多时候我觉得 Java 设计的一些规则对人们产生了误导实际开发中并不符合我们的使用习惯。而 Kotlin 则是根据多年来人们使用 Java 的经验简化了许多的调用更加符合我们使用习惯。所以说Kotlin 之所以强大是因为站在 Java 的肩膀上。 3、设计模式 问题谈谈你对 Android 设计模式的理解 问题项目中常用的设计模式有哪些 工厂策略用来创建各种实例比如美国一个实现中国一个实现的情形观察者一个页面对事件进行监听注册取消注册通知单例太多为了延迟初始化构建者类的参数太多为了方便调用适配器RecyclerView 的适配器模板设计一个顶层的模板类比如抽象的 Fragment 或者 Activity 等但是注意组合优于继承不要过度设计外观相机模块Camera1 和 Camera2封装其内部实现统一使用 CameraManager 的形式对外提供方法。 问题手写观察者模式 观察者设计模式类似于我们经常使用的接口回调下面的代码中在观察者的构造方法中订阅了主题其实这个倒不怎么重要什么时候订阅都可以。核心的地方就是主题中维护的这个队列需要通知的时候调一下通知的方法即可。另外如果在多线程环境中还要考虑如何进行线程安全控制比如使用线程安全的集合等等。下面只是一个非常基础的示例程序了解设计思想用的时候可以灵活一些不必循规蹈矩。见文末附录 问题手写单例模式懒汉和饱汉 // 饱汉就是在调用单例方法的时候实例已经初始化过了public class Singleton {private static Singleton singleton new Singleton();private Singleton() {}public static Singleton getInstance() {return singleton;}}// 懒汉在调用方法的时候才进行初始化public class Singleton {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton null) {sychronized(Singleton.class) {if (singleton null) {singleton new Singleton();}}}return singleton;}} 另外单例需要注意的问题是 如果用户使用反射进行初始化怎么办可以在创建第二个实例的时候抛出异常如果用户使用 Java 的序列化机制反复创建单例呢将所有的实例域设置成 transient 的然后覆写 readResolve() 方法并返回单例。 另外单实例太多的时候可以想办法使用一个 Map 将它们存储起来然后通过一种规则从哈希表中取出这样就没必要声明一大堆的单例变量了。 问题适配器模式、装饰者模式、外观模式、代理模式的异同这个几个设计模式比较容易混 四个设计模式相同的地方是它们都需要你传入一个类然后内部使用你传入的这个类来完成业务逻辑。 我们以字母 ABC 来表示 3 种不同的类某种东西。 外观模式要隐藏内部的差异提供一个一致的对外的接口 X那么让定义 3 个类 AX, BX, CX 并且都实现 X 接口其中分别引用 A, B, C 按照各自的方式实现 X 接口的方法即可。以相机开发为例Camera1 和 Camera2 各有自己的实现方式定义一个统一的接口和两个实现类。 假如现在有一个类 X其中引用到了接口 A 的实现 AX. AX 的逻辑存在点问题我们想把它完善一下。我们提供了 3 种方案分别是 A1, A2 和 A3. 那么此时我们让 A1, A2 和 A3 都实现 A 接口然后其中引用 AX 完成业务在实现的 A 接口的方法中分别使用各自的方案进行优化即可。这种方式我们对 AX 进行了修饰使其 A1, A2 和 A3 可以直接应用到 X 中。 对于适配器模式假如现在有一个类 X其中引用到了接口 A. 现在我们不得不使用 B 来完成 A 的逻辑。因为 A 和 B 属于两个不同的类所以此时我们需要一个适配器模式让 A 的实现 AX 引用 B 的实现 BX 完成 A 接口的各个方法。 外观模式的目的是隐藏各类间的差异性提供一致的对外接口。装饰者模式对外的接口是一致的但是内部引用的实例是同一个其目的是对该实例进行拓展使其具有多种功能。所以前者是多对一后者是一对多的关系。而适配器模式适用的是两个不同的类它使用一种类来实现另一个类的功能是一对一的。相比之下代理模式也是用一类来完成某种功能并且一对一但它是在同类之间目的是为了增强类的功能而适配器是在不同的类之间。装饰者和代理都用来增强类的功能但是装饰者装饰之后仍然是同类可以无缝替换之前的类的功能。而代理类被修饰之后已经是代理类了是另一个类无法替换原始类的位置。 问题设计模式相关例如Android中哪里使用了观察者模式单例模式相关 参考文献 《Android 内存缓存框架 LruCache 的源码分析》 《Java 注解及其在 Android 中的应用》 《死锁的四个必要条件和解决办法》 (关于 sychronized 的底层实现原理可以参考笔者的文章并发编程专题 3synchronized) 参考 《并发编程专题-5生产者和消费者模式》 中的三种写法。 (《ThreadLocal的使用及其源码实现》) 参考是继续Rxjava还是应该试试Kotlin的协程 - Android架构的文章 - 知乎 了解更多关于观察者设计模式的内容请参考文章设计模式解析观察者模式 了解更多关于单例设计模式的内容请参考文章设计模式-4单例模式 附录 1、生产者和消费者模式的三种写法 第一种写法基于 wait(), notify(), notifyAll() // 生产者private static class Consumer implements Runnable {private ListObject products;// 传入的对象是产品也就是说生产者和消费者通过产品建立联系public Consumer(ListObject products) {this.products products;}public void run() {while (true) { // 使用循环来不断消费synchronized (products) { // 对产品加锁 productswhile (products.isEmpty()) { // 1. 没有产品了try { // 调用 wait() 的时候使用 tru...catch 防止线程终端products.wait(); // 已经没有产品可以消费了} catch (InterruptedException e) {e.printStackTrace();}}products.remove(0); // 消费一个products.notifyAll(); // 通知其他线程System.out.println(Eat one. Left : products.size());}}}}// 消费者private static class Producer implements Runnable {private final int max; // 产品的上限private ListObject products;// 参数是产品的上限和产品列表理解成仓库和仓库的最大容量亦可public Producer(int max, ListObject products) {this.max max;this.products products;}public void run() {while (true) {synchronized (products) {while (products.size() max) { // 2. 大于上限就停止生产try {products.wait(); // 暂停生成} catch (InterruptedException e) {e.printStackTrace();}}products.add(new Object()); // 生成一个products.notifyAll(); // 唤醒System.out.println(Made one. Total : products.size());}}}}// 模拟 验证public static void main(String...args) {final int MAX_PRODUCTS 20;ListObject products new LinkedList();Executor executor Executors.newCachedThreadPool(); // 使用线程池executor.execute(new Producer(MAX_PRODUCTS, products));executor.execute(new Consumer(products));executor.execute(new Consumer(products));}第二种写法基于 ReentrantLock // 定义锁、仓库已满的条件和仓库为空的条件private static ReentrantLock lock new ReentrantLock();private static final Condition full lock.newCondition();private static final Condition empty lock.newCondition();// 消费者private static class Consumer implements Runnable {private ListObject products;public Consumer(ListObject products) {this.products products;}public void run() {while (true) {lock.lock(); // 加锁try {while (products.isEmpty()) { // 没有可消费的产品try {empty.await(); // 没有可用的产品了等待} catch (InterruptedException e) {e.printStackTrace();}}products.remove(0); // 消费一个full.signalAll(); // 唤醒所有生产者empty.signalAll(); // 唤醒所有消费者System.out.println(Eat one. Left : products.size());} finally {lock.unlock(); // 释放锁}}}}// 生产者private static class Producer implements Runnable {private final int max;private ListObject products;public Producer(int max, ListObject products) {this.max max;this.products products;}public void run() {while (true) {lock.lock(); // 加锁try {while (products.size() max) { // 已经达到了最大的产量try {full.await(); // 已达最大产量等待} catch (InterruptedException e) {e.printStackTrace();}}products.add(new Object()); // 生产一个full.signalAll(); empty.signalAll();System.out.println(Made one. Total : products.size());} finally {lock.unlock(); // 释放锁}}}}public static void main(String...args) {final int MAX_PRODUCTS 20;ListObject products new LinkedList();Executor executor Executors.newCachedThreadPool();executor.execute(new Producer(MAX_PRODUCTS, products));executor.execute(new Consumer(products));executor.execute(new Consumer(products));}第三自恶法基于 BlockingQueue private static class Consumer implements Runnable {private BlockingQueueObject products;public Consumer(BlockingQueueObject products) {this.products products;}Overridepublic void run() {while (true) {try {products.take(); // 取不到数据的时候自动阻塞System.out.println(Consumed one, Total products.size());} catch (InterruptedException e) {e.printStackTrace();}}}}// 生产者private static class Producer implements Runnable {private BlockingQueueObject products;public Producer(BlockingQueueObject products) {this.products products;}Overridepublic void run() {while (true) {try {products.put(new Object()); // 当达到了最大数量的时候会阻塞System.out.println(Produced one, Total products.size());} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String...args) {final int MAX_PRODUCTS 20;BlockingQueueObject products new LinkedBlockingDeque(MAX_PRODUCTS);Executor executor Executors.newCachedThreadPool();executor.execute(new Producer(products));executor.execute(new Consumer(products));executor.execute(new Consumer(products));}2、观察者模式 public class ConcreteSubject implements Subject {private ListObserver observers new LinkedList(); // 维护观察者列表Overridepublic void registerObserver(Observer o) { // 注册一个观察者observers.add(o);}Overridepublic void removeObserver(Observer o) { // 移除一个观察者int i observers.indexOf(o);if (i 0) {observers.remove(o);} }Overridepublic void notifyObservers() { // 通知所有观察者主题的更新for (Observer o : observers) {o.method();}}}public class ConcreteObserver implements Observer {private Subject subject; // 该观察者订阅的主题public ConcreteObserver(Subject subject) {this.subject subject;subject.registerObserver(this); // 将当前观察者添加到主题订阅列表中}// 当主题发生变化的时候主题会遍历观察者列表并通过调用该方法来通知观察者Overridepublic void method() {// ... }}另外 有什么技术问题欢迎加我交流 qilebeaf 本人10多年大厂软件开发经验精通AndroidJavaPython前端等开发空余时间承接软件开发设计、课程设计指导、解决疑难bug、AI大模型搭建AI绘图应用等。 欢迎砸单# Android 高级面试-2IPC 相关 1、IPC 问题Android 上的 IPC 跨进程通信时如何工作的 问题简述 IPC 问题进程间通信的机制 问题AIDL 机制 问题Bundle 机制 问题多进程场景遇见过么 IPC 就是指进程之间的通信机制在 Android 系统中启动 Activity/Service 等都涉及跨进程调用的过程。 Android 中有多种方式可以实现 IPC Bundle用于在四大组件之间传递信息优点是使用简单缺点是只能使用它支持的数据类型。Bundle 继承自 BaseBundle它通过内部维护的 ArrayMapString, Object 来存储数据。当我们使用 put() 和 get() 系列的方法的时候都会直接与其进行交互。ArrayMapString, Object 与 HashMap 类似也是用作键值对的映射但是它的实现方式与 SpareArray 类似是基于两个数组来实现的映射。目的也是为了提升 Map 的效率。它在查找某个哈希值的时候使用的是二分查找。 共享文件即两个进程通过读/写同一个文件来进行交换数据。由于 Android 系统是基于 Linux 的使得其并发读/写文件可以没有任何限制地进行甚至两个线程同时对同一个文件进行写操作都是被充许的。如果并发读/写我们读取出来的数据可能不是最新的。文件共享方式适合在对数据同步要求不高的情况的进程之间进行通信并且要妥善处理并发读/写的问题。 另外SharedPreferences 也是属于文件的一种但是系统对于它的读/写有一定的缓存策略即在内存中有一份 SP 文件的缓存因此在多进程模式下系统对它的读/写变得不可靠面对高并发的读/写访问有很大几率会丢失数据。不建议在进程间通信中使用 SP. Messenger 是一种轻量级的 IPC 方案它的底层实现是 AIDL可以在不同进程中传递 Message. 它一次只处理一个请求在服务端不需要考虑线程同步的问题服务端不存在并发执行的情形。在远程的服务中声明一个 Messenger使用一个 Handler 用来处理收到的消息然后再 onBind() 方法中返回 Messenger 的 binder. 当客户端与 Service 绑定的时候就可以使用返回的 Binder 创建 Messenger 并向该 Service 发送服务。 // 远程服务的代码private Messenger messenger new Messenger(new MessengerHandler(this));NullableOverridepublic IBinder onBind(Intent intent) {ToastUtils.makeToast(MessengerService bound!);return messenger.getBinder();}// 客户端 bind 服务的时候用到的 ServiceConnectionprivate ServiceConnection msgConn new ServiceConnection() {Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 这样就拿到了远程的 Messenger向它发送消息即可boundServiceMessenger new Messenger(service);}// ... ...}// 客户端发送消息的代码Message message Message.obtain(null, /*what*/ MessengerService.MSG_SAY_SOMETHING);message.replyTo receiveMessenger; // 客户端用来接收服务端消息的 MessengerBundle bundle new Bundle(); // 构建消息bundle.putString(MessengerService.MSG_EXTRA_COMMAND, 11111);message.setData(bundle);boundServiceMessenger.send(message); // 发送消息给服务端AIDLMessenger 是以串行的方式处理客户端发来的消息如果大量消息同时发送到服务端服务端只能一个一个处理所以大量并发请求就不适合用 Messenger 而且 Messenger 只适合传递消息不能跨进程调用服务端的方法。AIDL 可以解决并发和跨进程调用方法的问题。 AIDL 即 Android 接口定义语言。使用的时候只需要创建一个后缀名为 .aidl 的文件然后在编译期间编译器会使用 aidl.exe 自动生成 Java 类文件。 远程的服务只需要实现 Stub 类客户端需要在 bindService() 的时候传入一个 ServiceConnection并在连接的回调方法中将 Binder 转换成为本地的服务。然后就可以在本地调用远程服务中的方法了。 /* 注意这里使用了自定义的 Parcelable 对象Note 类但是 AIDL 不认识这个类所以我们要创建一个与 Note 类同名的 AIDL 文件Note.aidl. 并且类必须与 aidl 文件的包结构一致。*/// 远程服务的代码private Binder binder new INoteManager.Stub() {Overridepublic Note getNote(long id) {// ... ...}};// 绑定服务public IBinder onBind(Intent intent) {return binder;}// 客户端代码private INoteManager noteManager;private ServiceConnection connection new ServiceConnection() {Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 获取远程的服务转型然后就可以在本地使用了noteManager INoteManager.Stub.asInterface(service);}Overridepublic void onServiceDisconnected(ComponentName name) { }};// 服务端访问权限控制使用 Permission 验证在 manifest 中声明permission android:namecom.jc.ipc.ACCESS_BOOK_SERVICEandroid:protectionLevelnormal/uses-permission android:namecom.jc.ipc.ACCESS_BOOK_SERVICE/// 服务端 onBinder 方法中public IBinder onBind(Intent intent) {//Permission 权限验证int check checkCallingOrSelfPermission(com.jc.ipc.ACCESS_BOOK_SERVICE);if (check PackageManager.PERMISSION_DENIED) return null;return mBinder;}AIDL 支持的数据类型包括1).基本数据类型2).string 和 CharSequence3).List 中只支持 ArrayList并且其元素必须能够被 AIDL 支持4).Map 中只支持 HashMap并且其元素必须能够被 AIDL 支持5).所有实现了 Parcelable 接口的对象6).AIDL所有 AIDL 接口本身也可以在AIDL文件中使用。 ContentProvider主要用来对提供数据库方面的共享。缺点是主要提供数据源的 CURD 操作。 SocketSocket 主要用在网络方面的数据交换。在 Android 系统中启动的 Zygote 进程的时候会启动一个 ServerSocket. 当我们需要创建应用进程的时候会通过 Socket 与之进行通信这也是 Socket 的应用。 管道另外在使用 Looper 启动 MQ 的时候会在 Native 层启动一个 Looper. Native 层的与 Java 层的 Looper 进行通信的时候使用的是 epoll也就是管道通信机制。 问题为何需要进行 IPC多进程通信可能会出现什么问题 在 Android 系统中一个应用默认只有一个进程每个进程都有自己独立的资源和内存空间其它进程不能任意访问当前进程的内存和资源系统给每个进程分配的内存会有限制。如果一个进程占用内存超过了这个内存限制就会报 OOM 的问题很多涉及到大图片的频繁操作或者需要读取一大段数据在内存中使用时很容易报 OOM 的问题为了解决应用内存的问题Android 引入了多进程的概念它允许在同一个应用内为了分担主进程的压力将占用内存的某些页面单独开一个进程比如 Flash、视频播放页面频繁绘制的页面等。 实现的方式很简单就是在 Manifest 中注册 Activity 等的时候使用 process 属性指定一个进程即可。process 分私有进程和全局进程以 : 号开头的属于私有进程其他应用组件不可以和他跑在同一个进程中不以 : 号开头的属于全局进程其他应用可以通过 ShareUID 的方式和他跑在同一个进程中。此外还有一种特殊方法通过 JNI 在 native 层去 fork 一个新的进程。 但是多进程模式出现以下问题 静态成员和单例模式完全失效因为没有存储在同一个空间上线程同步机制完全失效因为线程处于不同的进程SharedPreferences 的可靠性下降因为系统对于它的读/写有一定的缓存策略即在内存中有一份 SP 文件的缓存Application 多次创建。 解决这些问题可以依靠 Android 中的进程通信机制即 IPC接上面的问题。 问题Binder 相关 为什么要设计 BinderBinder 模型高效的原因 Binder 是 Android 设计的一套进程间的通信机制。Linux 本身具有很多种跨进程通信方式比如管道Pipe、信号Signal和跟踪Trace、插口Socket、消息队列Message、共享内存Share Memory和信号量Semaphore。之所以设计出 Binder 是因为这几种通信机制在效率、稳定性和安全性上面无法满足 Android 系统的要求。 效率上 Socket 作为一款通用接口其传输效率低开销大主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式即数据先从发送方缓存区拷贝到内核开辟的缓存区中然后再从内核缓存区拷贝到接收方缓存区至少有两次拷贝过程。共享内存虽然无需拷贝但控制复杂难以使用。Binder 只需要一次数据拷贝性能上仅次于共享内存。 稳定性Binder 基于 C|S 架构客户端Client有什么需求就丢给服务端Server去完成架构清晰、职责明确又相互独立自然稳定性更好。共享内存虽然无需拷贝但是控制负责难以使用。从稳定性的角度讲Binder 机制是优于内存共享的。 安全性Binder 通过在内核层为客户端添加身份标志 UID|PID来作为身份校验的标志保障了通信的安全性。 传统 IPC 访问接入点是开放的无法建立私有通道。比如命名管道的名称SystemV 的键值Socket 的 ip 地址或文件名都是开放的只要知道这些接入点的程序都可以和对端建立连接不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。 在 Binder 模型中共有 4 个主要角色它们分别是Client、Server、Binder 驱动和 ServiceManager. Binder 的整体结构是基于 C|S 结构的以我们启动 Activity 的过程为例每个应用都会与 AMS 进行交互当它们拿到了 AMS 的 Binder 之后就像是拿到了网络接口一样可以进行访问。如果我们将 Binder 和网络的访问过程进行类比那么 Server 就是服务器Client 是客户终端ServiceManager 是域名服务器DNS驱动是路由器。 Client、Server 和 Service Manager 实现在用户空间中Binder 驱动程序实现在内核空间中Binder 驱动程序和 ServiceManager 在 Android 平台中已经实现开发者只需要在用户空间实现自己的 Client 和 ServerBinder 驱动程序提供设备文件 /dev/binder 与用户空间交互Client、Server 和 ServiceManager 通过 open 和 ioctl 文件操作函数与 Binder 驱动程序进行通信Client 和 Server 之间的进程间通信通过 Binder 驱动程序间接实现ServiceManager 是一个守护进程用来管理 Server并向 Client 提供查询 Server 接口的能力。 系统启动的 init 进程通过解析 init.rc 文件创建 ServiceManager. 此时会先打开 Binder 驱动注册 ServiceManager 成为上下文最后启动 Binder 循环。当使用到某个服务的时候比如 AMS 时会先根据它的字符串名称到缓冲当中去取拿不到的话就从远程获取。这里的 ServiceManager 也是一种服务。 客户端首先获取服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的“引用”该代理对象具有服务端的功能使其在客户端访问服务端的方法就像访问本地方法一样。客户端通过调用服务器代理对象的方式向服务器端发送请求。代理对象将用户请求通过 Binder 驱动发送到服务器进程。服务器进程处理用户请求并通过 Binder 驱动返回处理结果给客户端的服务器代理对象。 Binder 高效的原因当两个进程之间需要通信的时候Binder 驱动会在两个进程之间建立两个映射关系内核缓存区和内核中数据接收缓存区之间的映射关系以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。这样当把数据从 1 个用户空间拷贝到内核缓冲区的时候就相当于拷贝到了另一个用户空间中。这样只需要做一次拷贝省去了内核中暂存这个步骤提升了一倍的性能。实现内存映射靠的就是上面的 mmap() 函数。 2、序列化 问题序列化的作用以及 Android 两种序列化的区别 问题序列化Android 为什么引入 Parcelable 问题有没有尝试简化 Parcelable 的使用 Android 中主要有两种序列化的方式。 第一种是 Serializable. 它是 Java 提供的序列化方式让类实现 Serializable 接口就可以序列化地使用了。这种序列化方式的缺点是它序列化的效率比较低更加适用于网络和磁盘中信息的序列化不太适用于 Android 这种内存有限的应用场景。优点是使用方便只需要实现一个接口就行了。 这种序列化的类可以使用 ObjectOutputStream/ObjectInputStream 进行读写。这种序列化的对象可以提供一个名为 serialVersionUID 的字段用来标志类的版本号比如当类的解构发生变化的时候将无法进行反序列化。此外 静态成员变量不属于对象不会参与序列化过程用 transient 关键字标记的成员变量不会参与序列化过程。 第二种方式是 Parcelable. 它是 Android 提供的新的序列化方式主要用来进行内存中的序列化无法进行网络和磁盘的序列化。它的缺点是使用起来比较繁琐需要实现两个方法和一个静态的内部类。 Serializable 会使用反射序列化和反序列化过程需要大量 I/O 操作在序列化的时候会产生大量的临时变量从而引起频繁的 GC。Parcelable 自已实现封送和解封marshalled unmarshalled操作不需要用反射数据也存放在 Native 内存中效率要快很多。 我自己尝试过一些简化 Parcelable 使用的方案通常有两种解决方案第一种方式是使用 IDE 的插件来辅助生成 Parcelable 相关的代码插件地址第二种方案是使用反射根据字段的类型调用 wirte() 和 read() 方法性能比较低第三种方案是基于注解处理在编译期间生成代理类然后在需要覆写的方法中调用生成的代理类的方法即可。 3、进程与线程 问题进程与线程之间有什么区别与联系 问题为什么要有线程而不是仅仅用进程 一个进程就是一个执行单元在 PC 和移动设备上指一个程序或应用。在 Android 中一个应用默认只有一个进程每个进程都有自己独立的资源和内存空间其它进程不能任意访问当前进程的内存和资源系统给每个进程分配的内存会有限制。实现的方式很简单就是在 Manifest 中注册 Activity 等的时候使用 process 属性指定一个进程即可。process 分私有进程和全局进程以 : 号开头的属于私有进程其他应用组件不可以和他跑在同一个进程中不以 : 号开头的属于全局进程其他应用可以通过 ShareUID 的方式和他跑在同一个进程中 Android 系统启动的时候会先启动 Zygote 进程当我们需要创建应用程序进程的时候的会通过 Socket 与之通信Zygote 通过 fork 自身来创建我们的应用程序的进程。 不应只是简单地讲述两者之间的区别同时涉及系统进程的创建应用进程的创建以及如何在程序中使用多进程等。 线程是 CPU 调度的最小单元一个进程可包含多个线程。Java 线程的实现是基于一对一的线程模型即通过语言级别层面程序去间接调用系统的内核线程。内核线程由操作系统内核支持由操作系统内核来完成线程切换内核通过操作调度器进而对线程执行调度并将线程的任务映射到各个处理器上。由于我们编写的多线程程序属于语言层面的程序一般不会直接去调用内核线程取而代之的是一种轻量级的进程(Light Weight Process)也是通常意义上的线程。由于每个轻量级进程都会映射到一个内核线程因此我们可以通过轻量级进程调用内核线程进而由操作系统内核将任务映射到各个处理器。这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。 问题Android 中进程内存的分配能不能自己分配定额内存 问题进程和 Application 的生命周期 问题进程调度 问题Android 进程分类 Android 应用的内存管理由 AMS 集中管理所有进程的内存分配系统回收进程的时候优先级如下所示。Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级 Empty process (空进程)Background process (后台进程)Service process (服务进程)Visible process (可见进程)Foreground process (前台进程) 系统需要进行内存回收时最先回收空进程然后是后台进程以此类推最后才会回收前台进程。 附录 了解 Android 系统启动过程和虚拟机内存模型 JMM请参考我的文章Android 系统源码-1Android 系统启动流程源码分析 和 JVM扫盲-3虚拟机内存模型与高效并发了解 Binder 相关的知识请参考我的文章《Android 系统源码-2Binder 通信机制》 另外 有什么技术问题欢迎加我交流 qilebeaf 本人10多年大厂软件开发经验精通AndroidJavaPython前端等开发空余时间承接软件开发设计、课程设计指导、解决疑难bug、AI大模型搭建AI绘图应用等。 欢迎砸单
文章转载自:
http://www.morning.mtbth.cn.gov.cn.mtbth.cn
http://www.morning.wfykn.cn.gov.cn.wfykn.cn
http://www.morning.dcdhj.cn.gov.cn.dcdhj.cn
http://www.morning.cfmrb.cn.gov.cn.cfmrb.cn
http://www.morning.wkgyz.cn.gov.cn.wkgyz.cn
http://www.morning.bpmtj.cn.gov.cn.bpmtj.cn
http://www.morning.xbtlt.cn.gov.cn.xbtlt.cn
http://www.morning.mtzyr.cn.gov.cn.mtzyr.cn
http://www.morning.hfxks.cn.gov.cn.hfxks.cn
http://www.morning.bdypl.cn.gov.cn.bdypl.cn
http://www.morning.hxrg.cn.gov.cn.hxrg.cn
http://www.morning.plhyc.cn.gov.cn.plhyc.cn
http://www.morning.lnbyk.cn.gov.cn.lnbyk.cn
http://www.morning.llsrg.cn.gov.cn.llsrg.cn
http://www.morning.rqlbp.cn.gov.cn.rqlbp.cn
http://www.morning.cgdyx.cn.gov.cn.cgdyx.cn
http://www.morning.jmmz.cn.gov.cn.jmmz.cn
http://www.morning.touziyou.cn.gov.cn.touziyou.cn
http://www.morning.wcjk.cn.gov.cn.wcjk.cn
http://www.morning.nlglm.cn.gov.cn.nlglm.cn
http://www.morning.nlrxh.cn.gov.cn.nlrxh.cn
http://www.morning.bnfsw.cn.gov.cn.bnfsw.cn
http://www.morning.zbqry.cn.gov.cn.zbqry.cn
http://www.morning.byywt.cn.gov.cn.byywt.cn
http://www.morning.cnqwn.cn.gov.cn.cnqwn.cn
http://www.morning.ymdhq.cn.gov.cn.ymdhq.cn
http://www.morning.rtzd.cn.gov.cn.rtzd.cn
http://www.morning.hlfnh.cn.gov.cn.hlfnh.cn
http://www.morning.tgydf.cn.gov.cn.tgydf.cn
http://www.morning.rxfgh.cn.gov.cn.rxfgh.cn
http://www.morning.wlqll.cn.gov.cn.wlqll.cn
http://www.morning.dwtdn.cn.gov.cn.dwtdn.cn
http://www.morning.gwdkg.cn.gov.cn.gwdkg.cn
http://www.morning.yxbdl.cn.gov.cn.yxbdl.cn
http://www.morning.mtsgx.cn.gov.cn.mtsgx.cn
http://www.morning.qbfs.cn.gov.cn.qbfs.cn
http://www.morning.rfycj.cn.gov.cn.rfycj.cn
http://www.morning.ktmbp.cn.gov.cn.ktmbp.cn
http://www.morning.xhqr.cn.gov.cn.xhqr.cn
http://www.morning.jtkfm.cn.gov.cn.jtkfm.cn
http://www.morning.mlycx.cn.gov.cn.mlycx.cn
http://www.morning.rltsx.cn.gov.cn.rltsx.cn
http://www.morning.jmwrj.cn.gov.cn.jmwrj.cn
http://www.morning.bswhr.cn.gov.cn.bswhr.cn
http://www.morning.gagapp.cn.gov.cn.gagapp.cn
http://www.morning.dpfr.cn.gov.cn.dpfr.cn
http://www.morning.gqnll.cn.gov.cn.gqnll.cn
http://www.morning.gxeqedd.cn.gov.cn.gxeqedd.cn
http://www.morning.qrlkt.cn.gov.cn.qrlkt.cn
http://www.morning.jyyw.cn.gov.cn.jyyw.cn
http://www.morning.jzccn.cn.gov.cn.jzccn.cn
http://www.morning.jtwck.cn.gov.cn.jtwck.cn
http://www.morning.hpmzs.cn.gov.cn.hpmzs.cn
http://www.morning.dpsyr.cn.gov.cn.dpsyr.cn
http://www.morning.mfltz.cn.gov.cn.mfltz.cn
http://www.morning.sgmgz.cn.gov.cn.sgmgz.cn
http://www.morning.mrfbp.cn.gov.cn.mrfbp.cn
http://www.morning.xxsrm.cn.gov.cn.xxsrm.cn
http://www.morning.rrwft.cn.gov.cn.rrwft.cn
http://www.morning.mnnxt.cn.gov.cn.mnnxt.cn
http://www.morning.nyfyq.cn.gov.cn.nyfyq.cn
http://www.morning.xcfmh.cn.gov.cn.xcfmh.cn
http://www.morning.jzykq.cn.gov.cn.jzykq.cn
http://www.morning.qqbw.cn.gov.cn.qqbw.cn
http://www.morning.znkls.cn.gov.cn.znkls.cn
http://www.morning.mtzyr.cn.gov.cn.mtzyr.cn
http://www.morning.lwrcg.cn.gov.cn.lwrcg.cn
http://www.morning.ktsth.cn.gov.cn.ktsth.cn
http://www.morning.hffjj.cn.gov.cn.hffjj.cn
http://www.morning.bkfdf.cn.gov.cn.bkfdf.cn
http://www.morning.wjwfj.cn.gov.cn.wjwfj.cn
http://www.morning.kltmt.cn.gov.cn.kltmt.cn
http://www.morning.lkthj.cn.gov.cn.lkthj.cn
http://www.morning.zbtfz.cn.gov.cn.zbtfz.cn
http://www.morning.kdnrc.cn.gov.cn.kdnrc.cn
http://www.morning.wskn.cn.gov.cn.wskn.cn
http://www.morning.tqjwx.cn.gov.cn.tqjwx.cn
http://www.morning.pngfx.cn.gov.cn.pngfx.cn
http://www.morning.pkrtz.cn.gov.cn.pkrtz.cn
http://www.morning.zpnfc.cn.gov.cn.zpnfc.cn
http://www.tj-hxxt.cn/news/263065.html

相关文章:

  • 门户网站推广优势贵阳市城乡建设局网站
  • 昆明房产网站建设渠道招商
  • 镇江网站制作网站城市切换如何做
  • 一个网站备案两个域名吗wordpress 3.9.1 漏洞
  • 前期的网站建设的难度王业美
  • 做网站去哪找wordpress语言的设置
  • 临沂网站制作公司六安城市网招聘
  • 淮北论坛招聘最新消息wordpress+极致优化
  • 申请网站域名多少钱wordpress mdtf
  • 在linux系统上用什么做网站阿里云服务器在哪里
  • 网站建立数据库wordpress后台怎么登陆
  • 建设旅游网站需要多少钱数码产品商务网站建设
  • 茂名专业网站建设公司找人做网站需要交接什么
  • 大学生做外包项目的网站宁波网络营销推广
  • 绵阳 网站建设整站seo
  • 网站建设一条龙怎么样内蒙古做网站的公司有哪些
  • 在哪几个网站里可以做淘客360怎么做网站搜索
  • 可以做动画的网站都有哪些推广公司游戏
  • 福建住房与城乡建设厅网站wordpress 音乐页面
  • 免费公司网站建站wordpress娱乐插件
  • 中国建设银行网站公积金查询wordpress自动清缓存
  • 网站建设的研发项目网站推广策划的思路
  • 建设进出口外贸网站一级a做爰片了网站
  • pc网站优化排名营销做得好的品牌
  • 网站维护的内容和步骤江苏手机网站建设
  • 网站关键词优化排名要怎么做网站建设 申请报告
  • 我做网站了重庆推广一个网站
  • 新手建站教程报价单企业网站seo模板
  • 蓬莱做网站价格wordpress 插件 无法创建目录
  • 848给我做一下88网站网站建设万首先金手指13