安卓高级面试知识整理

                                                安卓高级面试知识整理

Android 四大组件:这是一份全面 & 详细的Activity学习指南

Android语言篇 (附答案)

匹配规则

手把手带你清晰梳理自定义View的工作全流程!

Carson带你学Android:深入解析自定义View工作流程_Carson带你学Android的博客-CSDN博客_安卓自定义view

1.activity启动流程

Activity启动过程简要介绍

    无论是通过点击应用程序图标来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都要借助于应用程序框架层的ActivityManagerService服务进程。在前面一篇文章Android系统在新进程中启动自定义服务过程(startService)的原理分析中,我们已经看到,Service也是由ActivityManagerService进程来启动的。在Android应用程序框架层中,ActivityManagerService是一个非常重要的接口,它不但负责启动Activity和Service,还负责管理Activity和Service。

 Android应用程序框架层中的ActivityManagerService启动Activity的过程大致如下图所示:

     在这个图中,ActivityManagerService和ActivityStack位于同一个进程中,而ApplicationThread和ActivityThread位于另一个进程中。其中,ActivityManagerService是负责管理Activity的生命周期的,ActivityManagerService还借助ActivityStack是来把所有的Activity按照后进先出的顺序放在一个堆栈中;

    对于每一个应用程序来说,都有一个ActivityThread来表示应用程序的主进程,而每一个ActivityThread都包含有一个ApplicationThread实例,它是一个Binder对象,负责和其它进程进行通信。

下面简要介绍一下启动的过程:

当请求启动Activity时:

  1. Launcher进程通过Binder驱动向ActivityManagerService类发起startActivity请求;
  2. ActivityManagerService类接收到请求后,向ActivityStack类发送启动Activity的请求;
  3. ActivityStack类记录需启动的Activity的信息 & 调整Activity栈 将其置于栈顶、通过 Binder 驱动 将 Activity 的启动信息传递到ApplicationThread线程中(即Binder线程)
  4. ApplicationThread线程通过HandlerActivity的启动信息发送到主线程ActivityThread
  5. 主线程ActivityThread类接收到该信息 & 请求后,通过ClassLoader机制加载相应的Activity类,最终调用ActivityonCreate(),最后 启动完毕

2.内存管理,进程管理

进程管理:

app是运行在虚拟机上,Android为每一个app都单独分配了一个虚拟机,也就是说每个app都有自己的进程,每个进程都有自己的内存空间,这样做的好处就是当我们当前的app出现问题的时候,系统仅仅杀死当前进程,不会导致其他的app受到牵连,回收之后释放出内存给其他app使用。

Android开发中何时使用多进程?使用多进程的好处是什么?
    要想知道如何使用多进程,先要知道Android里的多进程概念。一般情况下,一个应用程序就是一个进程,这个进程名称就是应用程序包名。我们知道进程是系统分配资源和调度的基本单位,所以每个进程都有自己独立的资源和内存空间,别的进程是不能任意访问其他进程的内存和资源的。
如何让自己的应用拥有多个进程?
    很简单,我们的四大组件在AndroidManifest文件中注册的时候,有个属性是android:process,1.这里可以指定组件的所处的进程。默认就是应用的主进程。指定为别的进程之后,系统在启动这个组件的时候,就先创建(如果还没创建的话)这个进程,然后再创建该组件。你可以重载Application类的onCreate方法,打印出它的进程名称,就可以清楚的看见了。再设置android:process属性时候,有个地方需要注意:如果是android:process=”:deamon”,以:开头的名字,则表示这是一个应用程序的私有进程,否则它是一个全局进程。私有进程的进程名称是会在冒号前自动加上包名,而全局进程则不会。一般我们都是有私有进程,很少使用全局进程。

进程从高到底分为5种:

前台进程
正在与用户交互的进程,通俗来讲就是你当前使用app的进程

可见进程
可以被用户看到,但是没有和用户交互,例如一个activity以对话框的形式覆盖在当前activity上面,当前activity可以被用户看到,但是不和用户交互

服务进程
这个相信大家都熟悉,也就是我们常说的service,能够运行在后台,常见的有音乐类的app

后台进程
注意,这个后台进程不要和服务进程搞混了,它的意思是说当前app在后台运行,例如我启动了app,然后点击home返回到桌面,那么这个app就会被切回到后台进程

空进程
空进程指的是在这些进程内部,没有任何东西在运行。保留这种进程的的唯一目的是用作缓存,以缩短该应用下次在其中运行组件所需的启动时间

当我们内存不足资源紧缺的时候,会从低到高去分别释放进程,从而使app有足够的内存运行,Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM,不同的设备会有不同的内存空间

内存管理:
1.内存管理机制概述:
(1)分配机制:操作系统会为每个进程分配合理的大小内存
(2)回收机制:当内存不足时,需要合理回收内存;
2.Android内存管理:
(1)分配机制:弹性分配,刚开始会为APP分配小额内存,根据每个APP的物理内存大小分配,然后在运行时,弹性的为其分配大小;
(2)回收机制:五大分级,前台->可见->服务->后台->空进程,优先级越低,被杀死的概率越大,lru算法,回收效益;
3.内存管理机制的目标:
(1)更少的占用内存
(2)在合适的时候,合理的释放系统资源
(3)在系统内存资源紧张时,能释放大部分不重要资源
(4)能够合理的在特殊生命周期中,保存或者还原重要数据,以保证系统能够正确的重新恢复该应用;
4.内存优化方法:
(1)当Service完成任务后,尽量停止它,使用IntentService代替Service;
(2)在UI不可见是,释放掉一些只有UI使用的资源,使用onTrimMemory方法释放
(3)在系统内存紧张时,尽可能多的释放一些非重要资源
(4)避免滥用Bitmap导致的内存浪费
(5)使用针对内存优化过的容器,例如 SparseArray,SparseBooleanArray 和 LongSparseArray,少用枚举常量
(6)避免使用依赖注入框架
(7)使用zip对齐的APK
(8)使用多进程。
 

3.Binder机制

可以从这几方面展开对binder机制的说明:
一、android中进行跨进程通信通常有以下几种方式:
    1.使用Intent
    2.使用文件共享
    3.使用Messenger
    4.使用AIDL
    5.使用ContentProvider
而Messenger,AIDL,ContentProvider都是基于Binder的。
二、Binder是Android系统中的一种IPC进程间通信结构。
    Binder的整个设计是C/S结构,客户端进程通过获取服务端进程的代理,并通过向这个代理接口方法中读写数据来完成进程间的数据通信。

