# interview **Repository Path**: xihe/interview ## Basic Information - **Project Name**: interview - **Description**: interview - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2019-08-02 - **Last Updated**: 2020-12-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1. HTTPS ![输入图片说明](https://images.gitee.com/uploads/images/2019/0802/101031_489314a6_560775.png "屏幕截图.png") # 2. Android应用程序启动过程: 一. :Launcher通过Binder进程间通信机制通知ActivityManagerService,它要启动一个Activity; 二.:ActivityManagerService通过Binder进程间通信机制通知Launcher进入Paused状态; 三.:Launcher通过Binder进程间通信机制通知ActivityManagerService,它已经准备就绪进入Paused状态,于是ActivityManagerService就创建一个新的进程,用来启动一个ActivityThread实例,即将要启动的Activity就是在这个ActivityThread实例中运行; 四. :ActivityThread通过Binder进程间通信机制将一个ApplicationThread类型的Binder对象传递给ActivityManagerService,以便以后ActivityManagerService能够通过这个Binder对象和它进行通信; 五 :ActivityManagerService通过Binder进程间通信机制通知ActivityThread,现在一切准备就绪,它可以真正执行Activity的启动操作了。 # 3.activity的startActivity和context的startActivity区别 (1)从Activity中启动新的Activity时可以直接mContext.startActivity(intent)就好, (2)如果从其他Context中启动Activity则必须给intent设置Flag: # 3.简述下Acitivty任务栈和使用方法 任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名. 1、standard:默认模式:每次启动都会创建一个新的activity对象,放到目标任务栈中 2、singleTop:判断当前的任务栈顶是否存在相同的activity对象,如果存在,则直接使用,如果不存在,那么创建新的activity对象放入栈中 3、singleTask:在任务栈中会判断是否存在相同的activity,如果存在,那么会清除该activity之上的其他activity对象显示,如果不存在,则会创建一个新的activity放入栈顶 4、singleIntance:会在一个新的任务栈中创建activity,并且该任务栈种只允许存在一个activity实例,其他调用该activity的组件会直接使用该任务栈种的activity对象 方法一: 使用android:launchMode="standard|singleInstance|single Task|singleTop"来控制Acivity任务栈。 方法二: Intent Flags: # 4. Context 相关问题 Activity和Service以及Application的Context是不一样的,Activity继承自ContextThemeWraper.其他的继承自ContextWrapper. 每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象 getApplication()用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法.getApplicationContext()比getApplication()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。 Context的数量等于Activity的个数 + Service的个数 + 1,这个1为Application. 那Broadcast Receiver,Content Provider呢?Broadcast Receiver,Content Provider并不是Context的子类,他们所持有的Context都是其他地方传过去的,所以并不计入Context总数。 # 5. HandlerThread 1、HandlerThread作用 当系统有多个耗时任务需要执行时,每个任务都会开启一个新线程去执行耗时任务,这样会导致系统多次创建和销毁线程,从而影响性能。为了解决这一问题,Google提供了HandlerThread,HandlerThread是在线程中创建一个Looper循环器,让Looper轮询消息队列,当有耗时任务进入队列时,则不需要开启新线程,在原有的线程中执行耗时任务即可,否则线程阻塞。 2、HanlderThread的优缺点 HandlerThread本质上是一个线程类,它继承了Thread; HandlerThread有自己的内部Looper对象,可以进行looper循环; 通过获取HandlerThread的looper对象传递给Handler对象,可以在handleMessage()方法中执行异步任务。 创建HandlerThread后必须先调用HandlerThread.start()方法,Thread会先调用run方法,创建Looper对象。 HandlerThread优点是异步不会堵塞,减少对性能的消耗 HandlerThread缺点是不能同时继续进行多任务处理,需要等待进行处理,处理效率较低 HandlerThread与线程池不同,HandlerThread是一个串行队列,背后只有一个线程。 # 6. IntentService 它本质是一种特殊的Service,继承自Service并且本身就是一个抽象类 它可以用于在后台执行耗时的异步任务,当任务完成后会自动停止 它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务 它内部通过HandlerThread和Handler实现异步操作 创建IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步方法,可以执行耗时操作 即使我们多次启动IntentService,但IntentService的实例只有一个,这跟传统的Service是一样的,最终IntentService会去调用onHandleIntent执行异步任务。 当任务完成后,IntentService会自动停止,而不需要手动调用stopSelf()。另外,可以多次启动IntentService,每个耗时操作都会以工作队列的方式在IntentService中onHandlerIntent()回调方法中执行,并且每次只会执行一个工作线程。 # 7. AsyncTask 3、AsyncTask引起的内存泄漏 原因:非静态内部类持有外部类的匿名引用,导致Activity无法释放 解决: AsyncTask内部持有外部Activity的弱引用 AsyncTask改为静态内部类 Activity的onDestory()中调用AsyncTask.cancel() 4.结果丢失 屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。 5、AsyncTask并行or串行 AsyncTask在Android 2.3之前默认采用并行执行任务,AsyncTask在Android 2.3之后默认采用串行执行任务 如果需要在Android 2.3之后采用并行执行任务,可以调用AsyncTask的executeOnExecutor(); 6.AsyncTask内部的线程池 private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; sDefaultExecutor是SerialExecutor的一个实例,而且它是个静态变量。也就是说,一个进程里面所有AsyncTask对象都共享同一个SerialExecutor对象 # 8. Service两种启动方式 1、startService(Intent)通过这种方式开启的服务,执行的生命周期方法: 第一次调用startService的时候:onCreate→onStartCommand 再次调用startService的时候:只执行onStartCommand 2、想停止用startService开启的服务要使用stopService(Intent),stopService执行之后,Service会走onDestroy()方法,执行之后Service销毁,再次调用stopService没有反应 3、如果在Activity中通过startService方法开启一个服务,当Activity退出的时候Service不会销毁,依然在后台运行,只有手动调用stopService或者在应用管理器中关闭Service,服务才会销毁 1、开启服务时生命周期比较: bindService onCreate→onBind(只会执行一次) startService onCreate→onStartCommand(调用一次startService执行一次) 2、startService开启的服务跟Activity没有关系,bindService开启的服务,跟Activity之间不求同生,但求同死,Activity退出的时候必须通过unbindService关闭服务 3、startService结束的时候stopService可以调用多次,只有第一次调用的时候有效,bindService结束的时候unbindService只能调用一次,调用多次应用会抛异常 4、bindService的时候传入的第二个参数是ServiceConnection,只有当onBind方法返回不为空的时候才会调用onServiceConnected # 9. 加载图片 对于图片显示:根据需要显示图片控件的大小对图片进行压缩显示。 如果图片数量非常多:则会使用LruCache等缓存机制,将所有图片占据的内容维持在一个范围内。 局部加载: BitmapRegionDecoder # 10. hashMap 1) HashMap可以接受null键值和值,而HashTable则不能,HashMap是非synchronized的;存储的是键值对。 2) HashMap是基于hashing原理,使用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获取对象,当我们给put方法传递键和值时,我们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来存储键对象和值对象,作为Map.Entry. 3) 如果两个对象hashCode相同: 存储时:他们会找到相同的bucket位置,发生碰撞,因为HashMap使用链表存储对象(每个Map.Entry都有一个next指针),这个Entry会存储在链表中。 获取时:会用hashCode找到bucket位置,然后调用key.equals()方法找到链表中正确的节点.最终找到要找的值对象. 减少碰撞:使用final修饰的对象、或不可变的对象作为键,使用(Integer、String)(是不可变、final的,而且已经重写了equals和hashCode方法)这样的wrapper类作为键是非常好的,(我们可以使用自定义的对象作为键吗?答:当然可以,只要它遵守了equals和hashCode方法定义规则,并且当对象插入到Map中之后将不会再改变。) 4) HashMap负载因子默认是0.75,可设置,当map填满了75%的bucket时候,将会创建原来HashMap大小两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中,这个过程叫做rehashing,因为它调用hash方法找到新的bucket位置。 5) 重新调整map大小可能会发生竞争问题:如果两个线程都发现HashMap需要调整大小了,它们都会尝试进行调整,在调整中,存储在链表中的元素的次序会反过来,因为移动bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历,如果条件竞争发生了,就死循环了 # 11. 四种线程池 1、由Executors的newFixedThreadPool方法创建。它是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。FixedThreadPool只有核心线程,且该核心线程都不会被回收,这意味着它可以更快地响应外界的请求。 2、由Executors的newCachedThreadPool方法创建,不存在核心线程,只存在数量不定的非核心线程,而且其数量最大值为Integer.MAX_VALUE。当线程池中的线程都处于活动时(全满),线程池会创建新的线程来处理新的任务,否则就会利用新的线程来处理新的任务,线程池中的空闲线程都有超时机制,默认超时时长为60s,超过60s的空闲线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空的集合,这将导致任何任务都会被执行,因为在这种场景下SynchronousQueue是不能插入任务的,SynchronousQueue是一个特殊的队列,在很多情况下可以理解为一个无法储存元素的队列。从CachedThreadPool的特性看,这类线程比较适合执行大量耗时较小的任务。当整个线程池都处于闲置状态时,线程池中的线程都会因为超时而被停止回收,几乎是不占任何系统资源。 3、通过Executors的newScheduledThreadPool方式创建,核心线程数量是固定的,而非核心线程是没有限制的,并且当非核心线程闲置时它会被立即回收,ScheduledThreadPool这类线程池主要用于执行定时任务和具有固定时期的重复任务。 4、通过Executors的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有外界任务一个线程中,这使得这些任务之间不需要处理线程同步的问题 # 12. recyclerview。listview 1.局部刷新:recyclerview有notifyItemCHanged等局部刷新方法 2.动画效果: 3. item点击事件: listview有onItemClickListener 4. 嵌套滚动:在事件分发机制中,Touch事件在进行分发的时候,由父View向子View传递,一旦子View消费这个事件的话,那么接下来的事件分发的时候,父View将不接受,由子View进行处理;但是与Android的事件分发机制不同,嵌套滚动机制(Nested Scrolling)可以弥补这个不足,能让子View与父View同时处理这个Touch事件,主要实现在于NestedScrollingChild与NestedScrollingParent这两个接口;而在RecyclerView中,实现的是NestedScrollingChild,所以能实现嵌套滚动机制; ListView就没有实现嵌套滚动机制 # 13.LruCache LruCache中维护了一个集合LinkedHashMap,该LinkedHashMap是以访问顺序排序的。当调用put()方法时,就会在结合中添加元素,并调用trimToSize()判断缓存是否已满,如果满了就用LinkedHashMap的迭代器删除队尾元素,即近期最少访问的元素。当调用get()方法访问缓存对象时,就会调用LinkedHashMap的get()方法获得对应集合元素,同时会更新该元素到队头 # 14. commit 和apply 相同点: 二者都是提交preference修改数据 二者都是原子过程。 区别: apply没有返回值而commit返回boolean表明修改是否提交成功 apply是将修改数据原子提交到内存,而后异步真正提交到硬件磁盘;而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。 apply方法不会提示任何失败的提示。 # 15. Bundle机制 ```java 1:待传递数据的创建: ....Bundle bundle = new Bundle();创建Bundle对象 ....bundle.putSerializable("data",((Serializable)data)); ....Intent intent = new Intent(context,MyService.class);//创建intent ....intent.putExtras(bundle);//把bundle存入Intent的mExtras成员变量中 2:进程A启动服务 //创建Parcel,并把Intent中的数据打包到Parcel中,然后调用transact传递Parcel对象  context.startService(intent); ....-service.writeToParcel(data,0);//service是Intent对象,data是Parcel ........-out.writeBundle(mExtras);//out是Parcel,mExtras是Intent中的Bundle ............-val.writeToParcel(this,0);//val是Bundle,this是Parcel ................-super.writeToParcelInner(parcel,flags);//super是BaseBundle ....................-parcel.writeArrayMapInternal(mMap);//把mMap中的数据按顺序写入parcel中 ....-mRemote.transact(START_SERVICE_TRANSACTION,data,reply,0); 3:ActivityManager,onTransact方法中处理进程A的请求,从Parcel对象创建Intent onTransact-case START_SERVICE_TRANSACTION ....-Intent service = Intent.CREATOR.createFromParcel(data); ........-readFromParcel(in);//in是Parcel ............-mExtras= in.readBundle();//从Parcel中获取Bundle,存入Intent ................-Bundle bundle=new Bundle(this,length);//this是Parcel,创建Bundle ....................-readFromParcelInner(parcelledData,length); ........................-Parcel p=Parcel.obtain();//创建一个Parcel ........................-p.appendFrom(parcel,offset,length);//从传递过来的parcel中获取数据 ........................-mParcelledData=p;//在Bundle中保存这个parcel 4:在进程B中获取传递过来的Intent中的Bundle数据 intent.getSerializableExtra("data") ....-mExtras.getSerializable(name);//mExtras是Bundle ........-unparcel();//从mParcelledData这个Parcel中解包,把数据取出存入mMap中 ............-mParcelledData.readArrayMapInternal(mMap,N,mClassLoader); ........-Object o = mMap.get(key);//从mMap中获取数据 ``` 16. app启动流程: ![输入图片说明](https://images.gitee.com/uploads/images/2019/0802/101904_267c9668_560775.png "WX20190802-101807.png") # 17。 dalvik art 虚拟机 Art虚拟机 即Android Runtime,Android 4.4发布了一个ART运行时,准备用来替换掉之前一直使用的Dalvik虚拟机。 ART 的机制与 Dalvik 不同。在Dalvik下,应用每次运行的时候,字节码都需要通过即时编译器(just in time ,JIT)转换为机器码,这会拖慢应用的运行效率,而在ART 环境中,应用在第一次安装的时候,字节码就会预先编译成机器码,使其成为真正的本地应用。这个过程叫做预编译(AOT,Ahead-Of-Time)。这样的话,应用的启动(首次)和执行都会变得更加快速。 Dalvik与Art的区别: Dalvik每次都要编译再运行,Art只会首次启动编译 Art占用空间比Dalvik大(原生代码占用的存储空间更大),就是用“空间换时间” Art减少编译,减少了CPU使用频率,使用明显改善电池续航 Art应用启动更快、运行更快、体验更流畅、触感反馈更及时 art缺点: 1. 更大的存储空间占用,可能会增加10%-20% 2. 更长的应用安装时间 # 18. android classloader 特点 从源码中我们也可以看出,loadClass方法在加载一个类的实例的时候, 会先查询当前ClassLoader实例是否加载过此类,有就返回; 如果没有。查询Parent是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类; 如果继承路线上的ClassLoader都没有加载,才由Child执行类的加载工作; 这样做有个明显的特点,如果一个类被位于树根的ClassLoader加载过,那么在以后整个系统的生命周期内,这个类永远不会被重新加载。 作用 首先是共享功能,一些Framework层级的类一旦被顶层的ClassLoader加载过就缓存在内存里面,以后任何地方用到都不需要重新加载。 除此之外还有隔离功能,不同继承路线上的ClassLoader加载的类肯定不是同一个类,这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。这也好理解,一些系统层级的类会在系统初始化的时候被加载,比如java.lang.String,如果在一个应用里面能够简单地用自定义的String类把这个系统的String类给替换掉,那将会有严重的安全问题。 同一个Class = 相同的 ClassName + PackageName + ClassLoade DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk; PathClassLoader只能加载系统中已经安装过的apk; # 19 . quicksort ```java public class Solution { public Random rand; public void sortIntegers2(int[] A) { rand = new Random(); // write your code here quickSort(A, 0, A.length - 1); } public void quickSort(int[] A, int start, int end) { if (start >= end) { return; } int index = rand.nextInt(end - start + 1) + start; int pivot = A[index]; int left = start; int right = end; while (left <= right) { while (left <= right && A[left] < pivot) { left ++; } while (left <= right && A[right] > pivot) { right --; } if (left <= right) { int temp = A[left]; A[left] = A[right]; A[right] = temp; left ++; right --; } } // A[start... right] quickSort(A, start, right); // A[left ... end] quickSort(A, left, end); } } ``` 20. 二叉树的层次遍历 ```java public class Solution { public List> levelOrder(TreeNode root) { List result = new ArrayList(); if (root == null) { return result; } Queue queue = new LinkedList(); queue.offer(root); while (!queue.isEmpty()) { ArrayList level = new ArrayList(); int size = queue.size(); for (int i = 0; i < size; i++) { TreeNode head = queue.poll(); level.add(head.val); if (head.left != null) { queue.offer(head.left); } if (head.right != null) { queue.offer(head.right); } } result.add(level); } return result; } } ``` # 21. jvm内存区域划分 ![输入图片说明](https://images.gitee.com/uploads/images/2019/0802/102259_4643bf63_560775.png "屏幕截图.png") ## 21.1 堆内存 ![输入图片说明](https://images.gitee.com/uploads/images/2019/0802/102335_7278311a_560775.png "屏幕截图.png") GC(垃圾回收器)对年轻代中的对象进行回收被称为Minor GC,用通俗一点的话说年轻代就是用来存放年轻的对象,年轻对象是什么意思呢?年轻对象可以简单的理解为没有经历过多次垃圾回收的对象,如果一个对象经历过了一定次数的Minor GC,JVM一般就会将这个对象放入到年老代,而JVM对年老代的对象的回收则称为Major GC。 如上图所示,年轻代中还可以细分为三个部分,我们需要重点关注这几点: 大部分对象刚创建的时候,JVM会将其分布到Eden区域。 当Eden区域中的对象达到一定的数目的时候,就会进行Minor GC,经历这次垃圾回收后所有存活的对象都会进入两个Suvivor Place中的一个。 同一时刻两个Suvivor Place,即s0和s1中总有一个总是空的。 年轻代中的对象经历过了多次的垃圾回收就会转移到年老代中。 当申请不到空间时会抛出 OutOfMemoryError。下面我们简单的模拟一个堆内存溢出的情况: ## 21.2 1.方法区存储类的信息、常量、静态变量等。GCLib、OSGi等技术会创建很多类,为了防止OOM,这些技术需要提供类卸载的功能。(OSGi破坏了双亲委派模型,因为用户追求程序的动态性,OSGi成了实际上Java模块化的标准,把类加载器从双亲委派模型形成的树状结构发展成了网状结构) 2.方法区一般不GC,回收目标主要针对常量池的回收和对类型的卸载。 # 22. Glide缓存大小 AppGlideModule # 23. 一、HahMap存储对象的过程如下 1、对HahMap的Key调用hashCode()方法,返回int值,即对应的hashCode; 2、把此hashCode作为哈希表的索引,查找哈希表的相应位置,若当前位置内容为NULL,则把hashMap的Key、Value包装成Entry数组,放入当前位置; 3、若当前位置内容不为空,则继续查找当前索引处存放的链表,利用equals方法,找到Key相同的Entry数组,则用当前Value去替换旧的Value; 4、若未找到与当前Key值相同的对象,则把当前位置的链表后移(Entry数组持有一个指向下一个元素的引用),把新的Entry数组放到链表表头; 二、HashSet存储对象的过程 往HashSet添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 , 然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。 情况1: 如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。 情况2: 如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次 ,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行添加。 # 24. reverse linke list ```java public class Solution { /** * @param head: The head of linked list. * @return: The new head of reversed linked list. */ public ListNode reverse(ListNode head) { //prev表示前继节点 ListNode prev = null; while (head != null) { //temp记录下一个节点,head是当前节点 ListNode temp = head.next; head.next = prev; prev = head; head = temp; } return prev; } } ``` https://blog.csdn.net/Young_penis/article/details/82349370 https://blog.csdn.net/qq_37050329/article/details/84473077