面试安卓Java Android 面试题

安卓面试知识点大全(适合中高级)

2019-05-31  本文已影响0人  小夫哥

JAVA

8种基本类型

1.byte(位)8位
2.short(短整数)16位
3.int(整数)32位
4.long(长整数)64位
5.float(单精度)32位
6.double(双精度)64位
7.char(字符)16位
8.boolean(布尔值)8位

int跟Integer区别

1.int是常用数据类型,Integer是int的封装类;
2.int类的变量初始为0,而Integer的变量则初始化为null;
3.int和Integer都可以表示某一个数值;
4.int和Integer不能够互用,因为他们两种不同的数据类型;

try...catch...finally的运行机制

  1. 只有当try代码块发生异常的时候,才会执行到catch代码块

  2. 不管try中是否发生异常,finally都会执行。
    以下两种情况例外:
    一:try中不发生异常时,try块中有System.exit(0);
    二:try中发生异常时,catch中有System.exit(0);
    说明:System.exit(0) 代码的作用的退出虚拟机;

  3. 若finally块内有return语句,则以finally块内的return为准
    说明:
    如果try 或者 catch内也有return 其实是先执行了try 或者 catch代码块中的return语句的,
    但是由于finally的机制,执行完try或者catch内的代码以后并不会立刻结束函数,还会执行finally块代码,
    若finally也有return语句,则会覆盖try块或者catch块中的return语句
    若finally代码块中有return语句,则屏蔽catch代码块中抛出的异常,否则,异常会在finally之后抛出。

抽象类和接口的区别

抽象类(abstract class)

使用abstract修饰符修饰的类。官方点的定义就是:如果一个类没有包含足够多的信息来描述一个具体的对象,这样的类就是抽象类。但抽象类不能实例化。

接口(interface)

接口在java中是一个抽象类型,是抽象方法的集合。一个类通过继承接口的方式,从而继承接口的抽象方法。接口和抽象类一样不能被实例化。

equals()与hashCode()

equals()

用来判断两个对象是否相等。

  • 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
  • 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。
  • java对equlas()的要求
  1. 对称性:如果x.equals(y)返回是"true",那么y.equals(x)也应该返回是"true"。
  2. 反射性:x.equals(x)必须返回是"true"。
  3. 类推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也应该返回是"true"。
  4. 一致性:如果x.equals(y)返回是"true",只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是"true"。
  5. 非空性,x.equals(null),永远返回是"false";x.equals(和x不同类型的对象)永远返回是"false"。

equals() 与 == 的区别是什么?

hashCode()

获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。
注意:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

hashCode() 和 equals() 的关系

  1. 第一种 不会创建“类对应的散列表”
  • 这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。
    在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!
    这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。
  1. 第二种 会创建“类对应的散列表”
  • 这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。
    在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
    1.如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。
    2.如果两个对象hashCode()相等,它们并不一定相等。
    因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。
  • 此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。

算法

快速排序

通过一趟扫描将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

冒泡算法

冒泡排序其实是基于“交换”。每次从第一个记录开始,一、二两个记录比较,大的往后放,二三两个记录比较...依次类推,这就是一趟冒泡排序。每一趟冒泡排序后,无序序列中值最大的记录冒到序列末尾,所以称之为冒泡排序。

选择排序算法

暂定第一个元素为最小元素,往后遍历,逐个与最小元素比较,若发现更小者,与先前的"最小元素"交换位置。达到更新最小元素的目的。
一趟遍历完成后,能确保刚刚完成的这一趟遍历中,最的小元素已经放置在前方了。然后缩小排序范围,新一趟排序从数组的第二个元素开始。
在新一轮排序中重复第1、2步骤,直到范围不能缩小为止,排序完成。

JSON与XML

JSON

一种轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。JSON采用兼容性很高的文本格式。

XML

扩展标记语言(Extensible Markup Language),用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。

优缺点
1.在可读性方面,JSON和XML的数据可读性基本相同。JSON和XML的可读性可谓不相上下,一边是建议的语法,一边是规范的标签形式,很难分出胜负。 
2.在可扩展性方面,XML天生有很好的扩展性,JSON当然也有,没有什么是XML能扩展,JSON不能的。 
3.在编码难度方面,XML有丰富的编码工具,比如Dom4j、JDom等,JSON也有json.org提供的工具,但是JSON的编码明显比XML容易许多,即使不借助工具也能写出JSON的代码,可是要写好XML就不太容易了。 
4.在解码难度方面,XML的解析得考虑子节点父节点,让人头昏眼花,而JSON的解析难度几乎为0。这一点XML输的真是没话说。 
5.在流行度方面,XML已经被业界广泛的使用,而JSON才刚刚开始,但是在Ajax这个特定的领域,未来的发展一定是XML让位于JSON。到时Ajax应该变成Ajaj(Asynchronous JavaScript and JSON)了。 
6.JSON和XML同样拥有丰富的解析手段。 
7.JSON相对于XML来讲,数据的体积小。
8.JSON与JavaScript的交互更加方便。
9.JSON对数据的描述性比XML较差。
10.JSON的速度要远远快于XML。

Synchronized关键字

