Android面试总结

面试大纲

  • java基础、面向对象、集合、线程使用;
  • Android 机型适配、SDK适配、内存优化、内存溢出、内存泄漏;
  • MVC/MVP/MVVM的使用场景;
  • 了解并能使用最新流行开源库RXjava+Retrofit+OKHttp、Glide、EventBus等;
  • JNI及NDK的使用、熟悉Framework。

频繁面试题

  • 1.面向对象(OOP)
  • 2.数据类型
  • 3.equal和==的区别
  • 4.字符串运行速度:StringBuilder、StringBuffer、 String
  • 5.Queue 队列、Stack 栈
  • 6.ListView机制
  • 7.Fragment生命周期
  • 8.四大引用类型
  • 9.线程、进程及其通信方式
  • 10.PackageManagerService
  • 11.binder机制
  • 12.launcher的实现
  • 13.Android 版本特性
  • 14.BroadcastReceiver广播
  • 15.RecycleView
  • 16.各种集合比较SparseArray、HashMap、ArrayMap、LinkedList、ArrayList
  • 17.SQLite升级
  • 18.Bitmap
  • 19.Handler机制
  • 20.性能优化技巧
  • 21.ANR
  • 22.自定义View
  • 23.MVC、MVP、MVVM三种架构设计
  • 24.APP 启动流程
  • 25.插件化
  • 26.Kotlin 多平台应用的静态编程语言
  • 27.Framework 工作原理
  • 28.Android 屏幕适配
  • 29.事件分发机制
  • 30.Android 动画分类:视图,属性,帧,gif
  • 31.Android 进程
  • 32.Android 五大存储方式
  • 33.屏幕旋转Activity生命周期
  • 34.Activity 四大启动模式
  • 35.CPU、GPU工作原理
  • 36.UI卡顿原因
  • 37.Application的生命周期
  • 38.如何避免因引入的开源库导致的安全性和稳定性?
  • 39.简单的音频/视频格式
  • 40.线程同步的方法
  • 41.线程安全的单例
  • 42.Serializable、Parcelelable
  • 43.Service启动方式和生命周期
  • 44.Android 常见布局
  • 45.Http、Https、Volley、OkHttp、RxJava + Retrofit + OkHttp、TCP、UDP
  • 46.如何节省内存使用,主动回收内存?
  • 47.Activity如何生成View?
  • 48.为什么要使用多线程?
  • 49.现场保护
  • 50.内存溢出、内存泄漏
  • 51.JNI和NDK
  • 52.常用的设计模式及其实现思想和作用
  • 53.HashMap、 HashTable、HashSet的异同
  • 54.Android 中内存泄漏原因及优化方案
  • 55.LeakCanary 内存泄漏检查的开源工具
  • 56.多线程、线程池
  • 57.腾讯 Bugly
  • 58.Glide
  • 59.Fresco
  • 60.React

面试题解析

1.面向对象(OOP)

针对业务处理过程的实体及其属性和行为进行抽象封装以获得高效清晰的逻辑单元划分。

三大特性:①封装(隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变量隔离便于使用,提高代码的服用小和安全性);②继承(提高代码的复用性,继承是多态的前提);③多态(父类或接口定义的引用变量可以指向子类或具体实现类的形象,提高了程序的拓展性)。

五大原则:单一职责、开放封闭、里氏替换、依赖倒置、接口分离。

2.数据类型

byte 1个字符,short 、char2 个字符,int 、float 4个字符,long 、double 8个字符。**

volatile是一个类型修饰符(type specifier)volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。(from百度)

volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。

在JDK1.7之前,switch语句仅支持byte、short、char、int,在JDK1.7之后,枚举、字符串类型都可以,通过String.hashcode转成int进行判断。

String不可变原因:字符串常量池的需要;运行String对象缓存HashCode,提高效率;多线程安全。

String 转integer的方法和原理

Integer.parseInt(String str)调用Integer内部的;

②Integer.parseInt(String s, int radix)parseInt内部首先判断字符串是否包含符号(-或者+),则对相应的negative和limit进行赋值,然后再循环字符串,对单个char进行数值计算

③Character.digit(char ch, int radix) 返回指定基数中字符表示的数值。函数肯定进入到0-9字符的判断(相对于string转换到int),否则会抛出异常,数字就是如上面进行拼接然后生成的int类型数值。

限定参数类型的上界:参数类型必须是T或T的子类型

<? super T> 限定参数类型的下界:参数类型必须是T或T的超类型

3.equal和==的区别:equal 比较对象,==比较原生类型

equal:存储空间的值是否相同,字符串内容比较,值是否相同

==:是否为同一内存空间,内存空间是否相同,引用是否相同

如果一个类重写了equals()方法,则一定也要重写hashCode()方法,原因是:虽然equals()方法重写可以保证正确判断两个对象在逻辑是否相同,但是hashCode()方法映射的物理地址是不相同的,依然会将逻辑上相同的两个元素存入集合,但是第二个对象的内容会是Null.

参考文章:关于java中Object类中的equals()和hashCode()方法的使用个人总结 - 张森(ZhangSen) - 博客园

4.字符串运行速度:StringBuilder > StringBuffer (线程安全)> String ,String为常量,其它为变量,所以运行慢。

5.Queue 队列:先进先出,Stack 栈:后进先出。Collect –>List/Set/Map

6.ListView机制:用到的适配器有ArrayAdapter、SimpleAdapter、BaseAdapter

