安卓高面试知识整理

大兔子大兔子 提交于 2020-08-16 08:47:32

                                                安卓高面试知识整理

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

https://blog.csdn.net/carson_ho/article/details/107012652

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

https://blog.csdn.net/carson_ho/article/details/98477394

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对象,负责和其它进程进行通信。

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

        Step 1. 无论是通过Launcher来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都通过Binder进程间通信进入到ActivityManagerService进程中,并且调用ActivityManagerService.startActivity接口;

        Step 2. ActivityManagerService调用ActivityStack.startActivityMayWait来做准备要启动的Activity的相关信息;

        Step 3. ActivityStack通知ApplicationThread要进行Activity启动调度了,这里的ApplicationThread代表的是调用ActivityManagerService.startActivity接口的进程,对于通过点击应用程序图标的情景来说,这个进程就是Launcher了,而对于通过在Activity内部调用startActivity的情景来说,这个进程就是这个Activity所在的进程了;

        Step 4. ApplicationThread不执行真正的启动操作,它通过调用ActivityManagerService.activityPaused接口进入到ActivityManagerService进程中,看看是否需要创建新的进程来启动Activity;

 

        Step 5. 对于通过点击应用程序图标来启动Activity的情景来说,ActivityManagerService在这一步中,会调用startProcessLocked来创建一个新的进程,而对于通过在Activity内部调用

startActivity来启动新的Activity来说,这一步是不需要执行的,因为新的Activity就在原来的Activity所在的进程中进行启动;

        Step 6. ActivityManagerServic调用ApplicationThread.scheduleLaunchActivity接口,通知相应的进程执行启动Activity的操作;

        Step 7. ApplicationThread把这个启动Activity的操作performLaunchActivity转发给ActivityThread,ActivityThread通过ClassLoader导入相应的Activity类,然后把它启动起来。

 

2.内存管理,进程管理

内存管理:

android内存机制的理解 对象的操作是在退内存中进行 而基本类型是在寄存器中进行,由于寄存器的读写速度远快于内存故基本类型的对性能影响没有对象大,常见的对象实例内存操作 拆箱和装箱操作 string类型的创建等等。

方法区和堆的内存回收由垃圾回收器决定

java的内存管理 即内存的分配和释放,内存管理的区域主要有两大快 即栈和堆,栈中的变量在程序运行处该变量的作用域之后自动回收,而堆中的内存通过垃圾回收器回收,当程序的堆内存不够用时,垃圾回收器会回收那些不被任何线程引用的对象实例。

内存泄露:jvm的内存回收机制是造成内存泄露的根本原因 jvm启动GC仅仅只在分配给程序的内存用完时才会启动,但是回收的对象内存是那些在任何线程都没有引用指向的实例, 而许多对象虽然在使用一次之后不再使用,但是依然可以被引用,因此良好的代码习惯是在退出对象的作用域之后将其引用置为null,让堆中的对象实例能够得到及时回收System.gc()提醒jvm进行垃圾回收,但是具体的回收还是虚拟机在检测到堆内存不够用时进行回收

进程机制和内存机制

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

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

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

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

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

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

当我们内存不足资源紧缺的时候,会从低到高去分别释放进程,从而使app有足够的内存运行,Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM,不同的设备会有不同的内存空间,所以啊,买Android手机的朋友,最好选配置好一点的,不然卡死你,并且如果多app运行在后台的话,也会增加流量消耗

其实Android的这种机制有利也有弊,弊我想大家都知道了,那利是什么呢?不知道大家发现一个问题没,上述说了Android和苹果的机制不同,苹果是会杀死进程,也就是说我重新打开这个app的时候,是需要重新去链接网络获取信息的,这样对有些用户来说也挺烦的,如果我在玩游戏,突然来消息了,我切换出去久了再切换回来,我又重新来到登录界面了,我就挺烦这个的,但是Android如果内存足够,就不需要重新登录了,另外,Android的app从后台切换到前台时,由于系统没有杀死app,界面是不需要重新读取,启动速度会加快,也挺人性化的
内存泄漏

我们了解了内存机制之后,我们来看看内存泄漏,内存泄漏通俗一点讲就是应该被释放的资源,因为某些原因而没有被释放掉,为什么会这样呢?我们需要简单了解下Java的垃圾回收机制(GC)
GC机制

我们在开发过程中,少不了对象的创建,我们所创建的对象都是存储在Java虚拟机的内存之中,我们并不用去关心对象的释放,因为这一切都是由GC帮我们去完成,但是,这是有条件的
1,无用对象
2,内存不足

无用对象简单概括就是,我们创建出一个对象的时候,但并没有对这个对象赋值或者说没用到这个对象,另外一种,这个对象已经使用完了,不会再被其他程序调用,举个例子,我们使用I/O流的时候,我们使用完了之后都需要释放掉,因为我们使用完之后已经没用其他程序在去调用了,那么这个对象应当属于无用对象