public class StaticSynDemo { 
 
private static String a="test"; 
 
//等同于方法print2 
public synchronized void print1(String b){ //调用前要取得StaticSynDemo实例化后对象的锁 
   System.out.println(b+a); 
} 
public void print2(String b){ 
   synchronized (this) {//取得StaticSynDemo实例化后对象的锁 
    System.out.println(b+a); 
   } 
} 
//等同于方法print4 
public synchronized static void print3(String b){//调用前要取得StaticSynDemo.class类的锁 
   System.out.println(b+a); 
} 
public static void print4(String b){ 
   synchronized (StaticSynDemo.class) { //取得StaticSynDemo.class类的锁 
    System.out.println(b+a); 
   } 

基础

Activity启动模式

activity有四种启动模式,分别为standard,singleTop,singleTask,singleInstance。如果要使用这四种启动模式,必须在manifest文件中<activity>标签中的launchMode属性中配置,如:

<activity android:name=".app.InterstitialMessageActivity"
                  android:label="@string/interstitial_label"
                  android:theme="@style/Theme.Dialog"
                  android:launchMode="singleTask"
        </activity>

standard 默认模式

标准启动模式,也是activity的默认启动模式。在这种模式下启动的activity可以被多次实例化,即在同一个任务中可以存在多个activity的实例,每个实例都会处理一个Intent对象。如果Activity A的启动模式为standard,并且A已经启动,在A中再次启动Activity A,即调用startActivity(new Intent(this,A.class)),会在A的上面再次启动一个A的实例,即当前的桟中的状态为A-->A。

singleTop 栈顶复用模式

singleTask 栈内复用模式

在这个模式下,如果栈中存在这个Activity的实例就会复用这个Activity,不管它是否位于栈顶,复用时,会将它上面的Activity全部出栈,并且会回调该实例的onNewIntent方法。其实这个过程还存在一个任务栈的匹配,因为这个模式启动时,会在自己需要的任务栈中寻找实例,这个任务栈就是通过taskAffinity属性指定。如果这个任务栈不存在,则会创建这个任务栈。

singleInstance 全局唯一模式

该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。

Activity生命周期

Activity生命周期图

Fragment生命周期

onAttach()

执行该方法时,Fragment与Activity已经完成绑定,该方法有一个Activity类型的参数,代表绑定的Activity,这时候你可以执行诸如mActivity = activity的操作。

onCreate()

初始化Fragment。可通过参数savedInstanceState获取之前保存的值。

onCreateView()

初始化Fragment的布局。加载布局和findViewById的操作通常在此函数内完成,但是不建议执行耗时的操作,比如读取数据库数据列表。

onActivityCreated()

执行该方法时,与Fragment绑定的Activity的onCreate方法已经执行完成并返回,在该方法内可以进行与Activity交互的UI操作,所以在该方法之前Activity的onCreate方法并未执行完成,如果提前进行交互操作,会引发空指针异常。

onStart()

执行该方法时,Fragment由不可见变为可见状态。

onResume()

执行该方法时,Fragment处于活动状态,用户可与之交互。

onPause()

执行该方法时,Fragment处于暂停状态,但依然可见,用户不能与之交互。

onSaveInstanceState()

保存当前Fragment的状态。该方法会自动保存Fragment的状态,比如EditText键入的文本,即使Fragment被回收又重新创建,一样能恢复EditText之前键入的文本。

onStop()

执行该方法时,Fragment完全不可见。

onDestroyView()

销毁与Fragment有关的视图,但未与Activity解除绑定,依然可以通过onCreateView方法重新创建视图。通常在ViewPager+Fragment的方式下会调用此方法。

onDestroy()

销毁Fragment。通常按Back键退出或者Fragment被回收时调用此方法。

onDetach()

解除与Activity的绑定。在onDestroy方法之后调用。

setUserVisibleHint()

设置Fragment可见或者不可见时会调用此方法。在该方法里面可以通过调用getUserVisibleHint()获得Fragment的状态是可见还是不可见的,如果可见则进行懒加载操作。

Fragment生命周期执行流程

Fragment创建

setUserVisibleHint()->onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume();

Fragment变为不可见状态(锁屏、回到桌面、被Activity完全覆盖):

onPause()->onSaveInstanceState()->onStop();

Fragment变为部分可见状态(打开Dialog样式的Activity):

onPause()->onSaveInstanceState();

Fragment由不可见变为活动状态

onStart()->OnResume();

Fragment由部分可见变为活动状态

onResume();

Fragment退出

onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()(注意退出不会调用onSaveInstanceState方法,因为是人为退出,没有必要再保存数据);

Fragment被回收又重新创建

被回收执行onPause()->onSaveInstanceState()->onStop()->onDestroyView()->onDestroy()->onDetach(),重新创建执行onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume()->setUserVisibleHint();

横竖屏切换

与Fragment被回收又重新创建一样。

Service

采用start的方式开启服务

使用这种start方式启动的Service的生命周期如下:

onCreate()--->onStartCommand()(onStart()方法已过时) ---> onDestory()
说明:如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStart()和onStartCommand()。
服务停止的时候调用 onDestory()。服务只会被停止一次。

特点:一旦服务开启跟调用者(开启者)就没有任何关系了。
开启者退出了,开启者挂了,服务还在后台长期的运行。
开启者不能调用服务里面的方法。

采用bind的方式开启服务

使用这种start方式启动的Service的生命周期如下:

onCreate() --->onBind()--->onunbind()--->onDestory()
注意:绑定服务不会调用onstart()或者onstartcommand()方法

特点:bind的方式开启服务,绑定服务,调用者挂了,服务也会跟着挂掉。
绑定者可以调用服务里面的方法。

广播

分类

  • 标准广播
    是一种异步的方式来进行传播的,广播发出去之后,所有的广播接收者几乎是同一时间收到消息的。他们之间没有先后顺序可言,而且这种广播是没法被截断的。
    1.同级别接收先后是随机的(无序的),级别低的后接收到广播
    2.接收器不能截断广播的继续传播,也不能处理广播
    3.同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
  • 有序广播
    是一种同步执行的广播,在广播发出去之后,同一时刻只有一个广播接收器可以收到消息。当广播中的逻辑执行完成后,广播才会继续传播。
    1.同级别接收是随机的(结合下一条)
    2.同级别动态注册(代码中注册)高于静态注册(AndroidManifest中注册)
    3.排序规则为:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值从大到小排序
    4.先接收的BroadcastReceiver可以对此有序广播进行截断,使后面的BroadcastReceiver不再接收到此广播,也可以对广播进行修改,使后面的BroadcastReceiver接收到广播后解析得到错误的参数值。当然,一般情况下,不建议对有序广播进行此类操作,尤其是针对系统中的有序广播。

注册的方式分类

  • 动态注册广播
    在代码中注册,必须在运行时才能进行,接收广播速度要优于静态注册。
  • 静态注册广播
    主要是在AndroidManifest中进行注册。

广播的安全性问题

Android中的广播可以跨进程甚至跨App直接通信,且exported属性在有intent-filter的情况下默认值是true,由此将可能出现的安全隐患如下:

  • 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
  • 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

增加安全性的方案包括:

  • 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
  • 在广播发送和接收时,都增加上相应的permission,用于权限验证;
  • 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
  • 采用LocalBroadcastManager的方式。使用该机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本地应用程序发出的广播。

ContentProvider内容提供者

强引用、软引用、弱引用、虚引用

强引用(StrongReference)

软引用(SoftReference)

弱引用(WeakReference)

虚引用(PhantomReference)

Android各版本新特性

Android 5.x

1.Material design,算是Android 系统风格的里程碑,其3D UI风格新颖,贴近人机交互;
2.改善通知栏,提升可视化、亲近性、可编辑性。同时支持手机在锁屏状态也可接收到通知,用户可以在锁屏状态下,设置接收全部应用的通知或者接收部分应用的通知或者不接收所有应用的通知;
3.系统由以往的Dalvik模式改为采用ART(Android Runtime)模式,实现ahead-of-time (AOT)静态编译与just-in-time (JIT)动态编译交互进行;
4.V7中引入CardView和RecycleView等新控件;
5.支持64位系统;

Android 6.x

1.新增运行时权限概念
Android6.0或以上版本,用户可以完全控制应用权限。当用户安装一个app时,系统默认给app授权部分基础权限,其他敏感权限,需要开发者自己注意,当涉及敏感权限时,开发者需要手动请求系统授予权限,系统这时会弹框给用户,倘若用户拒绝,如果没有保护,app将直接崩溃,倘若有保护,app也无法使用相关功能。
2.新增瞌睡模式和待机模式
瞌睡模式:当不碰手机,手机自动关闭屏幕后,过一会,手机将进入瞌睡模式。在瞌睡模式下,设备只会定期的唤醒,然后继续执行等待中的任务接着又进入瞌睡;
待机模式:假如用户一段时间不触碰手机,设备将进入待机模式。在这个模式下,系统会认为所有app是闲置的,这时系统会关闭网络,并且暂停app之前正在执行的任务。
3.移除对Apache HTTP client的支持,建议使用HttpURLConnection。
4.Doze电量管理
Android 6.0自带Doze电量管理功能,在“Doze”模式下,手机会在一段时间未检测到移动时,让应用休眠清杀后台进程减少功耗,谷歌表示,当屏幕处于关闭状态,平均续航时间提高30%。

Android 7.x

1.通知栏快捷回复
在Android N上,Android对通知栏进行了进一步的优化,其中一个非常大的改变就是让用户可以在通知栏上直接对通知进行回复,这对于一些IM类的App来说,提供了更加友好的回复功能。
2.加入原生分屏多任务功能,多任务快速切换
3.VR
Android N上对VR的支持,实际上是使用了一个新的跨平台图形计算库——Vulkan,Vlukan API提升处理能力,减少GPU处理,从而获得更佳的游戏体验,所以说,如果一个手机支持VR,那么从某种意义上来说,这个手机的性能应该是很赞的!
4.引入全新的JIT编译器,使得App安装速度快了75%,编译代码的规模减少了50%
5.安全:更安全的加密模式,可以对单独的文件进行加密,android系统启动加密

Android 8.x

画中画
1.Android O中,谷歌更加强调多任务处理场景中的流畅性,在I/O2017上,谷歌演示了增强功能的画中画模式,为用户带来不同应用程序间的流畅操作体验。例如用户可以在Netflix上观看电影,支持将电影屏幕缩小成悬浮窗口,在看电影的同时进行查看日历、搜索信息等其他工作,这和普通的画中画分屏模式并不相同。
2.Notification Dots
在Android O之前,使用安卓手机的用户,想要看到哪些应用程序推送了通知,可能只有在下拉通知中心中看到,但在Android O中,谷歌对安卓的通知功能做出了改进,这就是全新的Notification Dots功能,它是位于应用程序图标之上的小小的循环点,只有当应用出现未读通知时,它才会出现。这时候长按应用程序图标,就会以类似气泡的形式快速预览。而在通知中心中删除这些未读通知,应用图标上的标记点也会消失。
3.自动填充(Auto-Fill)
对于用户设备上最常用的应用,Android O将会帮助用户进行快速登录,而不用每次都填写账户名和密码。例如当用户使用一个新设备时,可以从Chrome中提取已经保存的账户名和密码,选择之后,自动填充功能便可以在本地进行,适用于你可能用到的大多数应用程序。开发人员也需要对其应用程序进行优化,让其应用程序能够和自动填充功能更好地兼容。
4.自适应图标(Adaptive icons)
Adaptive icons也是一项有趣的新功能,谷歌正在尝试整理Android中不一致的应用程序图标形状,这一功能为应用程序开发人员提供了适应其显示设备的每个图标的多个形状模板。因此,如果你的手机默认应用程序图示形状是圆角正方形,那么所有应用程序的图标都将是这个形状(前提是开发人员使用了这一功能)。也就是说,你将不再看到系统主屏上方形图标和圆形图标混合在一起的现象。
5.后台进程限制
谷歌表示一直在优化安卓Android的后台应用限制策略,以最大程度减小后台应用对电池的消耗和对资源的占用。在Android O的更新中,当应用被置入后台后,Android O将自动智能限制后台应用活动,主要会限制应用的广播、后台运行和位置,但应用的整体进程并没有被杀掉。不过,部分层级比较重要的应用可以不受限制,但总的来说,Android O将严格限制后台进程对手机资源的调用。
6.运行时权限策略变化
在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。

Andorid 9.x

1.利用 Wi-Fi RTT 进行室内定位
2.显示屏缺口支持
3.引入了多个通知增强功能
4.提升短信体验
5.广播渠道设置、广播和请勿打扰
6.多摄像头支持和摄像头更新
7.用于位图和 drawable 的 ImageDecoder
8.JobScheduler 中的数据费用敏感度
9.神经网络 API 1.1
10.自动填充的改进
11.用于 NFC 支付和安全交易的 Open Mobile API

Webview跟原生交互

Android去调用JS的代码

1.通过WebView的loadUrl(),特别注意:JS代码调用一定要在 onPageFinished() 回调之后才能调用,否则不会调用。
2.通过WebView的evaluateJavascript()
优点:该方法比第一种方法效率更高、使用更简洁。

JS去调用Android的代码

1.通过WebView的addJavascriptInterface()进行对象映射
被JS调用的方法必须加入@JavascriptInterface注解
// 设置与Js交互的权限
webSettings.setJavaScriptEnabled(true);
// 通过addJavascriptInterface()将Java对象映射到JS对象
mWebView.addJavascriptInterface(new AndroidtoJs(), "test");//AndroidtoJS类对象映射到js的test对象
优点:使用简单
缺点:4.4版本以下存在严重的漏洞问题,如:

WebSettings.setSavePassword(false) 
// 禁用 file 协议;
setAllowFileAccess(false); 
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);
// 需要使用 file 协议
setAllowFileAccess(true); 
setAllowFileAccessFromFileURLs(false);
setAllowUniversalAccessFromFileURLs(false);

// 禁止 file 协议加载 JavaScript
if (url.startsWith("file://") {
    setJavaScriptEnabled(false);
} else {
    setJavaScriptEnabled(true);
}

2.通过 WebViewClient 的shouldOverrideUrlLoading ()方法回调拦截 url
解析该 url 的协议
如果检测到是预先约定好的协议,就调用相应方法
优点:不存在方式1的漏洞;
缺点:JS获取Android方法的返回值复杂。
3.通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调拦截JS对话框alert()、confirm()、prompt() 消息

RecycleView与ListView区别

RecycleView

1.支持线性布局、网格布局、瀑布流布局
2.简单实现item动画
3.HeaderView、FooterView、EmptyView这些View需要自行实现
4.支持局部刷新
5.需要自定义监听item点击事件

性能优化

1.数据处理和视图加载分离。数据的处理逻辑放在异步处理,ViewHolder 就可以简单无压力地做数据与视图的绑定逻辑。
2.数据优化。分页拉取远端数据,对拉取下来的远端数据进行缓存。
3.布局优化。减少过渡绘制,减少 xml 文件 inflate 时间,这种 inflate 带来的损耗是相当大的,此时我们可以用代码去生成布局,即 new View() 的方式。
4.把默认动画关闭来提升效率。
5.设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程中停止加载的操作。
6.如果 Item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源。

ListView

1.仅支持垂直线性布局
2.实现item动画复杂
3.有HeaderView、FooterView、EmptyView这些View的Api
4.不支持局部刷新
5.拥有监听item点击事件

性能优化

1.convertView的复用,因为每次都去加载xml布局会损耗性能。
2.ViewHolder的使用,findViewById这个方法是从ViewGroup的子View里面循环遍历找id与给出的ID相同的子View,还是比较耗时的。点击事件监听也可以写在这里面去。
3.图片缓存。我们尽量不要在ListView滑动的时候加载网络图片。
4.少在getView里面new对象,避免做耗时操作。

加载大图优化

1.质量压缩。
2.大小尺寸压缩。
3.LruCache缓存。

Handler,Looper,MessageQueue

Handler

MessageQueue

MessageQueue翻译过来是"消息队列"的意思,实际上它内部的数据结构不是队列,而是单向链表;MessageQueue中储存了大量的消息,由于一个线程同一时间只能处理一条消息,所以我们建了一个链表,将我们需要处理的消息按顺序储存起来,然后一项一项的交给需要的线程处理,这就是MessageQueue存在的价值。

Looper

Message

Message也就是消息,井中的水。一个Message包括了消息类型(what),消息内容(arg1,arg2),发送它的Handler(target),Runnable回调接口等:

 public int what;        //数据类型
    public int arg1;        //简单的整数值
    public int arg2;        //简单的整数值可以直接发送,是一种替代setData(Bundle)的低成本方案,更加省资源
    public Object obj;
    ......
    /*package*/ int flags;
    /*package*/ long when;          //Handler发送一个消息之后,返回此消息的目标交付时间(以毫秒为单位)。
    /*package*/ Bundle data;        //Bundle可以携带更复杂的数据类型
    /*package*/ Handler target;     //哪个Handler发送的消息
    /*package*/ Runnable callback;

    //可以看到,Message带了一个指向一下个节点的链,也就是说,MessageQueue内部维护的实际上是一个链表
    /*package*/ Message next;

    private static final Object sPoolSync = new Object();
    private static Message sPool;       //消息池
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;    //消息池的最大容量

线程

Thread/Runnable/Callable

一般实现线程的方法有两种,一种是类继承Thread,一种是实现接口Runnable。

FutureTask

AsyncTask

并且提供了4个核心方法。

1.参数1,Params,异步任务的入参;
2.参数2,Progress,执行任务的进度;
3.参数3,Result,后台任务执行的结果;
4.方法1, onPreExecute(),在主线程中执行,任务开启前的准备工作;
5.方法2,doInbackground(Params…params),开启子线程执行后台任务;
6.方法3,onProgressUpdate(Progress values),在主线程中执行,更新UI进度;
7.方法4,onPostExecute(Result result),在主线程中执行,异步任务执行完成后执行,它的参数是doInbackground()的返回值。

HandlerThread

IntentService

线程池

我们来介绍一下不同特性的线程池,它们都直接或者间接通过ThreadPoolExecutor来实现自己的功能

FixedThreadPool

通过Executors的newFixedThreadPool()方法创建,它是个线程数量固定的线程池,该线程池的线程全部为核心线程,它们没有超时机制且排队任务队列无限制,因为全都是核心线程,所以响应较快,且不用担心线程会被回收。

CachedThreadPool

通过Executors的newCachedThreadPool()方法来创建,它是一个数量无限多的线程池,它所有的线程都是非核心线程,当有新任务来时如果没有空闲的线程则直接创建新的线程不会去排队而直接执行,并且超时时间都是60s,所以此线程池适合执行大量耗时小的任务。由于设置了超时时间为60s,所以当线程空闲一定时间时就会被系统回收,所以理论上该线程池不会有占用系统资源的无用线程。

ScheduledThreadPool

通过Executors的newScheduledThreadPool()方法来创建,ScheduledThreadPool线程池像是上两种的合体,它有数量固定的核心线程,且有数量无限多的非核心线程,但是它的非核心线程超时时间是0s,所以非核心线程一旦空闲立马就会被回收。这类线程池适合用于执行定时任务和固定周期的重复任务。

SingleThreadExecutor

通过Executors的newSingleThreadExecutor()方法来创建,它内部只有一个核心线程,它确保所有任务进来都要排队按顺序执行。它的意义在于,统一所有的外界任务到同一线程中,让调用者可以忽略线程同步问题。

线程池一般用法

MVC,MVP,MVVM架构的区别

MVC

MVP

在App开发过程中,经常出现的问题就是某一部分的代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。从实践中看到,这更多的出现在UI部分,也就是Activity里。想象一下,一个2000+行以上基本不带注释的Activity,我的第一反应就是想吐。Activity内容过多的原因其实很好解释,因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。而且现在大部分的Activity还对整个App起到类似IOS中的【ViewController】的作用,这又带入了大量的逻辑代码,造成Activity的臃肿。为了解决这个问题,让我们引入MVP框架。

优点
1.模型与视图完全分离,我们可以修改视图而不影响模型;
2.可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部;
3.我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
4.如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

MVVM

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

动画的实现方式

视图动画

补间动画

逐帧动画

属性动画

区别

View绘制流程

View的整个绘制流程可以分为以下三个阶段:

onMeasure

对于View的测量,肯定会和MeasureSpec接触,MeasureSpec是两个单词组成,翻译过来“测量规格”或者“测量参数”,很多博客包括官方文档对他的说明基本都是“一个MeasureSpec封装了从父容器传递给子容器的布局要求”,这个MeasureSpec 封装的是父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递” 两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。
MeasureSpec一共有三种模式:

onLayout

确定视图的位置,从顶层父View到子View递归调用layout()方法,父View将上一步measure()方法得到的子View的布局大小和布局参数,将子View放在合适的位置上。

onDraw

绘制最终的视图,首先ViewRoot创建一个Canvas对象,然后调用onDraw()方法进行绘制。onDraw()方法的绘制流程为: 绘制视图背景-> 绘制画布的图层->绘制View内容->绘制子视图。

View事件传递分支机制

ViewGroup接收到事件后进行事件的分派,如果自己需要处理这个事件,则进行拦截;如果不处理,则传递给子View进行处理,然后由子view进行分派,拦截和处理。打个比方:上级接到任务后进行任务分派,如果上级自己处理这个任务,则自己处理;如果不想处理,则把这个任务丢给下级进行处理。

对于ViewGroup,我们需要重写了以下三个方法:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   Log.d("KeithXiaoY", "ViewGroupA dispatchTouchEvent" ));
   return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
   Log.d("KeithXiaoY", "ViewGroupA onInterceptTouchEvent" );
   return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
   Log.d("KeithXiaoY", "ViewGroupA onTouchEvent" );
   return super.onTouchEvent(event);
}