一句话总结:
    ServerManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。

简介
Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信。

Binder原理简述:
    1.Server创建了Binder实体,为其取一个字符形式,可读易记的名字。
    2.将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名字为XX的Binder,它位于Server中。
    3.驱动为这个穿过进程边界的Binder创建位于内核中的实体结点以及ServiceManager对实体的引用,将名字以及新建的引用打包给ServiceManager。
    4.ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。但是一个Server若向ServiceManager注册自己Binder就必须通过这个引用和ServiceManager的Binder通信。
    5.Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Clent也利用保留的引用向ServiceManager请求访问某个Binder:我申请名字叫XX的Binder的引用。
    6.ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder引用,将该引用作为回复发送给发起请求的Client。

当然,不是所有的Binder都需要注册给ServiceManager广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字,所以是 匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。
    Binder的数据拷贝:
为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的"秘密"
    Android之所以选择Binder,我觉得有2个方面的原因
        性能角度:由于在移动设备诸如省电等性能的考虑,广泛地使用进程间通讯对于通信机制的性能有严格的要求,Binder相对于传统的Socket、管道方式更加高效。Bidner数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,内存共享方式一次内存拷贝都不需要,但是实现起来难度高,复杂性大。
        安全角度,传统的进程通信方式对于通信双方的身份并没有做出严格的验证,比如Socket通信ip地址是客户端手动填入,很容易进行伪造,而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。
         
浅析Android Binder机制
【Android进阶】浅析Android Binder机制_大圣代的博客-CSDN博客_android binder使用
Binder机制(非常好理解)
Binder机制(非常好理解)_Pigerrrr的博客-CSDN博客
跟面试官讲Binder
跟面试官讲Binder(零)_linmiansheng的博客-CSDN博客
了解 Binder 看这一篇就够了
了解 Binder 看这一篇就够了_落叶Ex的博客-CSDN博客_binder路由

4.handler消息模型

解释上图中的几个基本概念:
  1.Message
       消息对象,顾名思义就是记录消息信息的类。这个类有几个比较重要的字段:
       a.arg1和arg2:我们可以使用两个字段用来存放我们需要传递的整型值,在Service中,我们可以用来存放Service的ID。
      b.obj:该字段是Object类型,我们可以让该字段传递某个多项到消息的接受者中。
       c.what:这个字段可以说是消息的标志,在消息处理中,我们可以根据这个字段的不同的值进行不同的处理,类似于我们在处理Button事件时,通过switch(v.getId())判断是点击了哪个按钮。
  在使用Message时,我们可以通过new Message()创建一个Message实例,但是Android更推荐我们通过Message.obtain()或者Handler.obtainMessage()获取Message对象。这并不一定是直接创建一个新的实例,而是先从消息池中看有没有可用的Message实例,存在则直接取出并返回这个实例。反之如果消息池中没有可用的Message实例,则根据给定的参数new一个新Message对象。通过分析源码可得知,Android系统默认情况下在消息池中实例化10个Message对象。

  2.MessageQueue
       消息队列,用来存放Message对象的数据结构,按照“先进先出”的原则存放消息。存放并非实际意义的保存,而是将Message对象以链表的方式串联起来的。MessageQueue对象不需要我们自己创建,而是有Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue。我们可以通过Looper.myQueue()获取当前线程中的MessageQueue。
 
3.Looper
       MessageQueue的管理者,在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个MessageQueue对象。倘若我们的线程中存在Looper对象,则我们可以通过Looper.myLooper()获取,此外我们还可以通过Looper.getMainLooper()获取当前应用系统中主线程的Looper对象。在这个地方有一点需要注意,假如Looper对象位于应用程序主线程中,则Looper.myLooper()和Looper.getMainLooper()获取的是同一个对象。

4.Handler
       消息的处理者。通过Handler对象我们可以封装Message对象,然后通过sendMessage(msg)把Message对象添加到MessageQueue中;当MessageQueue循环到该Message时,就会调用该Message对象对应的handler对象的handleMessage()方法对其进行处理。由于是在handleMessage()方法中处理消息,因此我们应该编写一个类继承自Handler,然后在handleMessage()处理我们需要的操作。

Android消息队列模型——Thread,Handler,Looper,Massage Queue_fdaopeng的博客-CSDN博客

5.ams和pms的工作流程

主要是ActivityManagerService(AMS), WindowManagerService(WMS),PackageManerService(PMS)
    AMS 主要用于管理所有应用程序的Activity
    WMS 管理各个窗口,隐藏,显示等
    PMS 用来管理跟踪所有应用APK,安装,解析,控制权限等.

1, 概述

ActivityManagerService对于FrameWork层的重要性不言而喻,Android的四大组件无一不与它打交道:

1.startActivity最终调用了AMS的startActivity系列方法,实现了Activity的启动;Activity的生命周期回调,也在AMS中完成;

 2.startService,bindService最终调用到AMS的startService和bindService方法;

 3.动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)

 4.getContentResolver最终从AMS的getContentProvider获取到ContentProvider.

而PMS则完成了诸如权限校捡(checkPermission,checkUidPermission),Apk meta信息获取(getApplicationInfo等),

四大组件信息获取(query系列方法)等重要功能。AMS和PMS就是以Binder方式提供给应用程序使用的系统服务,

理论上也可以采用这种方式Hook掉它。但是由于这两者使用得如此频繁,Framework给他了一些“特别优待”,

这也给了相对于Binder Hook更加稳定可靠的hook方式。

阅读本文之前,可以先clone一份understand-plugin-framework,参考此项目的ams-pms-hook 模块。本编文章的源码基于android 6.0.

2, AMS获取过程

使用startActivity有两种形式:

 1.直接调用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag。

 2.调用被Activity类重载过的startActivity方法,通常在的Activity中直接调用这个方法就是这种形式;

2.1 startActivity

ContextWrapper的startActivity方法如下,

  1. @Override

  2. public void startActivity(Intent intent) {

  3. mBase.startActivity(intent);

  4. }

最终使用了ContextImpl里面的方法,代码如下:

  1. public void startActivity(Intent intent, Bundle options) {

  2. warnIfCallingFromSystemProcess();

  3. if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {

  4. throw new AndroidRuntimeException(

  5. "Calling startActivity() from outside of an Activity "

  6. + " context requires the FLAG_ACTIVITY_NEW_TASK flag."

  7. + " Is this really what you want?");

  8. }

  9. mMainThread.getInstrumentation().execStartActivity(

  10. getOuterContext(), mMainThread.getApplicationThread(), null,

  11. (Activity)null, intent, -1, options);

  12. }