BaseAdapter 重写的方法getCount()、getItem()、getItemId()、getView(),每绘制一次就调用一次getView(),在getView()中将事先定好的layout布局确定显示的效果并返回一个view对象作为一个item 显示出来,getItem()、getItemId()在调用LIstView响应方法时调用。

7.Fragment生命周期

参考文章:Fragment各种情况的生命周期 - 猫吻鱼的博客 - CSDN博客

onAttach(): 完成Fragment和Activity的绑定,参数中的Activity即为要绑定的Activity,可以进行赋值等操作。

onCreate() : 完成Fragment的初始化

onCreateView() : 加载Fragment布局,绑定布局文件

onActivityCreated() : 表名与Fragment绑定的Activity已经执行完成了onCreate,可以与Activity进行交互操作。

onStart() : Fragment变为可见状态

onResume() : Fragment变为可交互状态

onPause(): Fragment变为不可交互状态(不代表不可见)

onSaveInstanceState():保存当前Fragment的状态。记录一些数据,比如EditText键入的文本,即使Fragment被回收又重新创建,一样能恢复EditText之前键入的文本。

onStop(): Fragment变为不可见状态

onDestroyView() : 销毁Fragment的有关视图,但并未和Activity解绑,可以通过onCreateView()重新创建视图。Fragment销毁时或者ViewPager+Fragment情况下会调用

onDestroy() : 销毁Fragment时调用

onDetach() : 解除和Activity的绑定。Fragmen销毁最后一步。

8.四大引用类型

强引用: 是指创建一个对象并把这个对象赋给一个引用变量。 强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

软引用(SoftReference):如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。

弱引用(WeakReference):弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。

虚引用(PhantomReference):虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

对于强引用,我们平时在编写代码时经常会用到。而对于其他三种类型的引用,使用得最多的就是软引用和弱引用,这2种既有相似之处又有区别。它们都是用来描述非必需对象的,但是被软引用关联的对象只有在内存不足时才会被回收,而被弱引用关联的对象在JVM进行垃圾回收时总会被回收。

9.线程是进程的子集,一个进程可有多个线程。

线程间通讯方式:①共享变量;②管道;③handler;④runOnUiThread(Runnable);⑤view.post(Runnable)。

进程间通讯方式:①管道;②FIFO;③消息队列;④信号量;⑤共享内存区;⑥套接字socket信号。

Activity间的通信方式:①Intent;②借助类的静态变量;③借助全局变量/Application;④借助外部工具(SharedPreference、SQLite、File、剪贴板);⑤借助Service。

10.PackageManagerService APP安装有关的service,WindowManagerService APP调用窗口相关的service,ActivityManagerService 系统的引导服务,支持应用进程的启动、切换、调度、四大组件的启动和管理。

自定义系统服务:在ServiceManager注册,在Framework/base增加JNI,C++ 文件,在build 配置mk文件,全部编译,才可调用。

11.binder机制

Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。正如其名“粘合剂”一样,它把系统中各个组件粘合到了一起,是各个组件的桥梁

12.launcher的实现

Manifest 配置launcher,PackageManager、ActivityManager对应包的管理和应用进程的管理

13.Android 版本特性

6.0需要代码请求权限checkPermissions,7.0应用间文件共享限制,系统广播删除,8.0通知渠道、悬浮窗、透明窗口不允许屏幕旋转,9.0明文流量的网络请求(Https加密)

Android SDK兼容minSdkVersion必须,targetSdkVersion针对某版本,maxSdkVersion非必需。

详细特性可见转载文档:Android 各版本新特性介绍 - 落叶Ex的博客 - CSDN博客

14.BroadcastReceiver广播

BroadcastReceiver比较

特别注意:动态广播最好在Activity 的 onResume()注册、onPause()注销。

原因:对于动态广播,有注册就必然得有注销,否则会导致内存泄露,重复注册、重复注销也不允许

广播的类型主要分为5类:

普通广播(Normal Broadcast):开发者自身定义 intent的广播(最常用),sendBroadcast(intent);

系统广播(System Broadcast):涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播,每个广播都有特定的Intent - Filter(包括具体的action)

有序广播(Ordered Broadcast):发送出去的广播被广播接收者按照先后顺序接收,按照Priority属性值从大-小排序;Priority属性相同者,动态注册的广播优先

sendOrderedBroadcast(intent);

特点

接收广播按顺序接收

先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;

先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播

App应用内广播(Local Broadcast):Android高效安全的本地广播LocalBroadcast完全解析 - 简书

粘性广播(Sticky Broadcast):由于在Android5.0 & API 21中已经失效,所以不建议使用

15.RecycleView

方法:onCreateViewHolder() 、onBinderViewHolder()、getItemCount()

三种布局:垂直or水平、网格、瀑布流

需要自定义分割线、易于回收、View复用、便于实现添加和删除item动画。

16.各种集合比较

SparseArray稀疏数组与HashMap相比,正序插入快,逆序插入慢,查找慢占用内存少于HashMap;

HashMap和ArrayMap的区别

①查找效率 HashMap依据HashCode查找,效率增加;ArrayMap使用二分法查找,效率下降。数量大时用HashMap

②扩展数量 HashMap初始值16个长度,每次扩容申请双倍的数组空间;A扩容申请空间更少

③扩容效率 ArrayMap更好

④内存消耗 数据量小时,ArrayMap更节省内存