而对于View来说,我们需要重写了以下两个方法:

@Override
public boolean onTouchEvent(MotionEvent event) {
   Log.d("KeithXiaoY", "View onTouchEvent" );
   return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
   Log.d("KeithXiaoY", "View dispatchTouchEvent" );
   return super.dispatchTouchEvent(event);
}

序列化

为什么要序列化?
1.永久性保存对象,保存对象的字节序列到本地文件中;
2.通过序列化对象在网络中传递对象;
3.通过序列化在进程间传递对象。

实现序列化的方法:

Serializable

实现Serializable接口非常简单,声明一下就可以了,但它在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

Parcelable

进程间通信

IPC为进程间通信或跨进程通信,是指两个进程进行进程间通信的过程。

使用Bundle的方式

利用Bundle进行进程间通信是很容易的,大家应该注意到,这种方式进行进程间通信只能是单方向的简单数据传输,它使用时有一定的局限性。

使用文件共享的方式

使用Messenger的方式

我们也可以通过Messenger来进行进程间通信,在Messenger中放入我们需要传递的数据,就可以轻松的实现进程之间数据传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

使用AIDL的方式

AIDL(Android Interface Definition Language)是一种IDL语言,用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。
AIDL是IPC的一个轻量级实现,用了对于Java开发者来说很熟悉的语法。Android也提供了一个工具,可以自动创建Stub(类架构,类骨架)。当我们需要在应用间通信时,我们需要按以下几步走:
1.定义一个AIDL接口。
2.为远程服务(Service)实现对应Stub。
3.将服务“暴露”给客户程序使用。