代码相当简单;知道了两件事:

 1.其一,知道了在Service等非Activity的Context里面启动Activity为什么需要添加FLAG_ACTIVITY_NEW_TASK;

 2.其二,真正的startActivity使用了Instrumentation类的execStartActivity方法;继续跟踪:

  1. public ActivityResult execStartActivity(

  2. Context who, IBinder contextThread, IBinder token, Activity target,

  3. Intent intent, int requestCode, Bundle options) {

  4. // ... 省略无关代码

  5. try {

  6. intent.migrateExtraStreamToClipData();

  7. intent.prepareToLeaveProcess();

  8. // ----------------look here!!!!!!!!!!!!!!!!!!!

  9. int result = ActivityManagerNative.getDefault()

  10. .startActivity(whoThread, who.getBasePackageName(), intent,

  11. intent.resolveTypeIfNeeded(who.getContentResolver()),

  12. token, target != null ? target.mEmbeddedID : null,

  13. requestCode, 0, null, null, options);

  14. checkStartActivityResult(result, intent);

  15. } catch (RemoteException e) {

  16. }

  17. return null;

  18. }

到这里发现真正调用的是ActivityManagerNative的startActivity方法;

2.2 Activity.startActivity

Activity类的startActivity方法相比Context而言直观了很多;这个startActivity通过若干次调用

辗转到达startActivityForResult这个方法,在这个方法内部有如下代码:

Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options);

可以看到,其实通过Activity和ContextImpl类启动Activity并无本质不同,

他都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。

3.Hook AMS

其实startActivity最终通过ActivityManagerNative这个方法远程调用了AMS的startActivity方法。

那么这个ActivityManagerNative是什么呢?

 ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;

每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。

继续看ActivityManagerNative的getDefault()方法做了什么:

static public IActivityManager getDefault() {

return gDefault.get();

}

gDefault这个静态变量的定义如下:

  1. private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {

  2. protected IActivityManager create() {

  3. IBinder b = ServiceManager.getService("activity

  4. IActivityManager am = asInterface(

  5. return am;

  6. }

  7. };

由于整个Framework与AMS打交道是如此频繁,framework使用了一个单例把这个AMS的代理对象保存了起来;

这样只要需要与AMS进行IPC调用,获取这个单例即可。这是AMS这个系统服务与其他普通服务的不同之处,

也是不通过Binder Hook的原因——只需要简单地Hook掉这个单例即可。

这里还有一点小麻烦:Android不同版本之间对于如何保存这个单例的代理对象是不同的;

Android 2.x系统直接使用了一个简单的静态变量存储,Android4.x以上抽象出了一个Singleton类;

以6.0的代码为例说明如何Hook掉AMS,

  1. Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");

  2. // 获取 gDefault 这个字段, 想办法替换它

  3. Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");

  4. gDefaultField.setAccessible(true);

  5. Object gDefault = gDefaultField.get(null);

  6. // 6.0的gDefault是一个 android.util.Singleton对象; 取出这个单例里面的字段

  7. Class<?> singleton = Class.forName("android.util.Singleton");

  8. Field mInstanceField = singleton.getDeclaredField("mInstance");

  9. mInstanceField.setAccessible(true);

  10. // ActivityManagerNative 的gDefault对象里面原始的 IActivityManager对象

  11. Object rawIActivityManager = mInstanceField.get(gDefault);

  12. // 创建一个这个对象的代理对象, 然后替换这个字段, 让的代理对象帮忙干活

  13. Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");

  14. Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),

  15. new Class<?>[] { iActivityManagerInterface },

  16. new IActivityManagerHandler(rawIActivityManager));

  17. mInstanceField.set(gDefault, proxy);

4. PMS获取过程

PMS的获取也是通过Context完成的,具体就是getPackageManager这个方法;

姑且当作已经知道了Context的实现在ContextImpl类里面,直奔ContextImpl类的getPackageManager方法:

  1. public PackageManager getPackageManager() {

  2. if (mPackageManager != null) {

  3. return mPackageManager;

  4. }

  5. IPackageManager pm = ActivityThread.getPackageManager();

  6. if (pm != null) {

  7. // Doesn't matter if we make more than one instance.

  8. return (mPackageManager = new ApplicationPackageManager(this, pm));

  9. }

  10. return null;

  11. }

可以看到,这里干了两件事:

 1.真正的PMS的代理对象在ActivityThread类里面

 2.ContextImpl通过ApplicationPackageManager对它还进行了一层包装

继续查看ActivityThread类的getPackageManager方法,源码如下:

  1. public static IPackageManager getPackageManager() {

  2. if (sPackageManager != null) {

  3. return sPackageManager;

  4. }

  5. IBinder b = ServiceManager.getService("package");

  6. sPackageManager = IPackageManager.Stub.asInterface(b);

  7. return sPackageManager;

  8. }

可以看到,和AMS一样,PMS的Binder代理对象也是一个全局变量存放在一个静态字段中;可以如法炮制,Hook掉PMS。

现在的目的很明切,如果需要Hook PMS有两个地方需要Hook掉:

 1.ActivityThread的静态字段sPackageManager

 2.通过Context类的getPackageManager方法获取到的ApplicationPackageManager对象里面的mPM字段。

5. Hook PMS

现在使用代理Hook应该是轻车熟路了吧,通过上面的分析,Hook两个地方;代码信手拈来:

  1. // 获取全局的ActivityThread对象

  2. Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

  3. Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");

  4. Object currentActivityThread = currentActivityThreadMethod.invoke(null);

  5. // 获取ActivityThread里面原始的 sPackageManager

  6. Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");

  7. sPackageManagerField.setAccessible(true);

  8. Object sPackageManager = sPackageManagerField.get(currentActivityThread);

  9. // 准备好代理对象, 用来替换原始的对象

  10. Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");

  11. Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),

  12. new Class<?>[] { iPackageManagerInterface },

  13. new HookHandler(sPackageManager));

  14. // 1. 替换掉ActivityThread里面的 sPackageManager 字段

  15. sPackageManagerField.set(currentActivityThread, proxy);

  16. // 2. 替换 ApplicationPackageManager里面的 mPM对象

  17. PackageManager pm = context.getPackageManager();

  18. Field mPmField = pm.getClass().getDeclaredField("mPM");

  19. mPmField.setAccessible(true);

  20. mPmField.set(pm, proxy);