当内存空间出现紧缺,Java会回收一些进程来确保内存空间,我们知道我们每一个app都是有单独的进程,回收进程的时候,也就是将我们的对象资源进行回收,也就是将我们无用对象进行回收

但是,如果在一个进程当中,对象已经用完了,但是因为某些原因与其他资源捆绑住,GC回认为这个对象可能还需要被其他程序调用,但是实际上没有程序再去调用它,那么就会导致这个对象一直在占用着内存,另外一种,如果两个无用对象相互牵引着,也会导致GC无法回收,内存一直会被占用,就会导致了内存泄漏,举几个例子:

1,广播在Activity销毁的时候没有释放掉:因为广播持有Activity对象,当Activity销毁时,GC去回收它的对象,但是这时候,我们没有释放广播,所以它没办法去回收,就会导致内存泄漏

2,Handler做耗时操作:如果我们在Handler中做比较耗时的操作,在这个操作还没有完成的时候,我们销毁Activity,Handler应当是个无用对象,但是它却赖着不走,GC也无法强制把它带走,也会造成内存泄漏

3,非静态内部类的静态调用:我们在Activity中定义一个内部类,然后以静态的方式去引用它,我们知道静态的特性是唯一性,也就是说这个被定义为静态的对象的生命周期是和app生命周期一样长,但是我们的内部类是非静态的,是依附在当前Activity的生命周期上的,当Activity被回收后,因为内部类的对象持有Activity对象,而且它的生命周期是app的生命周期,所以会导致Activity没办法被正常回收


内存溢出
内存泄漏会造成内存无法被释放,系统无法为其他一些app开辟更多的内存空间,当我们内存泄漏越来越大的时候,已经超出我们正常内存空间的时候,就是我们常说的内存溢出

相信大家对内存泄漏和内存溢出的危害都有一定的了解,它可以让我们手机越来越卡,严重的会造成系统崩溃,举个很通俗的例子,我们往袋子中注入水,当越来越多的时候,水会溢出来,并且严重的时候袋子会裂开,所以,不要小看内存溢出,小的内存溢出可能我们感觉不到,但是积少成多,终究会发生问题
这也就是为什么我们手机系统会越来越卡,也是我们为什么Android系统会崩溃的原因,我们来简单总结下:
1,app都有自己单独的进程,每台设备在出厂的时候都会设定了我们单个app所占用的内存
2,app运行在后台时,当系统内存不足的时候,会根据进程的级别高低去回收进程
3,当我们对象资源无法被回收的时候,会造成内存泄漏,如果app的内存泄漏过多,超过内存空间,就会造成内存溢出
4,内存泄漏和内存溢出会造成手机系统越来越卡,严重的会导致系统崩溃
5,在实际开发之中,我们要严格按照Android的规范来编写程序,避免内存泄漏内存溢出等问题发生





3.Binder机制

Binder机制

​ Android系统中进程间通讯(IPC)的一种方式,Android中ContentProvider、Intent、aidl都是基于Binder

内存管理

​ Binder最大只能传1M的数据,因为Binder驱动只预留了一段1M大小的虚拟地址
mmap中定义BINDER_VM_SIZE为1M,Binder数据需要跨进程传递,需要在内核上开辟空间,所以允许在Binder上传递的数据不是无限大的

Binder 架构

​ 通信采用C/S架构,包含Client、 Server、 ServiceManager 以及 Binder 驱动
在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构
在 Native 层以 ioctl 的方式与 Binder 驱动通讯

在这里插入图片描述

Binder 机制

在这里插入图片描述

注册服务端

​ 通过 ServiceManager 注册服务。向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息,然后向ServiceManager的svcinfo列表中缓存注册的服务

获取服务端

​ 通过 ServiceManager 向 svcinfo 列表中查询,返回服务端的代理

发送请求

通过BinderProxy将请求参数发送给ServiceManager
通过共享内存的方式使用内核方法copy_from_user()将参数拷贝到内核空间
客户端进入等待状态
Binder驱动向服务端的todo队列里面插入一条事务
事务执行完之后把执行结果通过 copy_to_user()将内核的结果拷贝到用户空间(只执行拷贝命令,不拷贝数据,binder只进行一次拷贝)
唤醒客户端并响应结果




Binder 驱动

​ 运行在内核空间,负责各个用户进程通过 Binder 通信的内核模块
在这里插入图片描述