总结:数据量小时,并需要频繁使用map存储时,用ArrayMap,数据量大时,用HashMap。

HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

HashMap 的实例有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。在Java编程语言中,加载因子默认值为0.75,默认哈希表元为101

hashMap的默认加载因子为0.75,加载因子表示Hsah表中元素的填满的程度。加载因子越大,填满的元素越多,空间利用率越高,但冲突的机会加大了。反之,加载因子越小,填满的元素越少,冲突的机会减小,但空间浪费多了。冲突的机会越大,则查找的成本越高。反之,查找的成本越小。当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。

参考文章:为什么java Hashmap 中的加载因子是默认为0.75 - 简书

LinkedList 链表结构,查找慢,插入快;

ArrayList 数组结构,查找快,插入慢。

17.SQLite升级而不影响现有数据,DBHelper单例,在OnUpgrade()方法中判断oldVersion对数据库进行增删改查以实现数据库升级。

18.Bitmap

参考文章:Android Bitmap详解 - 小爷宋 - 博客园

位图包括图片的像素、长宽、颜色等描述,可通过这些信息计算出图像占用内存的大小。作为花架,可对图片做一些处理,位图文件显示效果好,但是非压缩格式,需要占用较大存储空间。

Config:表示图片像素类型

②三种压缩格式:Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP

BitmapFactory提供了四类加载方法:decodeFile、decodeResource、decodeStream、decodeByteArray。巨图加载:BitmapRegionDecoder,可以按照区域进行加载。高效加载:核心其实也很简单,主要是采样压缩、缓存策略、异步加载等

内存优化:缓存LRU、缩放、Config、Compress选择、内存管理、缓存方式等等方面入手。、内存管理、内存优化、缩放、config、compress

开源框架:ImageLoader、Glide(google)、Fresco(FaceBook)、Picasso(Square)

图片优化:异步加载,压缩处理bitmapFactory.options,设置内存大小,缓存于内存、SD卡,没有内存再从网络取

Picasso包体积小、清晰,但功能有局限不能加载gif、只能缓存全尺寸;

Glide功能全面,擅长大型图片流,体积较大;

Fresco内存优化,减少oom,体积更大。

如何处理大图:BitmapFactory.Options,把inJustDecodeBounds这个属性设为true,计算inSampleSize。参考文章:官方推荐方法,如何有效率的加载大图Bitmap - 月毛毛的专栏 - CSDN博客

19.Handler机制

主线程不能进行耗时操作,子线程不能更新UI,Handler实现线程间通信,将要发送的消息保存到Message中,Handler调用sendMessage()方法将message发送到MessageQueue,Looper对象不断调用loop()方法,不断从MessageQueue中取出message交给handler处理,从而实现线程间的通信。

主线程handler不需要调用Looper.prepare(),Looper.loop(),通过sendMessage将message添加到messagequeue。

子线程可以new Handler。

总结:当创建Handler时将通过ThreadLocal在当前线程绑定一个Looper对象,而Looper持有MessageQueue对象。执行Handler.sendMessage(Message)方法将一个待处理的Message插入到MessageQueue中,这时候通过Looper.loop()方法获取到队列中Message,然后再交由Handler.handleMessage(Message)来处理。

20.性能优化技巧

启动速度优化,布局优化,内存、电量、APP大小优化、列表滑动优化等等。

性能优化工具:TraceView、Hierarchy Viewer。

21.ANR(Application Not Responding)

扩展文链接:深入理解ANR - 简书

Android应用程序有一段时间响应不够灵敏,系统会向用户显示应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择“等待”而让程序继续运行,也可以选择“强制关闭”。所以一个流畅的合理的应用程序中不能出现ANR,而让用户每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样系统不会显示ANR给用户。默认情况下,在android中Activity的最长执行时间是5秒,BroadcastReceiver的最长执行时间则是10秒。

原因:主线程做耗时操作;主线程被其他线程锁;CPU被其他进程占用,该进程没有分配CPU资源;OnReceiver过多操作,IO操作,如数据库、文件、网络

22.自定义View

扩展文链接:Android自定义View全解 - 简书

自定义组合控件 多个控件组合成为一个新的控件,方便多处复用

继承系统View控件 继承自TextView等系统控件,在系统控件的基础功能上进行扩展

继承View 不复用系统控件逻辑,继承View进行功能定义

继承系统ViewGroup 继承自LinearLayout等系统控件,在系统控件的基础功能上进行扩展

继承ViewViewGroup 不复用系统控件逻辑,继承ViewGroup进行功能定义

View绘制流程基本由measure()、layout()、draw()这个三个函数完成

函数作用相关方法

measure()测量View的宽高measure(),setMeasuredDimension(),onMeasure()

layout()计算当前View以及子View的位置layout(),onLayout(),setFrame()

draw()视图的绘制工作draw(),onDraw()

自定义View的注意事项 参考文章:Android自定义View注意事项 - 简书

①需要在onMeasure方法中处理wrap_content的方法,让View支持wrap_content;

②避免padding和子元素的margin失效,让View支持padding;

③尽量不要在View中使用Handler,View中已提供了post系列方法,可替代Handler作用;

④避免造成内存泄漏,View中如果有线程或者动画,需要及时停止。

23.MVC、MVP、MVVM三种架构设计

扩展文链接:Android高精战争(MVC、MVP、MVVM) - lihaoxiang123的博客 - CSDN博客