使用ContentProvider的方式

ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作。ContentProvider分为系统的和自定义的,系统的(例如:联系人,图片等数据)。

使用广播接收者(Broadcast)的方式

BroadcastReceiver本质上是一个系统级的监听器,它专门监听各个程序发出的Broadcast,因此它拥有自己的进程,只要存在与之匹配的Intent被广播出来,BroadcastReceivert总会被激发。我们知道,只要注册了某个广播之后,广播接收者才能收到该广播。广播注册的一个行为是将自己感兴趣的IntentFilter注册到Android系统的AMS(ActivityManagerService)中,里面保存了一个IntentFilter列表。广播发送者将IntentFilter的action行为发送到AMS中,然后遍历AMS中的IntentFilter列表,看谁订阅了该广播,然后将消息遍历发送到注册了相应的IntentFilter或者Service中---也就是说:会调用抽象方法onReceive()方法。其中AMS起到了中间桥梁的作用。
程序启动BroadcastReceiver只需要两步:
1.创建需要启动的BroadcastReceivert的intent;
2.调用Context的sendBroadcast()或者sendOrderBroadcast()方法来启动指定的BroadcastReceivert。
每当Broadcast事件发生后,系统会创建对应的BroadcastReceiver实例,并自动触发onReceiver()方法,onReceiver()方法执行完后,BroadcastReceiver实例就会被销毁。
注意:onReceiver()方法中尽量不要做耗时操作,如果onReceiver()方法不能再10秒之内完成事件的处理,Android会认为该进程无响应,也就弹出我们熟悉的ANR对话框。