用户空间
用户程序的运行空间
只能执行简单的运算,不能直接调用系统资源,必须通过系统接口(又称 system call),才能向内核发出指令
用户空间访问内核空间的唯一方式就是系统调用,如访问文件,网络等。通过统一接口,资源访问均在内核控制下执行,以免用户程序对系统资源的越权访问。
内核空间
Linux 内核的运行空间
可以执行任意命令,调用系统的一切资源
他们之间考虑到安全因素是隔离的,即使用户程序崩溃,内核也不受影响
内核状态
当一个任务(进程)执行系统调用而陷入内核代码中执行时的状态,此时处理器处于特权级最高的(0级)内核代码中执行
用户运行态
当进程在执行用户自己的代码时,此时处理器在特权级最低的(3级)用户代码中运行










Binder 进程与线程

在这里插入图片描述

对于底层Binder驱动,通过 binder_procs 链表记录所有创建的 binder_proc 结构体,binder 驱动层的每一个 binder_proc 结构体都与用户空间的一个用于 binder 通信的进程一一对应,且每个进程有且只有一个 ProcessState 对象,这是通过单例模式来保证的。在每个进程中可以有很多个线程,每个线程对应一个 IPCThreadState 对象,IPCThreadState 对象也是单例模式,即一个线程对应一个 IPCThreadState 对象,在 Binder 驱动层也有与之相对应的结构,那就是 Binder_thread 结构体。在 binder_proc 结构体中通过成员变量 rb_root threads,来记录当前进程内所有的 binder_thread。

Binder 线程池:每个 Server 进程在启动时创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 binder 线程,例如 Android 的 system_server 进程就存在16个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。

ServiceManager 启动

分为 framework 层和 native 层,framework 层只是对 native 层进行了封装方便调用
启动是系统在开机时,init 进程解析 init.rc 文件调用 service_manager.c 中的 main() 入口启动的

在这里插入图片描述

启动过程
1、打开驱动创建全局链表 binder_procs
2、将自己当前进程信息保存到 binder_procs 链表
3、开启 loop 不断的处理共享内存中的数据,并处理响应


ServiceManager 注册服务

​ 通过 ServiceManager 的 addService() 方法来注册服务

在这里插入图片描述

注册过程
1、ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令,携带 ADD_SERVICE_TRANSACTION 命令
2、注册服务的线程进入等待状态。waitForResponse()
3、Binder 驱动收到请求命令向 todo 队列里面添加一条注册服务的事务
4、事务处理完之后发送 BR_TRANSACTION 命令
5、ServiceManager 收到命令后向 svcinfo 列表中添加已经注册的服务
6、最后发送 BR_REPLY 命令唤醒等待的线程,通知注册成功





ServiceManager 获取服务

在这里插入图片描述

获取过程
1、ServiceManager 向 Binder 驱动发送 BC_TRANSACTION 命令携带 CHECK_SERVICE_TRANSACTION 命令
2、获取服务的线程进入等待状态 waitForResponse()
3、Binder 驱动收到请求命令向 todo 队列里面添加一条查询服务的事务
4、如果查询到,事务发送BC_TRANSACTION命令唤醒
5、ServiceManager 收到命令后向 svcinfo 列表中添加已经注册的服务
6、最后发送 BR_REPLY 命令唤醒等待的线程,通知查询成功
7、如果未查询到,与 binder_procs 链表中的服务进行一次通讯再响应






完整通讯

在这里插入图片描述

步骤
1、通过 ServiceManager 获取到服务端的 BinderProxy 代理对象
2、调用 BinderProxy 将参数,方法标识(如:TRANSACTION_test,AIDL中自动生成)传给 ServiceManager
3、客户端线程进入等待状态
4、ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行执行方法的事务
5、事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间
6、唤醒等待的线程,响应结果





问题

传统的 Linux 通信机制,比如 Socket,管道等都是内核支持的;但是 Binder 并不是 Linux 内核的一部分,它是怎么做到访问内核空间的呢

​ Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行
它在运行时被链接到内核作为内核的一部分在内核空间运行

Binder一次通信中进行了几次有效数据拷贝

​ 一次。
Binder驱动程序位于内存的内核空间,Binder服务程序位于位于内存的用户空间,为Binder驱动和Binder服务分配了同一块物理内存地址
ServiceManager通过内核方法 copy_from_user()将数据拷贝到内核空间,这个过程中数据的物理地址发生了变化
事务将执行结果通过copy_to_user() 将结果拷贝到用户空间,这个过程中只传递了数据的虚拟地址,数据的物理地址没有变化


还有那些IPC通讯方式

linux中消息队列、共享内存、信号量、socket
socket传输效率低,开销大,用于网络和进程间低速通信,两次数据拷贝
消息队列、信号量采用存储-转发方式,两次拷贝
共享内存控制复杂,0次拷贝


4.handler消息模型

https://mp.csdn.net/console/editor/html/107675095

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这个方法,在这个方法内部有如下代码:

 
  1. Instrumentation.ActivityResult ar =

  2. mInstrumentation.execStartActivity(

  3. this, mMainThread.getApplicationThread(), mToken, this,

  4. intent, requestCode, options);

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

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