MVC全名是Model View Controller,如图,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。

MVP从更早的MVC框架演变过来,与MVC有一定的相似性:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理,Model提供数据。在MVP模式里通常包含3个要素(加上View interface是4个):

View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)

Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)

Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。

View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

MVC 与MVP的区别

(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互

通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View

Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。

在MVP中,Activity的代码不臃肿;

在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;

在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;

在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。

MVVM可以算是MVP的升级版,其中的VM是ViewModel的缩写,ViewModel可以理解成是View的数据模型和Presenter的合体,ViewModel和View之间的交互通过Data Binding完成,而Data Binding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

24.APP 启动流程

点击桌面图标,launcher进程启动主Activity以Binder方式发送给AMS服务,交付给ActivityManagerService处理Intent和flag信息,通过prepareMainLooper()方法loop处理消息

25.插件化

解除代码耦合,插件支持热插拔,静默升级,从根本上解决65k属性和方法的bug,进行自定义classLoader。

插件化和热修复都是动态加载技术,使用场景不同,热修复为解决线上问题或者小功能更新,插件化解决应用上的大问题。

组件化:为了解耦,把复杂系统拆分成多个组件,分离组件边界和责任,便于独立升级和维护。

26.Kotlin 是一个用于现代多平台应用的静态编程语言,由 JetBrains 开发。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。Kotlin已正式成为Android官方支持开发语言。

React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的JS框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。 flutter。

27.Framework 工作原理

Android 系统对Linux、kernel、lib库等封装,提供WMS、AMS、binder机制,handler-message机制等方式,供APP使用。Framework 就是提供APP生存环境。

28.Android 屏幕适配

各种dp文件,使用各种尺寸屏幕

布局文件中view设置高宽时不限定大小,尽量使用wrap_content,match_parent;代码中设置高宽前可获取屏幕大小,如果是线性布局可设置view在LinearLayout的weight;单位dp适配屏幕,单位sp适用字体,多图片,多布局。

29.事件分发机制

dispatchTouchEvent() 负责事件分发。当点击事件产生后,事件首先传递给当前Activity,调用Activity的dispatchTouchEvent()方法,返回值为true则表示View或子View消费了此事件,如果返回true,则表示没有消费事件,并调用父View的onTouchEvent方法。

onTouchEvent()用于处理事件,返回值决定当前控件是否消费了这个事件,也就是说在当前控件在调用父View的onTouchEvent方法完Touch事件后,是否还允许Touch事件继续向上(父控件)传递,一但返回True,则父控件不用操心自己来处理Touch事件。返回true,则向上传递给父控件。

onInterceptTouchEvent() ViewGroup的一个方法,用于处理事件(类似于预处理,当然也可以不处理)并改变事件的传递方向,也就是决定是否允许Touch事件继续向下(子控件)传递,一但返回True(代表事件在当前的viewGroup中会被处理),则向下传递之路被截断(所有子控件将没有机会参与Touch事件),同时把事件传递给当前的控件的onTouchEvent()处理;返回false,则把事件交给子控件的onInterceptTouchEvent()。

事件分发机制原理图

当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。

小结:onInterceptTouchEvent()默认返回false,不做截获。返回true之后,事件流的后端控件就没有机会处理touch事件。view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理,如果onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

30.Android 动画分类:视图,属性,帧,gif。

31.Android 进程

一般大体分为前台进程,后台进程,可见进程,服务进程,空进程这五大进程。其中空进程优先级最低,调用startService()让service所在进程成为前台进程,service的onDestory()里重新启动自己可避免后台进程被杀死。

一个应用允许多个进程,在清单文件配置的service为一个进程,Android:process就可以配置;

多进程会引起的异常:静态成员和单例模式会失效,线程同步机制完全失效,SharedPreferences可靠性下降,Application会多次创建。

32.Android 五大存储方式

使用SharedPreferences存储数据; 文件存储数据;SQLite数据库存储数据;使用ContentProvider存储数据;网络存储数据。

当APP没有获取文件存储权限时,当需要存储大文件时,可以保存在APP-data-cache目录里。

ContentProvider:抽象类,为不同应用数据提供数据共享,提供统一接口,通过uri标识要访问的数据。

33.屏幕旋转Activity生命周期

参考文章链接:Activity横竖屏切换生命周期变化 - 简书

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行1次,切竖屏时会执行1次

2、设置Activity的android:configChanges=”orientation”时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次

3、设置Activity的android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

4、设置Activity的android:configChanges=”orientation|keyboardHidden|screenSize”时,切屏切记要加上screenSize,否则4.0版本以上生命周期不生效

34.Activity 四大启动模式

standard 启动模式

Activity 默认的启动模式,每次 startActivity 都会在栈顶创建一个新的实例,在同一个任务中可以存在多个Activity 的实例。

singleTop 启动模式

栈顶复用,也就是说,要启动 singleTop 模式的 Activity,如果它恰好在当前栈顶,那么直接复用,执行其 onNewIntent 方法。否则,就重新创建一个实例入栈。

singleTask 启动模式

在系统中只有一个实例,当再次启动该 Activity 时,会重用已存在的任务和实例,并且会调用这个实例的 onNewIntent()方法,将 Intent 实例传递到该实例中。

singleInstance 启动模式

总是在新的任务中开启,并且这个新的任务中有且只有这一个实例,也就是说被该实例启动的其他 Activity 会自动运行于另一个任务中。当再次启动该 Activity 的实例时,会重用已存在的任务和实例。并且会调用这个实例的onNewIntent()方法,将 Intent 实例传递到该实例中。

总结:①standard每一次都会创建新的实例;②singleTop栈顶复用。和standard相似,但是如果栈顶已有实例,复用该实例,回调onNewIntent()方法;③singleTask栈内复用。查找栈内有没有该实例,有则复用回调onNewIntent()方法,如果没有,新建Activity,并入栈;④singleInstance单例模式,全局唯一。具备singleTask所有特性,独占一个任务栈。

35.CPU是一个有多功能的优秀领导者,它的优点在于调度、管理、协调能力强,计算能力则位于其次,而GPU相当于一个能接受CPU调度的“拥有强大计算能力”的员工,GPU提供了多核并行计算的基础结构,且核心数非常多,可支撑大量数据的并行操作,拥有更高的访存速度,更高的浮点运算能力。

36.UI卡顿原因

每16ms绘制一次Activity,如果由于一些原因导致了我们的逻辑、CPU耗时、GPU耗时大于16ms(应用卡顿的根源就在于16ms内不能完成绘制渲染合成过程,16ms需要完成视图树的所有测量、布局、绘制渲染及合成),UI就无法完成一次绘制,那么就会造成卡顿。①内存抖动问题,②方法耗时,③view本身卡顿。

解决办法:修改方法,使其不耗时,放到子线程中,如网络访问,大文件操作等,防止ANR,避免GPU过度绘制。

37.Application的生命周期

参考文章:Android中Application的用途及生命周期_YY小爬虫_新浪博客

①onCreate0 在创建应用程序时创建;

②onTerminate() 在模拟环境下执行。当终止应用程序对象时调用,不保证一定被调用,当程序是被内核终止以便为其他应用程序释放资源,那么将不会提醒,并且不调用应用程序的对象的onTerminate方法而直接终止进程;

③onLowMemory() 低内存时执行。好的应用程序一般会在这个方法里面释放一些不必要的资源来应付当后台程序已经终止,前台应用程序内存还不够时的情况;

④onConfigurationChanged(Configuration newConfig) 配置改变时触发这个方法。

⑤onTrimMemory(int level) 程序在进行内存清理时执行。

38.如何避免因引入的开源库导致的安全性和稳定性?

由于项目引入了太多第三方开源库,Android APP有65536方法数的问题,可使用multidex解决。Android Methods Count插件可以高效统计Android开源库的方法数。

39.简单的音频/视频格式

PCM脉冲编码调制,由二进制数字信号对光源进行通断调制产生,没有压缩的编码方式。

WAV无损音频文件格式,PCM是无损WAV文件中音频数据的一种编码方式,但是WAV还可以用其它编码。

AVI:音视频交错,调用方便,图像质量好,压缩标准可选。

WMV:可扩充的媒体类型,本地或网络回放,流优先级化。

3GP:3G流媒体,配合3G网络高速传输而开发。

FLV:文件小,加载速度快,用于网络观看视频。

MP4:音视频压缩编码标准。

40.线程同步的方法

转载文章:多线程同步的五种方法 - 平凡希 - 博客园

①synchronized 即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态;

②同步代码块 即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步;

③使用特殊变量Volatile

(1)volatile关键字为域变量的访问提供了一种免锁机制;

(2)使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;

(3)因此每次使用该域就要重新计算,而不是使用寄存器中的值;

(4)volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

使用重入锁

在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力;

⑤使用局部变量;

⑥使用阻塞队列。

线程sleep()和wait()的区别

sleep()不释放同步锁,自动唤醒,需要try-catch,线程方法。

wait()释放同步锁,需要notify唤醒,是object方法。

线程的生命周期?如何中断?

生命周期:文章转载:多线程——线程的生命周期 - 积_跬步 - 博客园

① 新建状态(New Thread):在Java语言中使用new 操作符创建一个线程后,该线程仅仅是一个空对象,它具备类线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。

线程处于创建状态时,可通过Thread类的方法来设置各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

② 就绪状态(Runnable):使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于就绪状态。此外,如果某个线程执行了yield()方法,那么该线程会被暂时剥夺CPU资源,重新进入就绪状态。

③ 运行状态(Running):Java运行系统通过调度选中一个处于就绪状态的线程,使其占有CPU并转为运行状态。此时,系统真正执行线程的run()方法。

a) 可以通过Thread类的isAlive方法来判断线程是否处于就绪/运行状态:当线程处于就绪/运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于阻塞状态,也可能处于停止状态。