使用Socket的方式

Socaket也是实现进程间通信的一种方式,Socaket也称为“套接字”,网络通信中的概念,通过Socket我们可以很方便的进行网络通信,都可以实现网络通信录,那么实现跨进程通信不是也是相同的嘛,但是Socaket主要还是应用在网络通信中。

热修复

类加载方案

底层替换方案

与类加载方案不同的是,底层替换方案不会再次加载新类,而是直接在Native层修改原有类,由于是在原有类进行修改限制会比较多,不能够增减原有类的方法和字段,如果我们增加了方法数,那么方法索引数也会增加,这样访问方法时会无法通过索引找到正确的方法,同样的字段也是类似的情况。

Instant Run方案

浏览器输入地址到返回结果发生了什么

1.DNS解析;
2.TCP链接;
3.发送HTTP请求;
4.服务器处理请求并返回HTTP报文;
5.浏览器解析渲染界面;
6.连接结束。

Rxjava常用操作符

Observable 的创建

from()

转换集合为一个每次发射集合中一个元素的 Observable 对象。
使用场景:对集合(数组、List 等)进行遍历。

just()

转换一个或多个 Object 为依次发射这些 Object 的 Observable 对象。

使用场景:转换一个或多个普通 Object 为 Observable 对象,如转换数据库查询结果、网络查询结果等。
just() 方法可传入 1~10 个参数,也就说当元素个数小于等于 10 的时候既可以使用just() 也可以使用 from(),否则只能用 from() 方法。