Context的实现类里面没有使用静态全局变量来保存PMS的代理对象,

而是每拥有一个Context的实例就持有了一个PMS代理对象的引用;

所以这里有个很蛋疼的事情,那就是如果想要完全Hook住PMS,需要精确控制整个进程内部创建的Context对象;

所幸,插件框架中,插件的Activity,Service,ContentProvider,Broadcast等所有使用到Context的地方,

都是由框架控制创建的;因此要小心翼翼地替换掉所有这些对象持有的PMS代理对象。

前面也提到过,静态变量和单例都是良好的Hook点,这里很好地反证了这句话:

想要Hook掉一个实例变量该是多么麻烦! 其实Hook并不是一项神秘的技术;

一个干净,透明的框架少不了AOP,而AOP也少不了Hook。

所讲解的Hook仅仅使用反射和动态代理技术,更加强大的Hook机制可以进行字节码编织。

6.activity的四种启动模式以及区别

standard模式:
standard是activity默认的启动模式,不指定启动模式时,所有activity使用的都是standard模式
每当启动一个新的activity,它就会进入任务栈,并处于栈顶的位置,对于使用standard模式的activity,系统不会判断该activity在栈中是否存在,每次都会创建一个新的实例
https://blog.csdn.net/qq601517284/article/details/104746007

singleTop模式:
当启动的activity以及位于栈顶时,则直接使用它不创建新的实例,同时它的onNewIntent方法会被回调,通过此方法的参数我们可以去除当前请求的信息,这个activity的onCreate、onStart不会被系统调用,因为它并没有发生改变。如果启动的activity没有位于栈顶时,则创建一个新的实例位于栈顶
举个例子,
假设目前栈内的情况为ABCD,其中ABCD为四个Activity, A位于栈底,D位于栈顶,这个时候假设要再次启动D,如果D的启动模式为singleTop,那么栈内的情况仍然为ABCD;
如果D的启动模式为standard,那么由于D被重新创建,导致栈内的情况就变为ABCDD。
https://blog.csdn.net/qq601517284/article/details/104749560

singleTask:栈内复用模式。
Activity在整个应用程序中只存在一个实例,每次启动该activity时,系统首先会检查栈中是否存在该活动的实例,如果发现已经存在则直接使用该实例,系统也会回调onNewIntent。并将当前activity之上的所有activity出栈,如果没有发现则创建一个新的实例
singleTask模式的activity切换到栈顶会导致在它之上的栈内的activity出栈
举几个例子:
1 比如目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需要的任务栈为S2,由于S2和D的实例均不存在,所以系统会先创建任务栈S2,然后再创建D的实例并将其入栈到S2。·
2 另外一种情况,假设D所需的任务栈为S1,其他情况如上面例子1所示,那么由于S1已经存在,所以系统会直接创建D的实例并将其入栈到S1。
3如果D所需的任务栈为S1,并且当前任务栈S1的情况为ADBC,根据栈内复用的原则,此时D不会重新创建,系统会把D切换到栈顶并调用其onNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD。
Activity的启动模式之singleTask模式_qq601517284的博客-CSDN博客

singleInstance模式:
单实例模式。
是一种加强的singleTask模式,它除了具有singleTask模式的所有特性以为,还加强了一点:具有此种模式的activity只能单独地位于一个任务栈中,比如activity A是singleInstance模式,当A启动后,系统会为它创建一个新的任务栈,然后A独自在这个新的任务栈中,由于栈内复用的特性,后续的请求均不会创建新的activity,除非这个独特的任务栈被系统销毁了
activity会启动一个新的任务栈来管理这个activity,singleInstance模式加载activity时,无聊从哪个任务栈中启动该activity,只会创建一个activity实例,并且会使用一个全新的任务栈来装载该activity实例
与singleTask区别:单独为该activity启动了一个新的任务栈来管理
https://blog.csdn.net/qq601517284/article/details/104749806

4种启动模式的区别

7.sleep和wait的区别,sleep会不会释放锁,notify和notifyAll的区别

sleep和wait
wait是Object的方法,sleep是Thread的静态方法。
wait会释放锁,sleep不会。

notify和notifyAll
通过wait使得线程挂起等待某个条件满足,当其他线程得运行使得这个条件满足时,就可以调用notify或者notifyAll来唤醒这个进程。他们都属于Object的方法。只能在同步块或者同步方法中调用,否则会抛出illegalMonitorException。
当调用wait方法后,线程会进入拥有锁的对象的等待池中。等待池中的对象不会竞争该对象的锁。
当调用notify方法,便会有一个进程从等待池进入该对象的锁池,锁池中的对象可以竞争该对象的锁。而notifyAll会唤醒所有等待池中的线程进入锁池。
优先级高的线程拥有更大的概率通过竞争得到对象的锁,得到锁后该线程会继续执行,直到执行完synchronized的代码块,便会释放锁。没有竞争到锁的进程会留在锁池中,直到该线程重新调用wait方法它才会进入等待池。当锁被释放后,锁池中的线程会重新竞争。

锁池和等待池
等待池:线程A调用B对象的wait方法,它便会进入B的等待池中。
锁池:线程A已经拥有了对象B的锁,此时线程C想要调用B的synchronized方法(或代码块),就会进入B的锁池。

8.Android 性能优化

Android性能优化(一)闪退治理、卡顿优化、耗电优化、APK瘦身_艾阳Blog的博客-CSDN博客_安卓手机性能优化

性能优化:
根据用户的4个方面需求,总结如下:
追求稳定,防止闪退
追求流畅,防止卡顿
追求续航,防止耗损
追求精简,防止臃肿
1.防止程序闪退
出现Crash应用闪退和崩溃一般有三个原因:ANR(程序无响应)、Exception(异常)、LMK(低内存杀死机制)。
1、ANR:造成ANR原因主要是系统规定在四大组件中不能做过多的耗时操作。
首先ANR的发生是有条件限制的,分为以下三点:
1.只有主线程才会产生ANR,主线程就是UI线程;
2.必须发生某些输入事件或特定操作,比如按键或触屏等输入事件,在BroadcastReceiver或Service的各个生命周期调用函数;
3.上述事件响应超时,不同的context规定的上限时间不同
    a.主线程对输入事件5秒内没有处理完毕
A)KeyDispatchTimeout
这个Key事件分发超时的时间,Android默认是5秒,主要是定义在ActivityManagerService.java
// How long we wait until we timeout on key dispatching.
static final int KEY_DISPATCHING_TIMEOUT = 5*1000;

    b.主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕
B)BroadcastTimeOut
广播的超时时间,分为FG和BG,分别是10秒和60秒。同样是定义在ActivitManagerService.java
// How long we allow a receiver to run before giving up on it.
static final int BROADCAST_FG_TIMEOUT = 10*1000;
static final int BROADCAST_BG_TIMEOUT = 60*1000;

    c.主线程在Service的各个生命周期函数时20秒内没有处理完毕。
C)ServiceTimeOut
Service的超时前台服务”20秒、后台服务200秒,定义在ActiveServices.java
// How long we wait for a service to finish executing.
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;

那么导致ANR的根本原因是什么呢?简单的总结有以下两点:
1.主线程执行了耗时操作,比如数据库操作或网络编程
2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。
细分的话,导致ANR的原因有如下几点:
1.耗时的网络访问
2.大量的数据读写
3.数据库操作
4.硬件操作(比如camera)
5.调用thread的join()方法、sleep()方法、wait()方法或者等待线程锁的时候
6.service binder的数量达到上限
7.system server中发生WatchDog ANR
8.service忙导致超时无响应
9.其他线程持有锁,导致主线程等待超时
10.其它线程终止或崩溃导致主线程一直等待
那么如何避免ANR的发生呢或者说ANR的解决办法是什么呢?
1.避免在主线程执行耗时操作,所有耗时操作应新开一个子线程完成,然后再在主线程更新UI。
2.BroadcastReceiver要执行耗时操作时应启动一个service,将耗时操作交给service来完成。
3.避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

2、Exception:造成Crash的原因却有很多,比如:运行时异常的空指针、数组越界、未实例化、强制类型、低内存机制等等,有些时候我们在开发测试阶段都没有出现异常崩溃现象,而发布上线后到了用户手机就会出现各种奇怪闪退。所以,我们要去努力实现一个永远打不死的小强 —— 不会出现Crash闪退的APP。通常我们会使用 try - catch,在发现容易出现崩溃的代码块,主动加上try-catch 预防异常闪退。

3、LMK:由于Android应用的沙箱机制,每个应用程序都运行在一个独立的进程中,各自拥有独立的Dalvik虚拟机实例,系统默认分配给虚拟机的内存是有限度的。当系统内存太低依然会触发LMK(Low Memory Killer)机制,即出现闪退、崩溃现象。

分析ANR:
主要是dump相关的信息到traces文件里面
一般要先分析Main log
  a)先在Main log里面搜索关键字 “ANR”,确定是否真的有发生ANR。
  b)查看在ANR之前,系统是否发生了JE(Java Exception)或者NE(Native Exception),有时候一些ANR是由JE或者NE引起的。
  c)在发生ANR的地方,查看对应的log信息。
 发生ANR的时间,对应的进程ANR in,ANR的进程号PID: ,ANR的原因Reason:,CPU使用信息CPU usage from 。
 Traces文件的分析
 Traces文件里面的相关信息要和main log对应上,才能确保是同一个问题
 ----- pid 进程号 at 2019-05-01 xx:xx:xx -----
Cmd line: 进程名
 pid为进程号,anr时间为2019-05-01 xx:xx:xx,进程名一样。
 
避免ANR:
与其事后来解决ANR的问题,不如在开发的过程中多注意,避免发生ANR。
总结一些需要注意的实现如下:
a)UI主线程尽量只做跟UI相关的工作.
b)耗时的操作,如I/O,网络连接,把它放入单独的线程处理
c)尽量用Handler来处理UI线程和非UI线程之间的交互
 
(二)防止画面卡顿
影响卡顿的两大因素,分别是界面绘制和数据处理。
界面绘制:主要原因是绘制的层级深、页面复杂、刷新不合理,由于这些原因导致卡顿的场景更多出现在 UI 和启动后的初始界面以及跳转到页面的绘制上。
数据处理:导致这种卡顿场景的原因是数据处理量太大,一般分为三种情况,
一是数据在处理 UI 线程,
二是数据处理占用 CPU 高,导致主线程拿不到时间片,
三是内存增加导致 GC 频繁,从而引起卡顿。

布局优化:
 布局复用,使用<include>标签重用layout;
提高显示速度,使用<ViewStub>延迟View加载;
减少层级,使用<merge>标签替换父级布局;
注意使用wrap_content,会增加measure计算成本;
删除控件中无用属性;
 绘制优化:
布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片
自定义View优化。使用 canvas.clipRect() 帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。
刷新优化
减少刷新次数,灵活利用缓存及限时刷新;
缩小刷新区域,局部刷新,避免多余请求;
动画优化
需要实现动画效果时,需要根据不同场景选择合适的动画框架来实现。
有些情况下,可以用硬件加速方式来提供流畅度降低动画卡顿。

(三)耗电优化
常见耗电原因,比如应用为了保持应用进程长期在后台存活,使用各种不合理进程保活方案,导致用户电量严重耗损。
 最后提供一些可供参考耗电优化的方法:
(1)计算优化。算法、for循环优化、Switch..case替代if..else、避开浮点运算。
(2)避免 Wake Lock 使用不当。
(3)使用 Job Scheduler 管理后台任务。
(四)减少安装包(APK)大小
1、资源优化。Android 自带 Lint 工具,检测代码,删除冗余资源,资源文件最少化等
2、图片优化。比如利用 PNG优化工具 对图片做压缩处理。如果应用在4.0版本以上,推荐使用 WebP图片格式。
3、可以使用微信开源资源文件混淆工具——AndResGuard 。
4、插件化热修复开发。比如功能模块放在服务器上,按需下载,可以减少安装包大小。
5、避免重复或无用功能的第三方库。例如,百度地图接入基础地图即可、讯飞语音无需接入离线、图片库Glide\Picasso等

9.常见内存泄漏和内存溢出,怎样规避