④ 阻塞和唤醒线程

阻塞状态(Blocked):一个正在运行的线程因某些原因不能继续运行时,就进入阻塞 状态。这些原因包括:

​ a)当执行了某个线程对象的sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集内,等待超时而自动苏醒。

​ b)当多个线程试图进入某个同步区域时,没能进入该同步区域的线程会被置入锁定集,直到获得该同步区域的锁,进入就绪状态。

​ c)当线程执行了某个对象的wait()方法时,线程会被置入该对象的等待集中,知道执行了该对象的notify()方法wait()/notify()方法的执行要求线程首先获得该对象的锁。

⑤ 死亡状态(Dead):线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。

终止线程的三种方法

① 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止,推荐使用。

② 使用stop方法强制终止线程(这个方法不推荐使用,因为stop和suppend、resume一样,也可能发生不可预料的结果)。

③ 使用interrupt方法中断线程。

41.线程安全的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    private static SingleInstance instance;

public static SingleInstance getInstance(SingleInstance instance){
if (instance == null) {
synchronized (SingleInstance.class) {
if (instance == null) {
instance = new SingleInstance();
}
}
}
return instance;
}

}

42.Serializable 序列化接口,开销大,建议使用,java方法;

Parcelelable 使用麻烦,效率高,多用于内存,Android方法。

