《Android秘籍.第二卷》

《Android秘籍.第二卷》

目录

《Android秘籍.第二卷》

《Window篇》

Activity、Window、View三者之间的关系?

Activity与Window

View跟Window有什么联系?

Window有哪几种类型?

Activity和Dialog创建过程的异同?

AlertDialog和popupWindow区别

Android UI中的View如何刷新。

《IPC篇》

IPC(Inter-Proscess Communication的缩写,进程间通讯)

Android中有哪些基于Binder的IPC方式?简单对比下?

为何需要进行IPC?多进程通信可能会出现什么问题?

Binder机制

Binder框架中ServiceManager的作用?

Linux现有的所有进程间IPC方式

Android中为何采用Binder来作为主要的IPC方式?

使用Binder进行数据传输的具体过程?

Android中进程和线程的关系?区别?

什么是序列化?Serializable接口和Parcelable接口的区别?为何推荐使用后者?

《AIDL篇》

AIDL和Messenger(信使)的区别:

AIDL和Binder

创建AIDL

 

如何优化多模块都使用AIDL的情况?

《Intent篇》

请描述一下Intent 和 Intent Filter。

Intent传递数据时,可以传递哪些类型数据?

说说Activity,Intent,Service是什么关系 ?

《Handler篇》

谈谈消息机制Hander?作用?有哪些要素?流程是怎样的?

为什么系统不建议在子线程访问UI?

一个Thread可以有几个Looper?几个Handler?

如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

可以在子线程直接new一个Handler吗?那该怎么做?

Message如何创建?哪种效果更好,为什么?

 

ThreadLocal有什么作用?

主线程中Looper的轮询死循环为何没有阻塞主线程?

handler发送消息时有几种方式

post()和postDealy()方法的异同

使用Hanlder的postDealy()后消息队列会发生什么变化?

《线程篇》

线程的生命周期?

死锁是如何发生的,如何避免死锁?

Android中还了解哪些方便线程切换的类?

AsyncTask相比Handler有什么优点?不足呢?

AsyncTask的使用场景以及使用时需要注意的地方和如何关闭。

AsyncTask中使用的线程池大小?

HandlerThread有什么特点?

Handler、Thread、HandlerThread三者的区别

快速实现子线程使用Handler

IntentService的特点?

为何不用bindService方式创建IntentService?

线程池的好处、原理、类型?

ThreadPoolExecutor的工作策略?

什么是ANR?什么情况会出现ANR?如何避免?在不看代码的情况下如何快速定位出现ANR问题所在?

《性能优化篇》

项目中如何做性能优化的?

了解哪些性能优化的工具?

布局上如何优化?列表呢?

内存泄漏是什么?为什么会发生?常见哪些内存泄漏的例子?都是怎么解决的?

内存泄漏和内存溢出的区别?

《NDK篇》

谈谈对Android NDK的理解。


Window篇


Activity、Window、View三者之间的关系?

  • Activity 主要管理生命周期,控制Window. windows 首要负责窗口绘制,负责承载视图(View). view主要是绘制内容,主要用于显示,。(Activity像一个院落(控制单元),Window像院落里的房子(承载模型),View像房子里的房间(显示视图)).
  • Activity要管理View需要通过Window来间接管理的。Window通过addView()、removeView()、updateViewLayout()这三个方法来管理View。即Activity包含了一个PhoneWindow,而PhoneWindow就是继承于Window的,Activity通过setContentView将View设置到了PhoneWindow上。Window的添加过程以及Activity的启动流程都是一次IPC的过程。Activity的启动需要通过AMS完成;Window的添加过程需要通过WindowSession完成。

Activity与Window

  • 每一次创建Activity实例后,接着会调用Activity.attach()来初始化一些内容,而Window对象就是在attach里进行创建初始化赋值的。在attach()里系统会创建Activity所属的Window对象并为其设置回调接口。由于Activity实现了Window的Callback接口,因此当Window接收到外界的状态改变就会回调到Activity的方法。

View跟Window有什么联系?

  • View需要通过Window来展示在Activity上

Window有哪几种类型?

 Framework定义了三种窗口类型,三种类型的定义在WindowManager的LayoutParams中。

  • 应用窗口,所谓的应用窗口是指该窗口对应一个Activity,由于加载Activity是由AmS完成的,因此,对于应用程序来说,要创建一个应用类窗口,只能在Activity内部完成
  • 子窗口,子窗口是指该窗口必须要有一个父窗口,父窗口可以是一个应用类型窗口,也可以是任何其他类型的窗口
  • 系统窗口,系统窗口不需要对应任何Activity,也不需要有父窗口,对于应用程序而言,理论上是无法创建系统窗口的,因为所有的应用程序都没有这个权限,然而系统进程却可以创建系统窗口

(WindowManager为这个三类进行了细化,把每一种类型都有int常量标识,WmS进行窗口叠加的时候会按照该int常量的大小分配不同层,int值越大层位置越靠上面;系统窗口层值(2000-2999)>子窗口(1000-1999)>应用窗口(1-99))

 