3.Hook AMS

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

那么这个ActivityManagerNative是什么呢?

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

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

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

 
  1. static public IActivityManager getDefault() {

  2. return gDefault.get();

  3. }

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.  
  3. // 获取 gDefault 这个字段, 想办法替换它

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

  5. gDefaultField.setAccessible(true);

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

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

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

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

  11. mInstanceField.setAccessible(true);

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

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

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

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

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

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

  20. new IActivityManagerHandler(rawIActivityManager));

  21. 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.  
  6. IPackageManager pm = ActivityThread.getPackageManager();

  7. if (pm != null) {

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

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

  10. }

  11. return null;

  12. }

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

 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.  
  6. // 获取ActivityThread里面原始的 sPackageManager

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

  8. sPackageManagerField.setAccessible(true);

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

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

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

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

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

  15. new HookHandler(sPackageManager));

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

  18. sPackageManagerField.set(currentActivityThread, proxy);

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

  21. PackageManager pm = context.getPackageManager();

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

  23. mPmField.setAccessible(true);

  24. 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。
https://blog.csdn.net/qq601517284/article/details/104749709






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




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

sleep和wait
wait是Object的方法,wait是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 性能优化

https://blog.csdn.net/csdn_aiyang/article/details/74989318

性能优化:
根据用户的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广播机制详解
https://blog.csdn.net/huaxun66/article/details/52935631

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几个意思?
https://blog.csdn.net/newchenxf/article/details/47414773










Android线程的创建与销毁
https://blog.csdn.net/lurely/article/details/81775406
android 主线程和子线程交互方式
https://blog.csdn.net/u012291541/article/details/38815943


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都在用,原理你真的了解吗?
https://blog.csdn.net/xuri24/article/details/86222272

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

hashSet的实现原理:
往Haset添加元素的时候,HashSet会先调用元素的hashCode方法得到元素的哈希值 ,然后通过元素 的哈希值经过移位等运算,就可以算出该元素在哈希表中 的存储位置。   
情况1:如果算出元素存储的位置目前没有任何元素存储,那么该元素可以直接存储到该位置上。
情况2:如果算出该元素的存储位置目前已经存在有其他的元素了,那么会调用该元素的equals方法与该位置的元素再比较一次,如果equals返回的是true,那么该元素与这个位置上的元素就视为重复元素,不允许添加,如果equals方法返回的是false,那么该元素运行 添加。
详见
https://blog.csdn.net/luzhensmart/article/details/88056574
https://blog.csdn.net/qq_32725491/article/details/79392801





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

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










OKHttp原理解析
https://blog.csdn.net/lanxingfeifei/article/details/64442574
OkHttp原理分析总结
https://blog.csdn.net/my_csdnboke/article/details/103840783
Okhttp3 总结研究 (面试)
https://blog.csdn.net/u012881042/article/details/79759203




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

对称加密 非对称加密 不可逆加密算法原理及优缺点
https://blog.csdn.net/ZhangSingle/article/details/70854540
java对称加密与非对称加密使用
https://blog.csdn.net/chengbinbbs/article/details/78640589
Android应用安全开发之浅谈加密算法的坑
http://www.cnblogs.com/alisecurity/p/5312083.html




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

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

自定义View流程 关键方法:

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

Android自定义RecyclerView:实现真正的Gallery效果
https://blog.csdn.net/haozidao/article/details/51776559

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的复用导致的多选混乱
https://blog.csdn.net/u010597493/article/details/79975190

安卓优化之SparseArray易懂详解
https://blog.csdn.net/wanyouzhi/article/details/78489270

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的区别,去重用哪一个?(面试题)

https://blog.csdn.net/qq601517284/article/details/104776819

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

startService:
作用:启动服务
生命周期:onCreate() → onStartCommand() → onDestory()
bindService:
作用:启动服务
生命周期:onCreate() → onBind() → onUnbind() → onDestory()
区别:
从通讯角度看,使用startService()方法启动的服务不能与Activity进行通讯,而使用bindService()方法启动的服务可以与Activity进行通讯。
从生命周期看,startService()方法启动服务是通过startCommand()方法,而bindService()方法是通过onBind()方法。
通过startService()方法启动的服务,当调用者退出后,服务仍然可以运行,而使用bindService()方法启动的服务则不行。
onCreate()方法在生命周期中只调用一次,若在服务已经启动的前提下,多次调用startService()方法或者调用bindService()方法,都不会再执行onCreate()方法,在使用starService()方法启动服务的情况下,会多次调用onStart()方法。









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

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

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

https://blog.csdn.net/weixin_43495390/article/details/86533482

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!