内存溢出
    OOM  (out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄漏
    memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
   内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。

memory leak堆积会最终会导致out of memory!

   以发生的方式来分类,内存泄漏可以分为4类:
1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。
3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,所以内存泄漏只会发生一次。
4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。
   从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。
   内存泄漏原因及解决方法
一.单例模式造成的内存泄漏:
  public class Signleton {
        private static Signleton mSignleton;
        private Context mContext;
        private Signleton(Context context){
            this.mContext = context;
        }
        public static Signleton getInstance(Context context){
            if (mSignleton == null){
                mSignleton = new Signleton(context);
            }
     
            return mSignleton;
        }
    }

     因为单例模式的生命周期和应用程序是一样长的,所以当我们在一个activity中调用这个单例,传入activity作为context,单例就持有了这个activity的引用,而当我们退出这个activity时,由于单例的生命周期是同应用程序一样长,所以这个单例还持有activity的引用,这个activity对象就不会被回收,这时就造成了内存泄漏。

如何解决:
 private Signleton(Context context){
        this.context = context.getApplicationContext();
    }
这里我们改成不管传入的context是activity还是其他的都转换为整个应用程序的context,这样生命周期就和单例一样长,就避免了内存泄漏。
二.非静态内部类造成的内存泄漏:  
   private final String TAG = "DemoActivity";
    private static Interior mInterior;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        if (mInterior == null){
            mInterior = new Interior();
        }
    }
    class Interior{ }

这个activity中有一个非静态内部类,因为非静态内部类中隐式持有外部类的引用,所以内部类Interior中就持有activity的引用,
例子中静态变量mInterior的生命周期是和应用程序一样长的,而该静态变量中又持有activity的引用,所以到activity销毁时,回收activity的时候无法被回收,就出现了内存泄漏。
如何解决:
 static class Interior{  
    }
把该非静态内部类改成静态内部类就可以了(因为静态内部类不会持有外部的引用)。
三.Handler造成的内存泄漏:
private void toHandler(){
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                tvTitle.setText("hello");
            }
        },1000);
    }

这段代码中有两个方面会造成内存泄漏:
1.Runnable是匿名内部类,持有Activity的 TextView会造成内存泄漏。
2.TextView持有Activity 的强引用,这样也会造成内存泄漏。
因为handler的消息机制,当Activity销毁,handler中还有为处理的Message时就会持有activity的引用从而导致无法被回收,出现内存泄漏。
如何解决:
方法1:改成静态内部类+弱引用
private static class DemoRunnable implements Runnable{
        private WeakReference<TextView> wTextView;
        protected DemoRunnable(TextView textView){
            wTextView = new WeakReference<TextView>(textView);
        }
        @Override
        public void run() {
            wTextView.get().setText("hello");
        }
    }
方法2:在Activity的onDestory中移除mHandler的所有Message
   @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
四.线程造成的内存泄漏:
线程的内存泄漏同Handler一样也是匿名内部类Runnable造成的,解决方式同handler方法1一样。
五.其他内存泄漏
OOM即Out Of Memory,一般是由程序编写者对内存使用不当,对该释放的内存资源没有释放,导致其一直不能被再次使用而使内存被耗尽的现象。根本的解决办法是对代码进行优化:在内存引用上做些处理,使用软引用,虚引用和弱引用;在内存中加载图片时直接在内存中做处理,边界压缩等;建立动态回收内存机制;优化Dalvik虚拟机的堆内存分配,自定义堆内存大小等。
1.static关键字(非静态内部类的静态引用)
2.构造adapter没有使用缓存Converview
资源未关闭造成的内存泄漏:
1.数据库的cursor没有关闭
2.调用registerReceiver后未调用unregisterReceiver
3.ContentObserver注册未调用unregister
4.未关闭InputStream/OutputStream
5.Bitmap使用未调用recycle

10.发送和接收隐式广播

Android8.0无法接收隐式广播消息
为何限制隐式广播:
在Manifest里面注册的系统广播接收器会被缓存在系统中,即使当App关闭之后,如果有相应的广播发出,应用程序仍然会被唤醒。而动态注册的广播则跟随组件的生命周期而消存。因此在Manifest里面注册广播接收器的App越多,设备的性能就越容易受到影响,限制隐式广播主要是为了优化系统性能。
解决限制:
1.优先使用动态注册Receiver的方式,能动态注册绝不使用Manifest注册
2.如果一定要Manifest注册,那么当发送广播的时候,指定广播接收者的包名,即发送显式广播
intent.setClass()或intent.setComponent()或intent.setPackage()等写法。
3.如果要接收系统广播,那么只能暂时把App的targetSdkVersion改为25或以下。

附:Android广播机制详解
Android广播机制详解_huaxun66的博客-CSDN博客_安卓广播

11.UI线程是怎样创建和绑定到进程的

当一个Android程序启动时,系统会为该程序创建一个进程,然后创建一个线程运行在这个进程中,称为主线程。
主线程是程序和UI控件交互的进程,所以也被称为UI线程。
单线程模型:Android中在单条线程中进行事件分发及UI交互的机制
两条规则:
1.不要在UI线程中进行耗时操作(会引起未响应等待)
2.不要在UI线程外操作界面控件(界面控件是非线程安全的)

一个android程序,首先从ActivityThread的main函数启动,创建了一个主线程。
ActivityManagerService通过binder,与ActivityThread取得联系,并调用相关API,启动某个activity。
ActivityThread内部,启动某个activity就用了looper+message+handle一套东西。
大致流程如下。
a) scheduleLaunchActivity
b) sendMessage(H.LAUNCH_ACTIVITY, r) 传说中的mH发送消息,looper会接收消息,按先进先出的原则存储/转发消息,转发其实就是转发到mH,于是回到mH的回调函数handleMessage。
c) mH处理消息,执行handleLaunchActivity
d) performLaunchActivity
e) mInstrumentation.callActivityOnCreate(activity, r.state);
详见:
Android UI 主线程,啥玩意?还有Handler+Looper+MessageQueue几个意思?
Android UI 主线程,啥玩意?还有Handler+Looper+MessageQueue几个意思?_长江很多号的博客-CSDN博客

Android线程的创建与销毁
Android线程的创建与销毁_花扁蛙的博客-CSDN博客_android销毁线程的方法
android 主线程和子线程交互方式
android 主线程和子线程交互方式_sx_mushuai的博客-CSDN博客

12.性能分析工具:systrace, traceview,leakcanary,Hierarchy Viewer等等

性能标准:内存占用、cpu占用、流量耗用、电池温度、流畅度等等。
TraceView
(1)查看跟踪代码的执行时间,分析哪些是耗时操作。
(2)可以用于跟踪方法的调用,尤其是Android Framework层的方法调用关系。
(3)可以方便的查看线程的执行情况,某个方法执行时间、调用次数、在总体中的占比等,从而定位性能点。
 在开始调试的地方,如Activity的onCreate函数,
添加Debug.startMethodTracing("tracefilename");
 结束调试的地方,如Activity的onStop函数,
添加Debug.stopMethodTracing();