create()

返回一个在被 OnSubscribe 订阅时执行特定方法的 Observable 对象,
使用场景:不推荐使用,可使用其他操作符替代,如使用 from()操作符完成遍历。

interval()

返回一个每隔指定的时间间隔就发射一个序列号的 Observable 对象。这是一个无限循环,可以采用interval + take的方式来实现指定次数循环。
使用场景:可使用该操作符完成定时、倒计时等功能。

timer()

创建一个在指定延迟时间后发射一条数据(固定值:0)的 Observable 对象。

range()

创建一个发射指定范围内的连续整数的 Observable 对象。
使用场景:可使用该操作符完成一个 fori 的循环,如 for(int i=5;i<=7;i++) -> Observable.range(5, 3)。

error()

创建不发射任何数据就发出 onError 通知的 Observable 对象。
使用场景:程序中捕获异常后,可使用该操作符把捕获的异常传递到后面的逻辑中处理。

重做

repeat()

使Observable 对象在发出 onNext() 通知之后重复发射数据。重做结束才会发出 onComplete() 通知,若重做过程中出现异常则会中断并发出 onError() 通知。
使用场景:可使用该操作符指定一次任务执行完成后立即重复执行上一次的任务,如发送多次网络请求等。

repeatWhen()

同上,指定满足一定条件时重复执行一个任务。

重试

retry()

在执行 Observable对象的序列出现异常时,不直接发出 onError() 通知,而是重新订阅该 Observable对象,直到重做过程中未出现异常,则会发出 onNext() 和 onCompleted()通知;若重做过程中也出现异常,则会继续重试,直到达到重试次数上限,超出次数后发出最新的 onError() 通知。

使用场景:网络等请求异常出错后,可重新发起请求。

retryWhen()

有条件的执行重试。
使用场景:网络等请求异常出错后,若满足一定条件,则重新发起请求。

变换

map()

把源 Observable 发射的元素应用于指定的函数,并发送该函数的结果。

使用场景:将从网络获取的数据(NetData 对象)转换为数据库相关对象(DBData对象)并使用 Observable 发送。

flatMap()

转换源 Observable 对象为另一个 Observable 对象。

使用场景:从网络获取数据并使用 obsA 对象发射,flatMap() 操作符中可将数据存进数据库并返回一个新的对象 obsB。

过滤

filter()

只发射满足指定谓词的元素。
使用场景:可使用 filter 代替 if 语句。

first()

返回一个仅仅发射源 Observable 发射的第一个[满足指定谓词的]元素的 Observable,如果源 Observable 为空,则会抛出一个 NoSuchElementException。
使用场景: 顺序发出多条数据,只接收第一条。

last()

返回一个仅仅发射源 Observable 发射的倒数第一个[满足指定谓词的]元素的 Observable,如果源 Observable 为空,则会抛出一个 NoSuchElementException。
使用场景: 顺序发出多条数据,只接收最后一条。

skip()

跳过前面指定数量或指定时间内的元素,只发射后面的元素。

skipLast()

跳过前面指定数量或指定时间内的元素,只发射后面的元素。指定时间时会延迟源 Observable 发射的任何数据。

take()

只发射前面指定数量或指定时间内的元素。

takeLast()

只发射后面指定数量或指定时间内的元素。指定时间时会延迟源 Observable 发射的任何数据。

elementAt()

只发射指定索引的元素。
使用场景: 按索引去集合中的元素等。

elementAtOrDefault()

只发射指定索引的元素,若该索引对应的元素不存在,则发射默认值。

ignoreElements()

不发射任何数据,直接发出 onCompleted() 通知。

组合操作

用于将多个Observable组合成一个单一的Observable的操作符

Join

数组长度是两个数组的乘积

Merge(合并)

把两个数组 合并为一个数组,新的数组长度是原来两个数组长度之和;

mergeDelayError()

在Observable中,一旦某一个时间抛出异常,后面的序列将会终止,如果我们希望在序列出错的时候不影响后面的序列,那么可以使用mergeDelayErroe()方法!

Zip

合并后的数组长度是两个数组长度最小的那一个数组的长度;而且我们可以自己定义合并的方式

Leak Canary 原理

1.通过registerActivityLifecycleCallbacks来监听Activity的生命周期。
2.监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。
3.如果Activity泄露了,就抓取内存dump文件(Debug.dumpHprofData)。
4.接着通过HeapAnalyzer来进行内存泄漏分析。
5.最后通过DisplayLeakService进行内存泄漏的展示。

Glide流程简析