43.Service启动方式和生命周期

①startService():开启,调用者退出后Service仍在;
生命周期:onCreate()–onStartCommand()–onDestory()
通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。
②bindService():开启,调用者退出后Service随即退出。
生命周期:onCreate()–onBind()–onUnBind()–onDestory()
①+② 的生命周期:onCreate()–onStartCommand()–onBind()–onUnBind()–onDestory()

44.Android 常见布局

FrameLayout (框架布局)、LinearLayout(线性布局)、AbsoluteLayout(绝对布局)、RelativeLayout(相对布局)、TableLayout(表格布局)

45.Http是服务层,借助HttpClient和HttpUrlConnection建立短连接,请求一次后断开,需要重连时才连。

①HttpClient:开源框架,无封装,原始,使用方便,开发快,实现比较稳定,Android废弃,Android 6.0删除;
②HttpUrlConnection:对网络请求没有HttpClient封装彻底,Android2.2之前存在bug,所以2.2之前用HttpClient,之后用容易优化的HttpUrlConnection,开源框架,封装了请求头、参数、内容体、响应在I/O流,接口中统一封成了HttpGet/HttpPost,减少了操作的繁琐性,访问速度快。

Https是以安全为目标的Http通道,简单讲就是Http的安全版,即Http下加入SSL层,安全基础是SSL,加密的详细内容是SSL,作用:建立一个信息安全通道,来保证数据传输的安全、确认网站的安全性。

Volley:适合处理数据量小,通信频繁的网络操作,内部封装了异步操作,可直接在线程执行并处理结果,同时可以取消,容易扩展,但是不适合大数据请求,比如下载表现糟糕,不支持https,android2.2及以下用HttpClient,android2.3及以上用HttpUrlConnection。

OkHttp:专注于提升网络连接效率的Http客户端,能够实现IP和端口的请求重用一个socket,大大降低了连接时间,也降低了服务器的压力,对Http和https都有良好的支持,不用担心app版本更换的困扰,但是okHttp请求是在线程里执行,不能直接刷新UI,需要手动处理。

总结:在项目实际运用中,视情况选择网络请求方式,也可以Volley+OkHttp搭配使用。异步回调用Volley,网络请求底层用OkHttp

RxJava + Retrofit3 + OkHttp3
①RxJava 主要用来实现线程切换,我们制定订阅在哪一个线程,观察在哪个线程,通过操作符进行数据变换,整个过程是键式的,简化逻辑。
②Retrofit 是网络请求的一个架子,用它设置一些参数和请求Url。
③OkHttp是网络请求的内核,实际的网络请求是它发出来的。

TCP 是网络层,滑动窗口协议,拥塞控制,可靠连接借助socket长连接,需要3次握手,第四次取消连接,画面优先。
UDP:不关心数据是否到达,是否阻塞,不可靠连接,流畅优先。

46.如何节省内存使用,主动回收内存?

答:尽量多使用内部类,提高程序效率,回收已使用的资源,合理使用缓存,合理设置变量的作用范围。

47.Activity如何生成View?

答:Activity执行在attch()方法的时候,会创建一个PhoneWindow(Window的子类),在onCreate()方法的setContentView()方法中,创建DecorView,DecorView的addView()方法,把layout布局加载出来。通过onDraw()画出来,画View之前调用onMeasure()方法计算显示的大小。

48.为什么要使用多线程?

①更好地利用CPU资源;②进程间数据不能数据共享,线程可以;③系统创建进程需要为该进程重新分配系统资源,创建线程代价较小;④Java语言内置了多线程功能支持,简化了java多线程编程。

线程池是一种多线程处理形式,处理过程中将任务添加到队列,在创建线程后自动启动这些任务,线程池线程是后台线程,每个线程都使用默认的堆栈大小,以优先级执行。

49.现场保护

1
2
3
4
5
6
7
8
9
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}

使用场景:①进程被异常杀死;②系统配置发生变化(比如横竖屏切花换)。
当Activity处于onPause() ,onStop() ,onDestroy() 三种状态时程序可能会被Android系统回收掉,这时可能会造成用户在程序当中的数据或者修改丢失。于是我们需要”现场保护”,当下次重启程序或activity时恢复上一次的数据。
因此Android提供了onSaveInstanceState(Bundlout State)方法会在程序被回收前进行调用,但需要注意的是onSaveInstanceState()方法只适合保存瞬态数据, 比如UI控件的状态, 成员变量的值等,而不应该用来保存持久化数据。onRestoreInstanceState方法,需要注意的是onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的,onRestoreInstanceState被调用的前提是,activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行。

50.内存溢出,内存泄漏

内存溢出(OOM):程序在申请内存时,没有足够的内存空间使用。
原因:加载对象过大,相对资源较多,来不及加载。
解决办法:内存引用上做处理,比如用软引用;图片加载时处理(压缩等);动态回收内存;优化内存分配,自定义堆内存大小,避免使用Enum,减少BitMap的内存占用,内存对象重复使用,避免对象的内存泄漏。
内存泄漏(memory leak): 程序在申请内存后,无法释放已申请的内存空间,一次泄漏危害可忽略,但推积严重最终会导致OOM;