Systrace跟踪代碼
Systrace的功能包括跟踪系统的I/O操作、内核工作队列、CPU负载以及Android各个子系统的运行状况等。在Android平台中,它主要由3部分组成:
 1.内核部分:Systrace利用了Linux Kernel中的ftrace功能。所以,如果要使用Systrace的话,必须开启kernel中和ftrace相关的模块。
 2.数据采集部分:Android定义了一个Trace类。应用程序可利用该类把统计信息输出给ftrace。同时,Android还有一个atrace程序,它可以从ftrace中读取统计信息然后交给数据分析工具来处理。
 3.数据分析工具:Android提供一个systrace.py(python脚本文件,位于Android SDK目录/tools/systrace中,其内部将调用atrace程序)用来配置数据采集的方式(如采集数据的标签、输出文件名等)和收集 ftrace统计数据并生成一个结果网页文件供用户查看。
  从本质上说,Systrace是对Linux Kernel中ftrace的封装。应用进程需要利用Android提供的Trace类来使用Systrace。
(1)应用层代码添加systrace跟踪方式:
   Trace.beginSection(“TEST”);
   Trace.endSection();
(2)framework的java层代码添加systrace跟踪方式:
  Trace.traceBegin(Trace.TRACE_TAG_VIEW, “performTraversals”);
  Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  也可以使用:
  ATRACE_BEGIN(“TEST”);
  ATRACE_END();
(3)framework的native代码添加systrace跟踪方式:   
  ATRACE_INIT();
  ATRACE_CALL();

LeakCanary 检测内存泄露
使用方法也很简单,首先加入依赖
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
从依赖中也是可以看出猫腻的。
然后在我们程序的Applictaion中进行安装,当然,不要忘记在清单文件中注册该Application。
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanary.install(this);
    }
}
安装你的应用打开应用退出来,这时候你发现桌面上多了一个Leaks的图标的应用。
打开它后通知栏会有一个通知,通知你发生了内存泄露。
然后在软件里你会看到内存泄露的跟踪信息。


Hierarchy Viewer工具
布局性能优化
sdk/tools下并没有HierarchyViewer工具,打开monitor.bat,就找到HierarchyViewer工具
 
选择一个节点,点击右上角的的按钮,就可以获取到布局绘制的时间
这里我们主要关注下面的三个圆圈,从左到右依次,代表View的Measure, Layout和Draw的性能,不同颜色代表不同的性能等级:
1) 绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;例如,一个绿点的测量时间意味着这个视图的测量时间快于树中的视图对象的50%。
2)黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;例如,一个黄点布局意味着这种观点有较慢的布局时间超过50%的树视图对象。
3)红: 表示该View的此项性能是View Tree中最慢的;例如,一个红点的绘制时间意味着花费时间最多的这一观点在树上画所有的视图对象。

测量结果分析
红色节点是代表应用性能慢的一个潜在问题,下面是几个例子,如何来分析和解释红点的出现原因?
1)如果在叶节点或者ViewGroup中,只有极少的子节点,这可能反映出一个问题,应用可能在设备上运行并不慢,但是你需要指导为什么这个节点是红色的,可以借助Systrace或者Traceview工具,获取更多额外的信息;
2)如果一个视图组里面有许多的子节点,并且测量阶段呈现为红色,则需要观察下子节点的绘制情况;
3)如果视图层级结构中的根视图,Messure阶段为红色,Layout阶段为红色,Draw阶段为黄色,这个是比较常见的,因为这个节点是所有其它视图的父类;
4)如果视图结构中的一个叶子节点,有20个视图是红色的Draw阶段,这是有问题的,需要检查代码里面的onDraw方法,不应该在那里调用。

布局常见问题与优化建议
1)没有用的父布局时指没有背景绘制或者没有大小限制的父布局,这样的布局不会对UI效果产生任何影响。我们可以把没有用的父布局,通过<merge/>标签合并来减少UI的层次;
2)使用线性布局LinearLayout排版导致UI层次变深,如果有这类问题,我们就使用相对布局RelativeLayout代替LinearLayout,减少UI的层次;
3)不常用的UI被设置成GONE,比如异常的错误页面,如果有这类问题,我们需要用<ViewStub/>标签,代替GONE提高UI性能。

13、hashmap的原理是怎样的

HashMap的工作原理
定位哈希桶数组索引位置
整个过程本质上就是三步:
1)拿到key的hashCode值
2)将hashCode的高位参与运算,重新计算hash值
3)将计算出来的hash值与(table.length - 1)进行&运算

HashMap的put方法实现
思路如下:
1.table[]是否为空
2.判断table[i]处是否插入过值
3.判断链表长度是否大于8,如果大于就转换为红黑二叉树,并插入树中
4.判断key是否和原有key相同,如果相同就覆盖原有key的value,并返回原有value
5.如果key不相同,就插入一个key,记录结构变化一次

    HashMap的get方法实现

实现思路:
1.判断表或key是否是null,如果是直接返回null
2.判断索引处第一个key与传入key是否相等,如果相等直接返回
3.如果不相等,判断链表是否是红黑二叉树,如果是,直接从树中取值
4.如果不是树,就遍历链表查找

扩容机制
HashMap扩容可以分为三种情况:
第一种:使用默认构造方法初始化HashMap。
第二种:指定初始容量的构造方法初始化HashMap。
第三种:HashMap不是第一次扩容。如果HashMap已经扩容过的话,那么每次table的容量以及threshold量为原有的两倍。

HashMap都在用,原理你真的了解吗?
HashMap都在用,原理你真的了解吗?_程序大视界的博客-CSDN博客

14.hashset的原理是怎样的?为什么能去重

hashSet的实现原理:
往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。   
情况1:如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
情况2:如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行 添加。
详见
HashSet的实现原理去重_这瓜保熟么的博客-CSDN博客_hash 去重
java hashset去重原理_Cris_February的博客-CSDN博客_hashset去重原理

15.okhttp的基本用法,和底层原理