开始---生成图片缓存key---创建缓存对象LruResourceCache---从内存缓存获取图片缓存---开启加载图片线程---从磁盘缓存中获取---从网络获取图片资源---写入磁盘缓存---图片加载完成---写入内存缓存---显示图片---结束

缓存

缓存图片资源:

缓存读取顺序:内存缓存 --> 磁盘缓存 --> 网络

内存缓存

LruCache(近期最少使用的算法)
弱引用

弱引用的对象具备更短生命周期,因为 **当JVM进行垃圾回收时,一旦发现弱引用对象,都会进行回收(无论内存充足否),用于存储正在显示的图片数据。

磁盘缓存

可缓存原始图片 & 缓存转换过后的图片,用户自行设置,自定义的DiskLruCache算法。

EventBus简析

Register流程

开始---根据订阅者类名查找当前订阅者的所有事件响应函数---循环每个事件的响应函数---根据优先级将当前订阅者信息插入到订阅者队列subscriptionsByEventType中---得到当前订阅者订阅的所有事件队列,将此事件保存到队列typesBySubscriber中,用于后续取消订阅---若为Sticky事件,则取出事件,post此事件给当前订阅者---结束

Post流程

这里的核心是使用了ThreadLocal去做数据存储。而真正执行方法就是通过反射调用了订阅者的订阅函数并把event对象作为参数传入。

ThreadLocal:是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而这段数据是不会与其他线程共享的。其内部原理是通过生成一个它包裹的泛型对象的数组,在不同的线程会有不同的数组索引值,通过这样就可以做到每个线程通过get() 方法获取的时候,取到的只能是自己线程所对应的数据。

不同的threadMode在不同的线程里invoke()订阅者的方法,ThreadMode共有四类:

PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;
MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理——调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。

Unregister流程

最终分别从typesBySubscriber和subscriptions里分别移除订阅者以及相关信息即可。

总结

实际上在EventBus里我们可以看到不仅可以使用注解处理器预处理获取订阅信息,EventBus也会将订阅者的方法缓存到METHOD_CACHE里避免重复查找,所以只有在最后invoke()方法的时候会比直接调用多出一些性能损耗,但是这些对于我们移动端来说是完全可以忽略的。

优化

Code Review

团队做Review能有效提高自己的代码质量和功能的稳定性。

清理操作

1.是否调用Handler的removeCallbacksAndMessages(null)来清空Handler里的消息;
2.是否取消了还没完成的请求;
3.在页面里注册的监听,是否反注册;
4.假如用了RxJava的话,是否解除订阅;
5.数据库的游标是否已经关闭
6.打开过的文件流是否关闭
7.使用完的Bitmap是否调用recycle()
8.WebView使用完是否调用了其destory()函数

优化代码

1.保存在内存中的图片,是否做过压缩处理再保存在内存里 否则可能由于图片质量太高,导致OOM
2.Intent传递的数据太大,会导致页面跳转过慢。太大的数据可以通过持久化的形式传递,例如读写文件
3.频繁地操作同一个文件或者执行同一个数据库操作,是否考虑把它用静态变量或者局部变量的形式缓存在内存里。用空间换时间
4.放在主页面的控件,是否可以考虑用ViewStub来优化启动速度
5.注意内存泄露问题
6.空指针问题

自测检查

1.思考某些情况下,某个变量是否会造成空指针问题
2.把手机横屏,检查布局是否有Bug
3.在不同分辨率的机型上,检查布局是否有Bug
4.切换到英文等外文字体下,检查外文是否能完整显示
5.从低版本升级上来,会不会有问题 比如可能会出现数据库不兼容的问题
6.按下Home再返回是否正常
7.熄灭屏幕再打开是否正常
8.切换成其它应用再切换回来会怎样
9.利用手机的开发者选项中的 “调试GPU过度绘制” ,“GPU呈现模式分析” 和 “显示FPS和功耗” 功能,看自己的新功能是否会导致过度绘制、是否会掉帧
10.测试看是否影响启动速度 adb shell am start -W 包名/Activity
11.对比看APK大小是否有增大
12.跑1小时Monkey,测试其稳定性

异形屏适配

需要适配的情况:
1.沉浸式风格

过度绘制优化

1.移除Window默认的background:getWindow.setBackgroundDrawable(null);
2.移除XML布局文件中非必需的Background;
3.减少布局嵌套;
4.可以用merge替代LinearLayout、RelativeLayout,这样子把UI元素直接衔接到include位置;
5.工具:HierarchyView查看视图层级。

App的冷启动优化

消除启动时的白屏/黑屏

在用户点击手机桌面APP的时候,看到的黑屏或者白屏其实是界面渲染前的第一帧,可以将Theme里的windowBackground设置成我们想要让用户看到的画面就可以了,这里有2种做法:

  1. 将背景图设置成我们APP的Logo图,作为APP启动的引导,现在市面上大部分的APP也是这么做的。
 <style name="AppWelcome" parent="AppTheme">
        <item name="android:windowBackground">@mipmap/bg_welcome_start</item>
    </style>

2.将背景颜色设置为透明色,这样当用户点击桌面APP图片的时候,并不会"立即"进入APP,而且在桌面上停留一会,其实这时候APP已经是启动的了,只是我们心机的把Theme里的windowBackground的颜色设置成透明的。

  <style name="Appwelcome" parent="android:Theme.Translucent.NoTitleBar.Fullscreen"/>

Application里面优化

1.不要让Application参与业务的操作。
2.不要在APPlication进行耗时操作,比如有些开发者会在自己的APP里一系列文件夹或文件(比如我自己),这些I/O操作应该放到"确实该使用的时候再去创建"亦或者是数据库的一些操作。
3.不要以静态变量的方式在Application中保存数据等。
4.使用一些库的初始化,可以使用IntentService去初始化。

ANR