handler泄露:消息引用了handler对象,该对象又隐性地持有了Activity对象,当发生GC时以为message-handler-activity的引用链导致Activity无法被回收,即发生泄漏,简单来说就是handler对activity强引用导致的GC,无法及时回收Activity。(PS:GC垃圾回收,当堆内存里的对象没有引用指向时,GC回收。)

解决办法:方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。

PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。

静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。
WebView泄漏:创建的对象没有在合适的时间销毁,则一直存在内存里耗费内存空间,WebView不建议在xml文件中指明,因为一直存在不能对其销毁,应该在代码中创建WebView,通过addView()的方式加入layout,在Activity 的onDestory()方法中需要销毁,先将加载的内容置为null,webView.destroy();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
WebView mWebView = new WebView(getApplicationContext());

@Override
protected void onDestroy() {
if( mWebView!=null) {

ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}

mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
mWebView.destroy();

}
super.onDestroy();
}

51.JNI和NDK

JNI是Java调用Native 语言的一种特性,属于Java,Java本地接口,使Java与本地其他类型语言交互(C++)

​ 实现步骤:在Java中声明Native方法,编译该文件得到.class文件,通过javah命令导出JNI头文件(.h文件),使用Java需要交互的本地代码实现子啊Java中声明的Native方法,编译so文件,通过Java执行Java程序,最终实现Java调用本地代码


NDK(Native Develop Kit):Android开发工具包,属于Android。

​ 作用:快速开发C、C++动态库,并自动将so文件和应用打包成APK,即可通过NDK在Android中使用JNI与本地代码(C、C++)交互(Android开发需要本地代码C、C++实现)

​ 特点:运行效率高,代码安全性高,功能拓展性好,易于代码复用和移植。

​ 使用步骤:①配置NDK环境;②创建Android项目,并于NDK进行关联;③在Android项目中声明所需调用的Native方法;④使用该Native方法;⑤通过NDK build命令编译产生so文件;⑥编译AS工程,实现调用本地代码。

JNI和NDK的关系:JNI实现目的,NDK是Android实现JNI的手段,即在AS开发环境中通过NDK从而实现JNI功能。

52.常用的设计模式及其实现思想和作用

转载文章:23种设计模式全解析 - codeTao - 博客园

①单例模式:单例对象能保证在一个JVM中,该对象只有一个实例存在。

②工厂模式

一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。每个具体工厂类只能创建一个具体产品类的实例。

抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。

③适配器模式

将某个类的接口转换成客户端期望的另一个接口表示,目的是消除由于接口不匹配所造成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

④装饰模式(Decorator)

顾名思义,装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

Source类是被装饰类,Decorator类是一个装饰类,可以为Source类动态的添加一些功能。

装饰器模式的应用场景:

1、需要扩展一个类的功能。

2、动态的为一个对象增加功能,而且还能动态撤销。(继承不能做到这一点,继承的功能是静态的,不能动态增删。)

缺点:产生过多相似的对象,不易排错!

⑤代理模式(Proxy):多一个代理类出来,替原对象进行一些操作。

⑥桥接模式(Bridge):桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化。

⑦观察者模式:一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。

⑧访问者模式:一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。

53.HashMap、 HashTable、HashSet的异同

转载文章:HashSet HashTable HashMap的区别 及其Java集合介绍 - ywl925 - 博客园

①HashSet是Set的一个实现类,HashMap是Map的一个实现类,同时HashMap是HashTable的替代品

②HashSet以对象作为元素,而HashMap以(key-value)的一组对象作为元素,且HashSet拒绝接受重复的对象。HashMap可以看作三个视图:key的Set,value的Collection,Entry的Set。 这里HashSet就是其实就是HashMap的一个视图。

HashSet内部就是使用HashMap实现的,和HashMap不同的是它不需要Key和Value两个值。

HashMap是一个数组和链表的结合体,新加入的放在链头,重复的key不同的alue被新value替代

③继承不同

public class Hashtable extends Dictionary<> implements Map<>

public class HashMap extends AbstractMap<> implements Map<>

④HashTable 方法同步,而HashMap需要自己增加同步处理。

⑤HashTable中,key和value都不允许出现null值。

在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。用containsKey()方法来判断是否存在某个键。

⑥两个遍历方式的内部实现上不同。

HashTable、HashMap都使用了 Iterator。而由于历史原因,HashTable还使用了Enumeration的方式 。

⑦哈希值的使用不同

HashTable直接使用对象的hashCode,HashTable中hash数组默认大小是11,增加的方式是 old*2+1。

而HashMap重新计算hash值,HashMap中hash数组的默认大小是16,而且一定是2的指数。

如何实现HashMap线程同步?

①使用 java.util.Hashtable 类,此类是线程安全的。

②使用 java.util.concurrent.ConcurrentHashMap,此类是线程安全的。

③使用 java.util.Collections.synchronizedMap() 方法包装 HashMap object,得到线程安全的Map,并在此Map上进行操作。

54.Android 中内存泄漏原因及优化方案?

文章转载:Android 中内存泄漏的原因和解决方案 - 简书

非静态内部类造成的内存泄漏 非静态类会持有外部类的引用,如果这个内部类比外部类的生命周期长,在外部类被销毁时,内部类无法回收,即造成内存泄漏;