OKHttp使用详解,步骤挺详细的,适合初学者使用!
OKHttp使用详解,步骤挺详细的,适合初学者使用!_m1766521525的博客-CSDN博客_okhttp用法
OkHttp总结
整个OkHttp功能的实现就在这五个默认的拦截器中,所以先理解拦截器模式的工作机制是先决条件。这五个拦截器分别为: 重试拦截器、桥接拦截器、缓存拦截器、连接拦截器、请求服务拦截器。每一个拦截器负责的工作不一样,就好像工厂流水线,最终经过这五道工序,就完成了最终的产品。
但是与流水线不同的是,OkHttp中的拦截器每次发起请求都会在交给下一个拦截器之前干一些事情,在获得了结果之后又干一些事情。整个过程在请求向是顺序的,而响应向则是逆序。
当用户发起一个请求后,会由任务分发起Dispatcher将请求包装并交给重试拦截器处理。
1、重试拦截器在交出(交给下一个拦截器)之前,负责判断用户是否取消了请求;在获得了结果之后,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器。
2、桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的行为(如:GZIP压缩);在获得了结果后,调用保存cookie接口并解析GZIP数据。
3、缓存拦截器顾名思义,交出之前读取并判断是否使用缓存;获得结果后判断是否缓存。
4、连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流;在获得结果后不进行额外的处理。
5、请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的响应数据。
在经过了这一系列的流程后,就完成了一次HTTP请求!

OKHttp原理解析
终于找到了一篇一看就懂的 OKHttp 原理解析_司小三石的博客-CSDN博客
OkHttp原理分析总结
OkHttp原理分析总结_且听风吟9527的博客-CSDN博客_okhttp原理
Okhttp3 总结研究 (面试)
Okhttp3 总结研究 (面试)_茶卡y的博客-CSDN博客_okhttp3 面试

16.对称加密和非堆成加密的优缺点,以及java中的基本用法

对称加密 非对称加密 不可逆加密算法原理及优缺点
Java进阶(八)Java加密技术之对称加密 非对称加密 不可逆加密算法_ZhangSingle的博客-CSDN博客
java对称加密与非对称加密使用
java对称加密与非对称加密_chengbinbbs的博客-CSDN博客_java对称加密和非对称加密
Android应用安全开发之浅谈加密算法的坑
Android应用安全开发之浅谈加密算法的坑 - 阿里安全 - 博客园

17.自定义view,关键方法?

重点知识梳理
自定义View分类
PS :实际上ViewGroup是View的一个子类。

自定义View流程 关键方法:

18.自定义recyclerview,以及recyclerview的条目复用,优化原理

Android自定义RecyclerView:实现真正的Gallery效果
Android 自定义RecyclerView 实现真正的Gallery效果_haozidao的博客-CSDN博客

recyclerview的条目复用,优化原理
RecyclerView 缓存和Item复用导致数据混乱
1. 设置缓存数量    
// RecyclerView可以设置自己所需要的ViewHolder数量
recyclerview.setItemViewCacheSize(20)
2.禁止RecyclerView复用
viewHolder.setIsRecyclable(false)
3.getItemViewType 适配器里面的这个方法返回改为position
 @Override
    public int getItemViewType(int position) {
     
        return position;
    }

  return super.getItemViewType(position);
改为
 return position;

4.RecycleView相对于ListView来说,它本身已经帮你解决了布局复用问题,但是使用不当,还会出现布局错乱问题。
1.当显示的数据是同步显示的,一般出现错乱都是因为逻辑问题,在recycleview中逻辑判断写if一定要写else
2.当显示的数据是异步的,比如加载网页图片,在图片下载成功以后再设置给imageview显示,如果显示错乱,可以在最开始给imageview设置一个tag,image.setTag(url),在图片下载成功以后,调用image.getTag(),如果获取的tag和之前设置的tag相同,再进行显示。
3.如果是多布局,在使用的时候一定要用getItemViewType进行类型判断
4.如果插入数据和删除数据导致刷新错乱,在刷新完以后要调用adapter.notifyItemChanged(int posation)或者 adapter.notifyItemRangeChanged(posationStart, itemCount),刷新position位置
5.当bindViewHolder没有调用导致错乱时,在给recycleview设置adapter之前调用adapter.setHasStableIds(true),然后在adapter里面重写getItemId(int position),返回position

@Override
public long getItemId(int position) {
    return position;
}

5.因为RecyclerView自带ViewHoler,所以会自动复用Item。
有时候因为这个会产生一些条目数据错误。
最简单的停止复用
recyclerView.getRecycledViewPool().setMaxRecycledViews(viewType,0);
参数说明
setMaxRecycledViews(int viewType,int max);
viewType: 值必须和getItemViewType()的返回值保持一致。如果是多类型Item,选择不想用复用的item的值。
max:设置缓存池里最多持有几个ViewHolder,设置为0就不存在复用。
缺点
如果这样设置后,列表条目增加,数据变大时,会造成性能下降,甚至是oom.
列表数据不多的场景可以这样做。
数据偏多的时候还是建议使用SparseArray缓存item的状态。

RecyclerView的复用导致的多选混乱
RecyclerView的复用导致的多选混乱_蜗牛改变自己的博客-CSDN博客

安卓优化之SparseArray易懂详解
安卓优化之SparseArray易懂详解_万明智的博客-CSDN博客

19.android OOM的原因有哪些

1.数据库的cursor没有关闭。
2.构造adapter没有使用缓存contentview。
3.调用registerReceiver()后未调用unregisterReceiver().
4.未关闭InputStream/OutputStream。
5.Bitmap使用后未调用recycle()。
6.Context泄漏。
7.static关键字等

20.list set map的区别,去重用哪一个?(面试题)

list set map的区别,去重用哪一个?(面试题)_qq601517284的博客-CSDN博客

21.startService和bindService区别,多次启动会调用哪些方法?

startService:
作用:启动服务
生命周期:onCreate() → onStartCommand() → onDestory()
bindService:
作用:启动服务
生命周期:onCreate() → onBind() → onUnbind() → onDestory()
区别:

  1. 从通讯角度看,使用startService()方法启动的服务不能与Activity进行通讯,而使用bindService()方法启动的服务可以与Activity进行通讯。

  2. 从生命周期看,startService()方法启动服务是通过startCommand()方法,而bindService()方法是通过onBind()方法。

  3. 通过startService()方法启动的服务,当调用者退出后,服务仍然可以运行,而使用bindService()方法启动的服务则停止运行。

  • onCreate()方法在生命周期中只调用一次,若在服务已经启动的前提下,多次调用startService()方法或者调用bindService()方法,都不会再执行onCreate()方法,在使用starService()方法启动服务的情况下,会多次调用onStart()方法。

22.MeasureSpec测量模式分别代表什么意思?

UNSPECIFIED:不对View大小做限制,如:ListView,ScrollView
EXACTLY:确切的大小,如:100dp或者march_parent
AT_MOST:大小不可超过某数值,如:wrap_content

附:2020JAVA面试题附答案(持续更新版)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值