ANR的全称是application not responding,意思就是程序未响应。
产生原因
1.主线程执行了耗时操作,在一定时间内没有响应操作。比如数据库操作或网络编程。
2.其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。
3.在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸)--主要类型。
4.BroadcastReceiver在10秒内没有执行完毕。
5.Service在特定时间内(20秒内)无法处理完成--小概率类型。
避免
1.避免在主线程上进行复杂耗时的操作,比如说发送接收网络数据/进行大量计算/操作数据库/读写文件等。这个可以通过使用AsyncTask或者使用多线程来实现。
2.broadCastReceiver 要进行复杂操作的的时候,可以在onReceive()方法中启动一个Service来处理。
3.在设计及代码编写阶段避免出现出现同步/死锁或者错误处理不恰当等情况。
修正
可以通过查看/data/anr/traces.txt查看ANR信息。根据日志文件的信息提示修改代码。

OOM

OOM,全称“Out Of Memory”,翻译为“内存用尽”当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出OOM。
常见情况
1.Acitivity没有对栈进行管理,如果开启过多,就容易造成内存溢出。
2.加载大的图片或者同时数量过多的图片的时候。
3.程序存在内存泄漏问题,导致系统可用内存越来越小。
4.递归次数过多,也会导致内存溢出。
5.频繁的内存抖动,也会造成OOM异常的发生,大量小的对象被频繁的创建,导致内存碎片,从而当需要分配内存的时候,虽然总体上还有内存分配,但是由于这些内存不是连续的,导致无法分配,系统就直接返回OOM了。
解决方法
1.减小对象的内存占用,避免OOM的第一步就是要尽量减少新分配出来的对象占用内存的大小,尽量使用更加轻量的对象。
2.内存对象的重复利用,大多数对象的复用,最终实施的方案都是利用对象池技术,要么是在编写代码时显式地在程序里创建对象池,然后处理好复用的实现逻辑。要么就是利用系统框架既有的某些复用特性,减少对象的重复创建,从而降低内存的分配与回收。
3.避免对象的内存泄露,内存对象的泄漏,会导致一些不再使用的对象无法及时释放,这样一方面占用了宝贵的内存空间,很容易导致后续需要分 配内存的时候,空闲空间不足而出现OOM,例如File、Bitmap、数据库、Cursor等。
4.内存使用策略优化。在某些情况下,我们需要事先评估那些可能发生OOM的代码,对于这些可能发生OOM的代码,加入catch机制,可以考虑在catch里面尝试一次降级的内存分配操作。

内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
常见情况
1.单例造成的内存泄漏
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
2.非静态内部类创建静态实例造成的内存泄漏,因为非静态内部类会持有外部对象强引用。
3.Handler造成的内存泄漏
当Activity结束时,未处理的消息持有handler的引用,而handler又持有它所属的外部类也就是MainActivity的引用。这条引用关系会一直保持直到消息得到处理,这样阻止了MainActivity被垃圾回收器回收,从而造成了内存泄漏。
4.线程造成的内存泄漏
若线程持有Activity的引用,且线程处于后台执行状态,当Activity销毁但线程仍然存活且持有Activity的引用,则会引起内存泄漏。
5.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。
6.使用ListView时造成的内存泄漏
初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的View对象,同时ListView会将这些View对象缓存起来。当向上滚动ListView时,原先位于最上面的Item的View对象会被回收,然后被用来构造新出现在下面的Item。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的Item的View对象(初始化时缓存中没有View对象则convertView是null)。构造Adapter时,没有使用缓存的convertView。
7.集合容器中的内存泄露
我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
8.WebView造成的泄露
当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其长期占用的内存也不能被回收,从而造成内存泄露。
防止内存泄漏的措施
1.在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context,当然它并不是万能的,对于有些地方则必须使用Activity的Context。
2.对于需要在静态内部类中使用非静态外部成员变量(例如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
3.对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
4.保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
5.对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以采用以下方式避免内存泄漏:
① 将内部类改为静态内部类
② 静态内部类中使用弱引用来引用外部类的成员变量

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

内存泄漏:主要是因为程序存在BUG 导致内存没有释放 。
内存溢出:是指内存不够用了,导致不够用的原因很多,内存泄漏是导致内存溢出原因一种。

进程保活

1.开启后台服务播放循环播放一段无声音乐。
2.点击home键使app长时间停留在后台,内存不足被kill
处理这种情况前提是你的app至少运行了一个service,然后通过Service.startForeground() 设置为前台服务,可以将oom_adj的数值由4降低到1,大大提高存活率。
3.在大多数国产手机下,进入锁屏状态一段时间,省电机制会kill后台进程
注册广播监听锁屏和解锁事件, 锁屏后启动一个1像素的透明Activity,这样直接把进程的oom_adj数值降低到0,0是android进程的最高优先级。 解锁后销毁这个透明Activity。
4.在app的设置界面加一个选项,提示用户自己去勾选自启动,设置为白名单,设置电池管理 。

代码加密防止反编译

1.代码混淆技术(ProGuard) 该技术主要是进行代码混淆,降低代码逆向编译后的可读性,但该技术无法防止加壳技术进行加壳(加入吸费、广告、病毒等代码),而且只要是细心的人,依然可以对代码依然可以对代码进行逆向分析,所以该技术并没有从根本解决破解问题,只是增加了破解难度。
2.签名比对技术。该技术主要防止加壳技术进行加壳,但代码逆向分析风险依然存在。而且该技术并不能根本解决被加壳问题,如果破解者将签名比对代码注释掉,再编译回来,该技术就被破解了。
3.NDK .so动态库技术,该技术实现是将重要核心代码全部放在C文件中,利用NDK技术,将核心代码编译成.so动态库,再用JNI进行调用。该技术虽然能将核心代码保护起来,但被加壳风险依然存在。

  1. 动态加载技术,该技术可以有效的防止逆向分析、被破解、被加壳等问题,动态加载技术分为以下几步:

Apk瘦身

1.classes.dex:通过代码混淆,删掉不必要的jar包和代码实现该文件的优化;
2.资源文件:通过Lint工具扫描代码中没有使用到的静态资源;
3.图片资源:使用tinypng和webP;
4.SO文件将不用的去掉,目前主流app一般只放一个arm的so包。

链接

以上资料有部分收集于各位大神的分析文章中,感谢以下大神的付出!(如有侵权,联删)

上一篇下一篇

猜你喜欢

热点阅读