②外部类中持有非静态内部类的静态对象 保持一致的生命周期,将内部类对象改成非静态;

Handler 或 Runnable 作为非静态内部类 Handler 和 Runnable 作为匿名内部类,都会持有 Activity 的引用,由于 Handler 和 Runnable 的生命周期比 Activity 长,导致Activity 无法被回收,从而造成内存泄漏。 解决办法:将Handler 和 Runnable 定义为静态内部类,在Activity 的onDestory()方法中调用Handler 的 removeCallbacks 方法来移除 Message。

还有一种特殊情况,如果 Handler 或者 Runnable 中持有 Context 对象,那么即使使用静态内部类,还是会发生内存泄漏。解决办法:使用弱引用

④其他内存泄漏情况:比如BraodcastReceiver 未注销,InputStream 未关闭,再代码中多注意注销或关闭。

55.LeakCanary内存优化

参考文章:LeakCanary原理解析 - 简书

①项目如何使用LeakCanary

LeakCanary.enableDisplayLeakActivity(context);内存溢出图标,图标以通知的形式显示内存溢出

②工作机制

LeakCanary.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,用于自动监控调用Activity.onDestroy() 之后泄露的 activity。

1.RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。

2.然后在后台线程检查引用是否被清除,如果没有,调用GC。

3.如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。

4.在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。

5.得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。

6.HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。

7.引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

56.多线程、线程池

参考文章:Java中的多线程你只要看这一篇就够了 - Givefine - 博客园

线程的并行和并发

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

线程安全:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。


Java通过Executors提供四种线程池(from 百度)

newCachedThreadPool——创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool——创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool——创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor——创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

参考文章:Java并发编程:线程池的使用 - Matrix海子 - 博客园

①线程池中的线程初始化

创建线程池后,线程池中没有线程,需要提交任务才会创建线程。

prestartCoreThread():初始化一个核心线程;

prestartAllCoreThreads():初始化所有核心线程

②workQueue,任务缓存队列,用来存放等待执行的任务

workQueue的类型为BlockingQueue,通常可以取下面三种类型:

  1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

  2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

  3)SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

③任务拒绝策略

当线程池的任务缓存队列已满或线程数目达到maximumPoolSize,还有任务来时会采用任务拒绝策略

1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

④线程池的关闭

1)shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

2)shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

⑤线程池容量的动态调整

1)setCorePoolSize:设置核心池大小

2)setMaximumPoolSize:设置线程池最大能创建的线程数目大小

参考文章:由浅入深理解Java线程池及线程池的如何使用 - Janti - 博客园

corePoolSize :线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。

当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话:corePoolSize表示允许线程池中允许同时运行的最大线程数。

如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize :线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize。

keepAliveTime :表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于corePoolSize时,keepAliveTime才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown。

Unit:keepAliveTime的单位。

workQueue:一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能

threadFactory:线程工厂,用来创建线程。

handler :表示当拒绝处理任务时的策略。

在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池

Executors.newCachedThreadPool(); //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE

Executors.newSingleThreadExecutor(); //创建容量为1的缓冲池

Executors.newFixedThreadPool(int); //创建固定容量大小的缓冲池

57.腾讯 Bugly

腾讯公司为移动开发者开放的服务之一,面向移动开发者提供专业的 Crash 监控、崩溃分析等质量跟踪服务。Bugly 能帮助移动互联网开发者更及时地发现掌控异常,更全面的了解定位异常,更高效的修复解决异常。

针对移动应用,腾讯 Bugly 提供了专业的 Crash、Android ANR ( application not response)、iOS 卡顿监控和解决方案。移动开发者 ( Android / iOS ) 可以通过监控,快速发现用户在使用过程中出现的 Crash (崩溃)、Android ANR 和 iOS 卡顿,并根据上报的信息快速定位和解决问题。

58.Glide

github 地址

项目依赖Glide,在app build.gradle 中配置 compile’com.github.bumptech.glide:glide:3.7.0’

使用glide3.7版本,更高版本或出现异常:Error:Failed to resolve: com.android.support:support-annotations:27.0.2

Glide缓存机制

内存存缓存的 读存都在Engine类中完成。内存缓存使用弱引用和LruCache结合完成的,弱引用来缓存的是正在使用中的图片。图片封装类Resources内部有个计数器判断是该图片否正在使用。

Glide内存缓存的流程

读:是先从lruCache取,取不到再从弱引用中取;

存:内存缓存取不到,从网络拉取回来先放在弱引用里,渲染图片,图片对象Resources使用计数加一;

渲染完图片,图片对象Resources使用计数减一,如果计数为0,图片缓存从弱引用中删除,放入lruCache缓存。

参考文章:

Google推荐——Glide使用详解 - 简书

Glide 系列(四) Glide缓存机制 - 野生的安卓兽 - 简书

59.Fresco

github 地址

项目依赖Glide,在app build.gradle 中配置:implementation’com.facebook.fresco:fresco:1.9.0’

https://github.com/desmond1121/Fresco-Source-Analysis

参考文章:Fresco的使用小结 - 简书

https://blog.csdn.net/yw59792649/article/details/78921025

60.React Native

混合开发技术移动开发-混合App介绍 - Primise7的博客 - CSDN博客

坚持原创技术分享,您的支持将鼓励我继续创作!