Activity和Dialog创建过程的异同?

这个问题一下子不知道从何开始。

AlertDialog和popupWindow区别

  • AlertDialog builder:用来提示用户一些信息,用起来也比较简单,设置标题类容 和按钮即可,如果是加载的自定义的view ,调用 dialog.setView(layout);加载布局即可(其他的设置标题 类容 这些就不需要了)
  • popupWindow:就是一个悬浮在Activity之上的窗口,可以用展示任意布局文件
  • 区别:AlertDialog是非阻塞式对话框:AlertDialog弹出时,后台还可以做事情;而PopupWindow是阻塞式对话框:PopupWindow弹出时,程序会等待,在PopupWindow退出前,程序一直等待,只有当我们调用了dismiss方法的后,PopupWindow退出,程序才会向下执行。并且AlertDialog的位置固定,而PopupWindow的位置可以随意。

Android UI中的View如何刷新。

  • Android中对View的更新方式有很多种,使用时要区分不同的应用场合。要分清的是:多线程和双缓冲。
  • 1、不使用多线程和双缓冲,这种情况最简单,一般只希望View在发生改变时对UI进行重绘。你只需要Activity中显式调用View对象中的invalidate()方法即可。系统会自动调用View的onDraw()方法。
  • 2、使用多线程和不使用双缓冲,这种情况下需要开启新的线程,新开的线程就不好访问View对象了。强行访问的话会报错:android.view.ViewRoot$ CalledFromWrongThreadException: only theoriginal thread that created a view hierarchy can touch its views。
  • 这时候你需要创建一个继承了android.os.handler的子类,并重写handleMessage方法。Android.os.Handle是能发送和处理消息的,你需要在Activity中发出更新UI的消息,然后再你的Handler(可以使用匿名内部类)中处理消息(因为匿名内部类可以访问父类变量,你可以直接调用View对象中的invalidate()方法。也就是说:在新线程中创建并发送一个Message,然后在主线程中捕获、处理该消息。
  • 使用多线程和双缓冲Android的SurfaceView是View的子类,她同时也实现了双缓冲。你可以定义一个她的子类并实现Surfaceholder.Callback接口。由于SurfaceHolder.Callback接口,新线程就不要android.os.Handler帮忙了。SurfaceHolder中lockCanvas()方法可以锁定画布,绘制完新的图像后调用unlockCanvasand Post解锁。

 

《IPC篇》

IPC(Inter-Proscess Communication的缩写,进程间通讯)

  • Android中有多种IPC机制,如AIDL(AIDL:Android Interface Definition Language,即Android接口定义语言),Messenger,Socket,ContentProvider,但是这些机制底层全部都是用了Binder机制来实现。

Android中有哪些基于Binder的IPC方式?简单对比下?

    

名称优点缺点适用场景
 Bundle简单易用只能传输 Bundle 支持的数据类型   四大组件的进程间
文件共享简单易用不适合高并发场景,并且无法做到进程间的即时通信无并发访问情形,交换简单的数据实时性不高的场景
AIDL功能强大,支持一对多并发,支持实时通信

使用稍复杂,需要处理好线程同步

一对多通信且有 RPC 需求
Messenger功能一般,支持一对多串行通信,支持实时通信不能很好处理高并发,只能传输 Bundle 数据低并发一对多
ContentProvider在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其它操作可以理解为受约束的 AIDL一对多的进程间的数据共享
Socket功能强大,可以通过网络传输字节流,支持一对多并发实时通信实现细节稍微有点烦琐,不支持直接的 RPC网络数据交换

为何需要进行IPC?多进程通信可能会出现什么问题?

为了实现安全的数据交互,同步数据等。

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效
  • SharedPreferences的可靠性下降
  • Application会多次创建

Binder机制

Binder是Android系统中的一种IPC进程间通信结构。

Binder的整个设计是C/S结构,客户端进程通过获取服务端进程的代理,并通过向这个代理接口方法中读写数据来完成进程间的数据通信。 ( 通过下面5个步骤,完成了一次Binder通信)

  • 客户端获取服务端的代理对象(proxy)。我们需要明确的是客户端进程并不能直接操作服务端中的方法,如果要操作服务端中的方法,那么有一个可行的解决方法就是在客户端建立一个服务端进程的代理对象,这个代理对象具备和服务端进程一样的功能,要访问服务端进程中的某个方法,只需要访问代理对象中对应的方法即可;
  • 客户端通过调用代理对象向服务端发送请求。
  • 代理对象将用户请求通过Binder驱动发送到服务器进程;
  • 服务端进程处理客户端发过来的请求,处理完之后通过Binder驱动返回处理结果给客户端的服务端代理对象;
  • 代理对象将请求结果进一步返回给客户端进程。

Binder有三部分组成(Client、Server、ServiceManager)

  • Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的;
  • 在Binder通信中,Server进程先注册一些Service到ServiceManager中,ServiceManager负责管理这些Service并向Client提供相关的接口;
  • Client进程要和某一个具体的Service通信,必须先从ServiceManager中获取该Service的相关信息,Client根据得到的Service信息与Service所在的Server进程建立通信,之后Clent就可以与Service进行交互了;
  • Binder驱动程序提供设备文件/dev/binder与用户空间进行交互,Client、Server和ServiceManager通过open和ioctl文件操作函数与Binder驱动程序进行通信;
  • Client、Server、ServiceManager三者之间的交互都是基于Binder通信的,所以通过任意两者这件的关系,都可以解释Binder的机制。

Binder框架中ServiceManager的作用?

Android系统进程间通信机制Binder的总体架构由Client、Server、ServiceManager和驱动程序Binder四个组件构成。

Service Manager是系统中一个独立的进程,它是整个Binder机制的守护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能。

ServiceManager是整个Binder IPC通信过程中的守护进程,本身也是一个Binder服务,但并没有采用libbinder中的多线程模型来与Binder驱动通信,而是自行编写了binder.c直接和Binder驱动来通信,并且只有一个循环binder_loop来进行读取和处理事务,这样的好处是简单而高效。 
ServiceManager本身工作相对并不复杂,主要就两个工作:查询和注册服务。 

Linux现有的所有进程间IPC方式

  • 管道(PIPE):在创建时分配一个page大小的内存,缓存区大小比较有限;
  • 消息队列(Message queues):信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
  • 共享内存(Share Memory):无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
  • 套接字(Socket):作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
  • 信号量(Semaphore):常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 信号(signal): 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;
  • 内存映射(Memory Map):每个使用该机制的进程通过吧同一个共享的文件映射到自己进程地址空间来实现多个进程通信。

Android中为何采用Binder来作为主要的IPC方式?

  • Android的内核也是基于Linux内核 ,首先可以简单说下Linux现有的所有进程间IPC方式。
  • Android之所以选择Binder。 1是安全,每个进程都会被Android系统分配UID和PID,不像传统的在数据里加入UID,这就让那些恶意进程无法直接和其他进程通信,进程间通信的安全性得到提升。 2是高效,像Socket之类的IPC每次数据拷贝都需要2次,而Binder只要1次,在手机这种资源紧张的情况下很重要,3稳定,4从语言层面来说,Binder更适合基于面向对象语言的Android系统

(Linux是基于C语言(面向过程的语言),而Android是基于Java语言(面向对象的语句))

 


使用Binder进行数据传输的具体过程?

 


Android中进程和线程的关系?区别?

  • 进程:一般指一个执行单元,在PC和移动设备上指一个程序或应用(是系统进行资源分配和调度的一个独立单位。可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体,是一个“执行中的程序”。即:进程是程序执行的最小单位)
  • 线程:CPU调度的最小单元。线程是一种有限的系统资源(是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程比进程更小,基本上不拥有系统资源,故对它的调度所用资源小,能更高效的提高系统内多个程序间并发执行的)
  •  两者关系: 一个进程可包含多个线程,即一个应用程序上可以同时执行多个任务。

(注1:Android进程包含五类:前台进程(Foreground process)、可见进程(Visible prcess)、服务进程(Service process)。后台进程(Background process)、空进程(Empty process)。注2:主线程(UI线程):UI操作 有限个子线程:耗时操作 注意:不可在主线程做大量耗时操作,会导致ANR((应用程序无响应(Application Not Responding))


什么是序列化?Serializable接口和Parcelable接口的区别?为何推荐使用后者?

  • 序列化 (Serialization)把Java对象转换为字节序列的过程。(反序列化(deserialization ):把字节序列恢复为Java对象的过程)
  • Serializable是JAVA本身自带的序列化接口,用起来简单但开销较大,使用IO读写存储在硬盘上。序列化过程使用了反射技术,并且期间产生临时对象。优点代码少。
  • Parcelable是Android中的序列化方式,是直接在内存中读写,使用较麻烦但效率很高
  • 两者区别在于存储媒介的不同。建议大家使用Parcelable方式实现序列化,效率很高性能好。

《AIDL篇》

  • AIDL(Android Interface Define Language):(Android 接口定义语言) 是 Android 提供的一种进程间通信 (IPC) 机制,即实现了服务端和客户端的通信。

AIDL和Messenger(信使)的区别:

  • Messenger不适用大量并发的请求:Messenger以串行的方式来处理客户端发来的消息,服务端只能一个个的处理。
  • Messenger主要是为了传递消息:对于需要跨进程调用服务端的方法,这种情景不适用Messenger。
  • Messenger的底层实现是AIDL,系统为我们做了封装从而方便上层的调用。
  • AIDL适用于大量并发的请求,以及涉及到服务端端方法调用的情况

AIDL和Binder

  • 他们都与IPC(远程)调用有关。
  • 本质不同,Binder是一个对象,继承了IBinder对象,你可以借助它来自定义RPC协议。AIDL是android提供的接口定义语言,借助这个工具,你可以很轻松地实现IPC通信机制,根据需要灵活定义接口,
  • 作用范围不同。如果是在一个应用里实现远程调用,使用Binder即可,没必要使用AIDL。如果涉及到在多个应用程序之间使用IPC通信,并且在服务又有多线程业务处理,这时可以使用AIDL

创建AIDL

创建AIDL可以分为三步:

书写 AIDL

  • 创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化
  • 新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件
  • Make project ,生成 Binder 的 Java 文件

编写服务端 (创建一个Service用来监听客户端的链接需求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个接口)

  • 创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法
  • 在 onBind() 中返回

编写客户端 (绑定服务端的Service,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了)

  • 实现 ServiceConnection 接口,在其中拿到 AIDL 类
  • bindService()
  • 调用 AIDL 类中定义好的操作请求 

 


如何优化多模块都使用AIDL的情况?

  • 常规使用AIDL,无非就是一个.aidl对应一个Service,但是当我们的项目很大时,很多模块都需要用到Service,我们总不能为每一个模块都创建一个Service。Service 是四大组件之一,是需要消耗内存的。一直创建就很吓人了。
  • 对于多模块,《Android开发艺术探索》给了一个方法,可用Binder连接池的思想: 
  • 每个业务模块创建自己的AIDL接口并实现此接口,但是不同模块之间不能耦合,所有实现单独开来,然后向服务端提供自己的唯一标识和其相对应的Binder对象,对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口根据业务模块特征返回相应打的Binder对象给客户端,不同业务模块拿到所需的Binder对象后就可以进行远程方法的调用了。由此可见,Binder连接池的作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程

 

《Intent篇》

请描述一下Intent 和 Intent Filter。

  • Intent在Android中被翻译为”意图”,他是三种应用程序基本组件-Activity,Service和broadcast receiver之间相互激活的手段。在调用Intent名称时使用ComponentName也就是类的全名时为显示调用。这种方式一般用于应用程序的内部调用,因为你不一定会知道别人写的类的全名。而Intent Filter是指意图过滤,不出现在代码中,而是出现在android Manifest文件中,以<intent-filter>的形式。(有一个例外是broadcast receiver的intent
  • filter是使用Context.registerReceiver()来动态设定的,其中intent filter也是在代码中创建的)
  • 一个intent有action,data,category等字段。一个隐式intent为了能够被某个intent filter接收,必须通过3个测试,一个intent为了被某个组件接收,则必须通过它所有的intent filter中的一个。

 

Intent传递数据时,可以传递哪些类型数据?

  • intent间传送数据一般有两种常用的方法: 1、extra 2、data。
  • extra可以用Intent.putExtra放入数据。新启动的Activity可用Intent.getExtras取出Bundle,然后用Bundles.getLong,getInt,getBoolean,getString等函数来取放进去的值。
  • Data则是传输url。url可以是指我们熟悉的http,ftp等网络地址,也可以指content来指向ContentProvider提供的资源。Intent.setData可以放入数据,Intent.getData可以取出数据。

 

说说Activity,Intent,Service是什么关系 ?

  • 一个Activity通常是一个单独的屏幕,每一个Activity都被实现为一个单独的类,这些类都是从Activity基类中继承而来的。Activity类会显示由视图控件组成的用户接口,并对视图控件的事件做出响应。
  • Intent的调用是用来进行屏幕之间的切换。Intent描述应用想要做什么。Intent数据结构中两个最重要的部分是动作和动作对应的数据,一个动作对应一个动作数据。
  • Service是运行在后台的代码,不能与用户交互,可以运行在自己的进程里,也可以运行在其他应用程序进程的上下文里。需要一个Activity或者其他Context对象来调用。
  • Activity跳转Activity,Activity启动Service,Service打开Activity都需要Intent表明意图,以及传递参数,Intent是这些组件间信号传递的承载着。

Handler篇


谈谈消息机制Hander?作用?有哪些要素?流程是怎样的?

  • Message:主要功能是进行消息的封装,同时可以指定消息的操作形式; 
  • Looper:消息循环泵,用来为一个线程跑一个消息循环。每一个线程最多只可以拥有一个。 
  • MessageQueue:就是一个消息队列,存放消息的地方。每一个线程最多只可以拥有一个。 
  • Handler:消息的处理者,handler 负责将需要传递的信息封装成Message,发送给Looper,继而由Looper将Message放入MessageQueue中。当Looper对象看到MessageQueue中含有Message,就将其广播出去。该handler 对象收到该消息后,调用相应的handler 对象的handleMessage()方法对其进行处理。 


为什么系统不建议在子线程访问UI?

  • 可能在非UI线程中刷新界面的时候,UI线程(或者其他非UI线程)也在刷新界面,这样就导致多个界面刷新的操作不能同步,导致线程不安全。


一个Thread可以有几个Looper?几个Handler?

  • 一个线程中只能有一个Looper,只能有一个MessageQueue,可以有多个Handler,多个Messge; 
  • 一个Looper只能维护唯一一个MessageQueue,可以接受多个Handler发来的消息; 
  • 一个Message只能属于唯一一个Handler; 
  • 同一个Handler只能处理自己发送给Looper的那些Message;


如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

新建一个Looper,添加looper.prepare和looper.loop即可

特点:可以创建handler,可以拥有自己的消息队列


可以在子线程直接new一个Handler吗?那该怎么做?

不可以,会报错线程中不存在Looper,新建一个Looper,添加looper.prepare和looper.loop即可


Message如何创建?哪种效果更好,为什么?

创建Message对象的时候,有三种方式:

  • Message msg = new Message(); (这种就是直接初始化一个Message对象,没有什么特别的 )
  • Message msg2 = Message.obtain(); (整个Messge池中返回一个新的Message实例,通过obtainMessage能避免重复Message创建对象 )。
  • Message msg3 = handler.obtainMessage(); (第三种最后也是调用的第二种的方法,所以第二种跟第三种其实是一样的,都可以避免重复创建Message对象)
  • 推荐使用后2个方法,可以节省新创建message的内存 。

ThreadLocal有什么作用?

ThreadLocal采用了类似于哈希表的形式轻松实现Looper在线程中的存取,可以在多个线程中互不干扰地存储和修改数据。


主线程中Looper的轮询死循环为何没有阻塞主线程?

卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。

handler发送消息时有几种方式

  • post(Runnable)
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)
  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long) 
  • 以上post类方法允许你排列一个Runnable对象到主线程队列中, sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新

post()和postDealy()方法的异同

  • post()和postDealy都是Handler的方法,用以在子线程种发送Runnabled对象的方法都是将指定Runnable(包装成PostMessage)加入到MessageQueue中,然后Looper不断从MessageQueue中读取Message进行处理。
  • post()方法可以直接在非UI线程更新UI,不同于Handler的Send类方法,需要进行切换。
  • post()和postDealy()在实现UI线程上不同,post()是立即执行,postDealy()延迟执行。


使用Hanlder的postDealy()后消息队列会发生什么变化?

  • postDealy()方法即表示在一段时间后执行新的线程,从而达到特点程序延迟执行的过程。具体是等待多少毫秒以后再将线程加入队列,但是程序后面的代码依然会立即执行,而不是等待多少毫秒执行,所以postDealy不会阻塞线程。变化即到延迟的时间过了之后再把postDelay的消息放进消息队列.

 

 

线程篇

线程的生命周期?

  • NEW:创建状态,线程创建之后,但是还未启动。
  • RUNNABLE:运行状态,处于运行状态的线程,但有可能处于等待状态,例如等待CPU、IO等。
  • WAITING:等待状态,一般是调用了wait()、join()、LockSupport.spark()等方法。
  • TIMED_WAITING:超时等待状态,也就是带时间的等待状态。一般是调用了wait(time)、join(time)、LockSupport.sparkNanos()、LockSupport.sparkUnit()等方法。
  • BLOCKED:阻塞状态,等待锁的释放,例如调用了synchronized增加了锁。
  • TERMINATED:终止状态,一般是线程完成任务后退出或者异常终止。

死锁是如何发生的,如何避免死锁?

  • 当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。


Android中还了解哪些方便线程切换的类?

  • AsyncTask:底层采用了线程池
  • HandlerThread:底层使用了线程
  • IntentService:底层使用了线程


AsyncTask相比Handler有什么优点?不足呢?

  • Handler:主要接受子线程发送的数据,然后根据子线程发送的数据配合主线程更新UI.
  • AsyncTask是一种轻量级的异步任务类方便调用,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。
  • 从实现上来说,AsyncTask封装了Thread和Handler。但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。


AsyncTask的使用场景以及使用时需要注意的地方和如何关闭。

  • 场景:AsyncTask主要场景是需要进行耗时操作且操作完后要更新主线程,或者操作过程中对更新主线程UI.
  • 注意事项:asynctask中维护着一个长度为128的线程池,同时可以执行5个工作线程,还有一个缓冲队列,当线程池中已有128个线程,缓冲队列已满时,如果此时向线程池提交任务,将会抛出ReJectedExcutionException。解决方法就是:由一个控制线程来处理asynctask的调用判断线程是否满了,如果满了则线程睡眠,否则请求asynctask继续处理。
  • 关闭:在activity中调用asynctask对象的cancel()方法,在asynctask的后台任务中。随时使用iscanceled()来判断是否已经cancel任务,如果已经cancel则退出任务。
  • AsyncTask的类必须在主线程中加载,这就意味着第一次访问AsyncTask必须发生在主线程中
  • AsyncTask的对象必须在主线程中创建
  • execute方法必须在UI线程中调用
  • 不能直接在程序中调用着四个方法:
  • onPreExecute():异步任务开启之前回调,在主线程中执行
  • doInBackground():执行异步任务,在线程池中执行
  • onProgressUpdate():当doInBackground中调用publishProgress时回调,在主线程中执行
  • onPostExecute():在异步任务执行之后回调,在主线程中执行
  • onCancelled():在异步任务被取消时回调
  • 一个AsyncTask对象只能执行一次,即只能调用一次excute方法,否则会报运行时异常。


AsyncTask中使用的线程池大小?

  • AsyncTask的执行方法有两种,execute(串行)使用默认线程池、excuteOnexcutor(并行)需要设置线程池。
  • AsyncTask提供了两个全局的线程池:SERIAL_EXECUTOP(同步线程池,一次执行一个任务)、THREAD_POOL_EXECUTOR(异步线程池,一次执行多个)。
  • asynctask中维护着一个长度为128的线程池,同时可以执行5个工作线程
  • corePoolSize(核心池大小)=CPU核心数+1。
  • maximumPoolSize(线程池最大线程数)=2倍的CPU核心数+1。
  • workQueuesize任务队列的容量为128。
  • 核心线程无超时机制,非核心线程在闲置时间的超时时间为1S。
  • 处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务(一般为抛出java.util.concurrent.RejectedExecutionException异常)。

HandlerThread有什么特点?

  • HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,然后在内部直接实现了Looper的实现,这是Handler消息机制必不可少的。有了自己的looper,可以让我们在自己的线程中分发和处理消息。如果不用HandlerThread的话,需要手动去调用Looper.prepare()和Looper.loop()这些方法
  • HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。
  • 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。
    相比多次使用new Thread(){…}.start()这样的方式节省系统资源。
    但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程。
  • 通过设置优先级就可以同步工作顺序的执行,而又不影响UI的初始化;

Handler、Thread、HandlerThread三者的区别

  • Handler:在android中负责发送和处理消息,通过它可以实现其他支线线程与主线程之间的消息通讯。
  • Thread:Java进程中执行运算的最小单位,亦即执行处理机调度的基本单位。某一进程中一路单独运行的程序。
  • HandlerThread:一个继承自Thread的类HandlerThread,Android中没有对Java中的Thread进行任何封装,而是提供了一个继承自Thread的类HandlerThread类,这个类对Java的Thread做了很多便利的封装。


快速实现子线程使用Handler

  • Android的消息机制遵循三个步骤:创建当前线程的Looper、创建当前线程的Handler、调用当前线程Looper对象的loop方法
  • 但是当你statr子线程的时候,虽然子线程的run方法得到执行,但是主线程中代码依然会向下执行,造成空指针的原因是当我们new Handler(childThread.childLooper)的时候,run方法中的Looper对象还没初始化。当然这种情况是随机的,所以造成偶现的崩溃。
  • HandlerThread类start的时候,Looper对象就初始化了,并唤醒之前等待的。所以HandlerThread很好的避免了之前空指针的产生。所以以后要想创建非主线程的Handler时,我们用HandlerThread类提供的Looper对象即可


IntentService的特点?

  • IntentService是Service的子类,比普通的Service增加了额外的功能。优先级比普通的线程高。
  • 先看Service本身存在两个问题:Service不会专门启动一条单独的进程,Service与他所在应用位于同一个进程中。 Service也不是专门一条新进程,因此不应该在Service中直接处理耗时的任务。
  • 特点: IntentService会创建独立的worker线程来处理所有的Intent请求; 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程的问题;其内部也是封装了Handler。 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service; 为Service的onBind()提供默认实现,返回null; 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;


为何不用bindService方式创建IntentService?

  • IntentService本身设计就不支持bind操作。查看IntentService源码,其中的onBind()函数被实现,而且返回null。这从侧面就证明了以上结论。再者,IntentService本身就是异步的,本身就不能确定是否在activity销毁后还是否执行,如果用bind的话,activity销毁的时候,IntentService还在执行任务的话就很矛盾了。


线程池的好处、原理、类型?

优点:

  • 重用存在的线程,减少对象创建、消亡的开销,提升性能。
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
  • 提供定时执行、定期执行、单线程、并发数控制等功能

原理:

  • 就是一个线程集合workerSet和一个阻塞队列workQueue。当用户向线程池提交一个任务(也就是线程)时,线程池会先将任务放入workQueue中。workerSet中的线程会不断的从workQueue中获取线程然后执行。当workQueue中没有任务的时候,worker就会阻塞,直到队列中有任务了就取出来继续执行 。

类型:

  • newCachedThreadPool(根据需要创建新线程的线程池)
  • newFixedThreadPool(指定工作线程数量的线程池)
  • newScheduledThreadPool(可安排在给定延迟后运行命令或者定期地执行)
  • newSingleThreadExecutor(一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程)

ThreadPoolExecutor的工作策略?

  • 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
  • 如果线程池中的线程数量已经达到或超过了核心线程的数量,那么任务会进入任务队列中排队等待。
  • 如果在上一环节中无法将任务插入到任务队列,这往往是因为任务队列已满,这时候如果线程数量未达到线程池规定的最大值,那么会立即启动一个非核心线程来执行任务。
  • 如果上一环节中线程的数量已经达到了线程池规定的最大值,那么就拒绝执行此任务。


什么是ANR?什么情况会出现ANR?如何避免?在不看代码的情况下如何快速定位出现ANR问题所在?

  • Application Not Responding(应用程序无响应),即应用程序5秒内没有反应就会提示应用程序无响应,应避免在主线程中进行耗时操作。

 

性能优化篇


项目中如何做性能优化的?

 

  • 避免创建不必要的对象 (首先分配内存本身需要时间,其次虚拟机运行时堆内存使用量是有上限的,当使用量到达一定程度时会触发垃圾回收,垃圾回收会使得线程甚至是整个进程暂停运行。如果有对象频繁的创建和销毁,或者内存使用率很高,就会造成应用程序严重卡顿) 
  • 合理使用static成员 (如果一个方法不需要操作运行时的动态变量和方法,那么可以将方法设置为static的。常量字段要声明为“static final”,因为这样常量会被存放在dex文件的静态字段初始化器中被直接访问,否则在运行时需要通过编译时自动生成的一些函数来初始化。此规则只对基本类型和String类型有效。不要将视图控件声明为static,因为View对象会引用Activity对象,当Activity退出时其对象本身无法被销毁,会造成内存溢出) 
  • 避免内部的Getters/Setters (Android开发中限于硬件条件,除非字段需要被公开访问,否则如果只是有限范围内的内部访问(例如包内访问)则不建议使用Getters/Setters。在开启JIT(just in time,即时编译技术,可以加速java程序的运行速度),直接访问的速度比间接访问要快7倍)
  • 使用增强for循环 (优先使用增强for循环通常情况下会获得更高的效率;ArrayList进行遍历时,使用普通的for循环效率要更高)
  • 使用public代替private以便私有内部类高效访问外部类成员   
  • 合理使用浮点类型 (在Android设备中浮点型大概比整型数据处理速度慢两倍,另外一些处理器有硬件乘法但是没有除法,这种情况下除法和取模运算是用软件实现的。为了提高效率,在写运算式时可以考虑将一些除法操作直接改写为乘法实现) 
  • 采用<merge>优化布局层数。 采用<include>来共享布局。 
  • 延时加载View. 采用ViewStub 避免一些不经常的视图长期被引用,占用内存. 
  • 移除Activity默认背景,提升activity加载速度.(Activity中使用不透明的背景,那么可以移除Activity的默认背景。getWindow().setBackgroundDrawable(null))
  • cursor 的使用。(不要每次打开关闭cursor.因为打开关闭Cursor非常耗时。不再使用的cursor要记得关闭(一般在finally语句块执行)。在CursorAdapter中应用的情况,我们不能直接将Cursor关闭掉但是注意,CursorAdapter在Acivity结束时并没有自动的将Cursor关闭掉,因此,你需要在onDestroy函数中,手动关闭)
  • 广播BroadCast动态注册时,记得要在调用者生命周期结束时unregisterReceiver,防止内存泄漏。 
  • 针对ListView的性能优化 (item尽可能的减少使用的控件和布局的层次;背景色与cacheColorHint设置相同颜色;ListView中item的布局至关重要,必须尽可能的减少使用的控件,布局。RelativeLayout是绝对的利器,通过它可以减少布局的层次。同时要尽可能的复用控件,这样可以减少ListView的内存使用,减少滑动时GC次数。ListView的背景色与cacheColorHint设置相同颜色,可以提高滑动时的渲染性能。ListView中getView是性能是关键,这里要尽可能的优化。getView方法中要重用view;getView方法中不能做复杂的逻辑计算,特别是数据库操作,否则会严重影响滑动时的性能;ListView数据项较多时考虑分页加载)
  • 注意使用线程的同步机制(synchronized) (防止多个线程同时访问一个对象时发生异常。) 
  • 合理使用StringBuffer,StringBuilder,String (在简单的字符串拼接中,String的效率是最高的但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,这里就要求使用StringBuilder了.在单线程中StringBuilder的性能要比StringBuffer高。多线程为了线程安全需要采用StringBuffer,因为它是同步的。常规下一般用StringBuilder) 
  • 尽量使用局部变量 (调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化)
  • I/O流操作记得及时关闭流对象。 
  • 使用IntentService代替Service (IntentService和Service都是一个服务,区别在于IntentService使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent(在onHandleIntent方法中),对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程,如果有耗时的操作与其在Service里面开启新线程还不如使用IntentService来处理耗时操作 )
  • 使用Application Context代替Activity中的Context (不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的 ,对于生命周期长的对象,可以使用Application Context,不要把Context对象设置为静态)
  • 集合中的对象要及时清理 (我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了)
  • Bitmap的使用 (较大的Bitmap注意压缩后再使用,加载高清大图可以考虑BitmapRegionDecoder的使用,Bitmap注意及时回收recycle())
  • 巧妙的运用软引用(SoftRefrence)(有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。)
  • 尽量不要使用整张的大图作为资源文件,尽量使用9path图片 (应用图标优先放在mipmap目录下,其他资源图,.9图应该放在drawable-xxxx下,需要复制到手机sd卡上使用的应放在asset目录 )
  • 了解并使用库函数 (Java标准库和Android Framework中包含了大量高效且健壮的库函数,很多函数还采用了native实现,通常情况下比我们用Java实现同样功能的代码的效率要高很多。所以善于使用系统库函数可以节省开发时间,并且也不容易出错。 )
  • WebView (在Activity或者Fragment销毁时记得把WebView也销毁)

 


了解哪些性能优化的工具?

  • Android Studio 自带的内存分析工具Memory,可以很方便的查看应用内存情况,实时地显示出内存占用情况,内存泄漏发生时的主要表现为内存抖动,可用内存慢慢变少,我们可以根据实际情况分析,从而使用leakCanary等工具进一步去分析内存泄漏和OOM等问题.
  • LeakCanary LeakCanary是Square开源的一个内存泄露自动探测神器,它是一个Android和Java的内存泄露检测库,可以大幅度减少了开发中遇到的OOM问题。 使用方法也很简单,只需在Application中进行初始化,然后在需要检测内存泄漏的地方进行watch即可.
  • TraceView TraceView是从每个方法运行的时间的角度来分析应用的性能.


布局上如何优化?列表呢?

  • 采用<merge>优化布局层数。 采用<include>来共享布局。 
  • 延时加载View. 采用ViewStub 避免一些不经常的视图长期被引用,占用内存. 
  • 复用convertView,对convetView进行判空,当convertView不为空时重复使用,为空则初始化,从而减少了很多不必要的View的创建、减少findViewById的次数,
  • 避免在getView方法中做耗时操作
  • 设置图片缓存
  • 采用ViewHolder模式缓存item条目的引用,避免了每次在调用getView的时候都去通过findViewById实例化数据
  • 给listView设置滚动监听器 根据不同状态 不同处理数据 分批分页加载 根据listView的状态去操作,比如当列表快速滑动时不去开启大量的异步任务去请求图片
  • listview每个item层级结构不要太复杂,item中异步加载图片,并对图片加载做优化,同时item中不要创建线程。
  • 尽量能保证 Adapter 的 hasStableIds() 返回 true 这样在 notifyDataSetChanged() 的时候,如果item内容并没有变化,ListView 将不会重新绘制这个 View,达到优化的目的
  • 在一些场景中,ScollView内会包含多个ListView,可以把listview的高度写死固定下来。 由于ScollView在快速滑动过程中需要大量计算每一个listview的高度,阻塞了UI线程导致卡顿现象出现,如果我们每一个item的高度都是均匀的,可以通过计算把listview的高度确定下来,避免卡顿现象出现
  • 使用 RecyclerView 代替listview: 每个item内容的变动,listview都需要去调用notifyDataSetChanged来更新全部的item,太浪费性能了。RecyclerView可以实现当个item的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
  • ListView 中元素避免半透明: 半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。 在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
  • (注:ListVies和ScrollView兼容问题:方法一,重写ListView, 覆盖onMeasure()方法,方法二:动态设置ListView的高度,方法三,在xml文件中,直接将Listview的高度写死。方法四,避免ScrollView嵌套Listview)


内存泄漏是什么?为什么会发生?常见哪些内存泄漏的例子?都是怎么解决的?

  • 原因:生命周期较长的对象持有生命周期较短的对象的引用。即某个对象已经不需要再用了,但是它却没有被系统所回收,一直在内存中占用着空间,而导致它无法被回收。还有一些其他的会导致内存泄漏的情况,比如 BraodcastReceiver 未取消注册,InputStream 未关闭等
  • 实例:如果一个 Activity 被一个单例对象所引用,那么当退出这个 Activity 时,由于单例的对象依然存在(单例对象的生命周期跟整个 App 的生命周期一致),而单例对象又持有 Activity 的引用,这就导致了此 Activity 无法被回收,从而造成内存泄漏。
  • 多见于无限循环的动画和handler中没有及时清空message队列,在Activity的destroy()方法中关闭动画或清空队列即可


内存泄漏和内存溢出的区别?

  • 内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。 
  • 内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。 
  • 内存泄漏的堆积最终会导致内存溢出
  • 内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。
  • 内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。
  • 内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错,

 

《NDK篇》

谈谈对Android NDK的理解。

  • android NDK是一套工具,允许Android应用开发者嵌入从C、C++源代码编译来的本地机器代码到各自的应用软件包中。
  • 1、 NDK是一系列工具的集合。
  • NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者帮助时巨大的。
  • NDK集成了交叉编辑器,并提供了相应的mk文件隔离CPU、平台、API等差异,开发人员只需要简单修改mk文件(指出“那些文件需要编译”、“编译特性要求”等),就可以创建出so。NDK可以自动将so和Java应用一起打包,极大的减轻了开发人员的打包工作。
  • 2、NDK提供了一份稳定、功能有限的API头文件声明。这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、log库(liblog)。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值