android基本功

这几天一直在看Android一些基本知识,都比较零散,先各个知识点分析记录总结着,等日后整理成各个模块。也为自己以后面试做准备吧,未完待续。

一、 Android四大组件

1. Application相关

1.1 Application实例

在一个Dalvik虚拟机里只会存在一个实例,一个app可以有多个Dalvik,每个Dalvik都会存在一个Application实例,这就是多进程模式。

1.2 Application的继承关系

Application实质上是一个Context,它继承自ContextWrapper,而ContextWrapper继承自Cotext,对Context的一个包装。

1.3 Application生命周期

应用启动时,会先调用Application.attach()方法,再调用Application.onCreate()方法
但别在此方法中进行太多耗时操作,否则会影响app启动速度

1.4 怎么获取Application实例

获取Application对象可通过Context的getApplicationContext()或是通过Activity.getApplication()\Service.getApplication,三者获取到的都是同一个Application对象。

1.5 为什么最好不要在Application对象中缓存数据

在低内存情况下,Application可能被销毁,而Activity栈并没有为空,这时如果要恢复的Activity用到了Application中缓存的数据,很可以报空指针异常。解决办法,如果在Application保存数据,最好先判空,并将数据持久化保存,在使用时如果为空再从手机中读取。

2. Activity生命周期

android基本功_第1张图片
1) 整个的生命周期,从onCreate(Bundle)开始到onDestroy()结束。
2) 可见的生命周期,从onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。
3) 前台的生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有 Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换。
4) 从界面A跳转到界面B,生命周期变化情况:
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。
5) 对于栈最顶上的界面A,按Back键和按Home键的区别:
如果按下Back键,系统返回到桌面,并依次执行A:onPause -> A:onStop -> A:onDestroy。
此时如果按下Home键(非长按),系统返回到桌面,并依次执行A:onPause -> A:onStop。由此可见,Back键和Home键主要区别在于是否会执行onDestroy。

3. Activity的4种启动模式

3.1 standard:默认的标准启动模式

不管有没有已存在的实例,都生成新的实例。即使是A startActivity A,也会重新生成一个新的实例,再回退时,A也会出现两次;

3.2 singleTop

如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例,如A启动A,不会生成新的实例,会走A的onNewIntent方法,而不是onCreate方法,回退时,也只会回退一次;

3.3 singleTask

所在Activity栈中有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前,这一般用在程序的主界面上;

3.4 singleInstance

当被启动时,系统会首先判断系统其他栈中是否已经存在此Activity实例,有则直接使用,并且其所在的Activity栈理论上只有它一个Activity元素。
singleInstance表示该Activity在系统范围内“实例唯一”。ingInstance和singleTask主要区别在与系统范围内的“实例唯一”还是当前Activity栈“实例唯一”。

4. Activity的状态保存

4.1 onSaveInstanceState()、onRestoreInstanceState()

Activity的状态保存onSaveInstanceState()、onRestoreInstanceState()
Activity的 onSaveInstanceState() 和onRestoreInstanceState()并不是生命周期方法,当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState()会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,开发者可以覆写onSaveInstanceState()方法。

4.2 如何保存数据

onSaveInstanceState()方法接受一个Bundle类型的参数, 开发者可以将状态数据存储到这个Bundle对象中, 这样即使activity被系统摧毁, 当用户重新启动这个activity而调用它的onCreate()方法时, 上述的Bundle对象会作为实参传递给onCreate()方法, 开发者可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态。如在横竖屏切换时,应用的Activity可能要保存edittext中的值,需要调用onSaveInstanceState()来何存输入值。

5. Activity启动方式

5.1 startActivityForResult会立马执行情况

如果被启动的与Activity在AndroidManifest.xml文件中配置的launchMode设为”singleTask”的时候,startActivityForResult执行后,onActivityResult立即会被执行到。
原因:startActivityForResult(Intent,int,Bundle)有说明:if the activity you are launching uses thesingleTask launch mode, it will not run in your task and thus you willimmediately receive a cancel result.因为Activity1用startActivityForResult启动Activity2, 如果一个Activity2的启动方式是singleTask时,若Activity2之前在任务栈中存在,再次启动它时,它会把它上面所有的Activity实例都pop出栈,这样再调setResult时,返回的是Activity2任务栈下面的界面,而不是Activity1,所以,Activity1收到的一个cancel的Result。

5.2 要求requestCode>0

startActivityForResult的requestCode值必须要大于等于0,不然,startActivityForResult就变成了 startactivity。

6. Activity的isFinish/finish()和destroy的区别

activity的finish()方法有两个层面含义:将此Activity从Activity栈中移除;调用了此Activity的onDestroy方法。
1) finish()方法用于结束一个Activity的生命周期,但是并没有释放他的资源。
2) onDestory()方法则是Activity的一个生命周期方法,其作用是在一个Activity对象被销毁之前,Android系统会调用该方法,用于释放此Activity之前所占用的资源。
有可能程序在运行了finish()方法而没有运行onDestory()方法,因为Activity生命周期里的方法,不确定什么时候会被调用,皆是由系统确定;
3) isFinishing方法来判断Activity是否处于销毁状态。

7. Activity和Fragment的区别

一个fragment必须总是嵌入在一个activity中,同时fragment的生命周期受activity而影响,一个Activity可以运行多个 Fragment,一个fragment也可以在多个activity中作为一个模块,fragment有自己的生命周期,接收自己的输入事件,可以从运行中的activity添加或移除。
Activity 的后退栈由系统管理,而 Fragment 的后退栈由所在的Activity 管理。

8.Broadcast广播相关

8.1 Broadcast的生命周期

广播接收器仅在它执行这个方法时处于活跃状态。当 onReceive() 返回后,它即为失活状态。

8.2 Broadcast的两种注册方式

1) 静态注册,在AndroidManifest.xml中用标签声明注册,并在标签内用标签设置过滤器。

2) 动态注册:动态地在代码中先定义并设置好一个 IntentFilter对象,然后在需要注册的地方调 Context.registerReceiver()方法,如果取消时就调用Context.unregisterReceiver()方法。如果用动 态方式注册的BroadcastReceiver的Context对象被销毁时,BroadcastReceiver也就自动取消注册了。
注意事项:若在使用sendBroadcast()的方法是指定了接收权限,则只有在AndroidManifest.xml中用标签声明了拥有此权限的BroascastReceiver才会有可能接收到发送来的Broadcast。

9. Service服务相关

9.1 定义

一个专门在后台处理时间任务,没有UI界面;

9.2 Service的生命周期

android基本功_第2张图片① 通过bindService启动的Service生命周期:onCreate()->onBind()->onUnbind()->onDestory()
② 通过startService启动的Service生命周期:onCreate()->onStartCommand()->onDestory()

9.3 Service两种启动方式和区别:bindService, startService

① startService只是启动Service,与启动的组件没有关联,当Service调用stopSelf或是相关组件调用stopService服务才会终止;
② bindService,和组件绑定,其他组件可通过回调获取Service代理来和Service交互,当启动方销毁,Service也会调用unBinder进行解绑,只有所有绑定该Service的组件都解绑了,Service才会销毁。

9.4 Service和Activity怎么进行通信

9.5 Service与IntentService

(1) Service:与启动它的应用位于同一个进程中; Service还是在主线程中跑,所以不应该在Service中直接处理耗时的任务;
(2) IntentService:IntentService是Service的子类,比普通的Service增加了额外的功能。
1) 它会创建独立的worker线程来处理所有的Intent请求;
2) 会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
3) 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
4) 为Service的onBind()提供默认实现,返回null,故适合用startService来启动;
5) 为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;

10. Fragment相关

10.1 Fragment的生命周期

android基本功_第3张图片

10.2 Fragment之间数据传递方式

10.2.1 通过共同的Activity传递

在Activity中定义一个字段,然后添加set和get方法,在相应的Fragment中只要采用(XXXActivity(getActivity)).getXXX();方法叉可以获取到变量。

10.2.2 使用bundle进行参数传递

DemoFragment demoFragment = new DemoFragment();  
    Bundle bundle = new Bundle();  
    bundle.putString("key", "这是方法二");  
    demoFragment.setArguments(bundle);  
    ft.add(R.id.fragmentRoot, demoFragment, SEARCHPROJECT);  
    ft.commit();  

10.2.3 采取接口回调的方式

实现步骤如下:
1) 在FragmentA中创建一个接口以及接口对应的set方法:

public interface OnDataTransmissionListener {
  public void dataTransmission(String data);
}
public void setOnDataTransmissionListener(OnDataTransmissionListener mListener) {
  this.mListener = mListener;
}

2) 在FragmentB中实现此接口,并在相应的地方调用setXX方法

10.2.4 使用RxBus或是EventBus

10.3 Fragment与Activity之间数据传递方式

10.3.1 通过Bundle传值

10.3.2 使用RxBus或是EventBus

10.3.3 采取接口回调的方式

二、 开发中遇到的一些问题

1. app被杀死怎么启动

1.1 场景

用户按了home键,系统内存不足,导致应用被强杀;

1.2 可能引起的问题

当类中设置了static变量而在A类中初始化,在B类页面调用,应用被强杀时,再点击应用回到B类,由于static尚未初始化,会导致空指针,应用崩溃;

1.3 被强杀后应用变量情况

当app在后台被强杀后,app中所有变量都被清空(包括application实例),整个app进程都被销毁,但Activity的栈信息依然保存着,所以在回到应用时还能得知页面的打开顺序;就用被强杀时,会自动调用onSaveInstance方法保存一些核心变量。

1.4 解决办法

1.4.1 如何判断应用是否被强杀

可以在Application中设置一个静态变量,并赋一个初始值,在应用的启动页,对Application中的静态变量进行重新赋其他值,在其他Activity进行检测该值,如果是Application中赋的初始值,说明应用被强杀了直接恢复该Activity,如果是启动页重新赋过的值,则说明应用是正常启动的;

1.4.2 应用被强杀后该如何处理避免Crash

1) 将需要的变量,保存在手机本地一份,如SharedPerference或是数据库/sd卡等,在Application的onCreate()中进行相应静态变量的初始化操作,如用户Id等信息,保证其他Activity使用的变量不会因为应用重新初化Applciation而静态变量变为空;
2) 如果应用中有BaseActivity,可以在BaseActivity的onCreate()函数中进行判断应用是否被强杀,如果应用被强杀,重新启动应用,重在应用流程。
if(isForceKilled) {
startActivity(new Intent(this, SplashActivity.class));
}
3) Activity中跳转的变量尽量采用Intent传值,少用静态变量;

2. 如何完全退出一个应用

2.1 Activity管理栈

做一个自定义的栈来管理相应的Activity,需要退出时再pop出栈,相应Activity finish。

2.2 BaseActivity + RxBus(EventBus)

如果采用Rxjava开发,直接采用RxBus, 也可以自己写个广播,所有的类都继承自BaseActivity。
直接用RxBus发送退出应用的消息,在BaseActivty接收消息,再调用finish();所有栈内继承于BaseActivity的Activity都会接收到事件,并调用finish结束Activity。在finish之后还可以使用System.exit(0),这样会提示虚拟机kill掉进程。

3. 为什么service里startActivity必须添加FLAG_ACTIVITY_NEW_TASK否则抛异常,而Activity里不会?

3.1 启动activity的两种方式

一种是直接调用context类的startActivity方法,这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须添加上FLAG_ACTIVITY_NEW_TASK这个Flag,服务就是通过context调用;
另一种方式是调用被Activity类重载过的tartActivity方法;

3.2 两种启动方式为什么会不同

Activity是继承自ContextThemeWrapper,而service是继承自ContextWrapper,ContextWrapper里是通过调用ContextImpl的startActivity方法,它会判断intent.getFlags有没有还FLAG_ACTIVITY_NEW_TASK标记。而ContextThemeWrapper调用startActivity方法不会去检查标记。最后它们都通过Instrumentation辅助类的ActivityManagerNative这个方法。

三、 Android自定义View

1. 事件分发机制

1.1 事件传递

ViewGroup接收到事件后进行事件分派(dispatchTouchEvent),如果自己需要处理事件则拦截(interceptTouchEvent返回true),调用自己的onTouchEvent进行事件处理;不处理(interceptTouchEvent返回false)则传给子view进行处理,再由子view进行分派(dispatchTouchEvent),处理/拦截;

1.2 事件处理

子View的onTouchEvent进行事件处理,若返回true,则消耗事件,不再继续传递;返回false则不处理,把这个事件往上一级的viewGroup进行传递,由上一级进行处理。

1.3 view的dispatchTouchEvent

1) view之所有dispatchTouchEvent方法,因为view可以注册很多事件监听器,如onClick, onLongClick, onTouch, view自身的onTouchEvent方法,这么多事件需要管理者dispatchTouchEvent。所以view也会有事件分发。
2) view事件相关的方法调用顺序:onTouchListener > onTouchEvent > onLongClickListener > onClickListener

1.4 注意点

1) 不论view自身是否注册事件,只要view可点击就会消费事件,如view注册了onClickListener\ onLongClickListener\ onContextClickListener\ android:clickable = “true”;
onTouchListener返回false才会调用onClickListener
2) 即使给view注册了onTouchListener也不会影响view的可点击状态,只要不返回true就不会消费事件;
3) ViewGroup和ChildView同时注册了事件监听器,由ChildView消费;
4) 一次触摸流程中产生事件应被同一个view消费,全部接收或全部拒绝;

2. view绘制原理

view绘制到手机屏幕上需要经过三个步骤:measure(测量控件大小)、layout(view摆放位置)、draw(view的绘制)

2.1 view的测量measure

1) 作用:测量视图大小,即视图宽高,在view中measure为final型,子类不能修改,它内部会调用onMeasure(),视图大小最终在这里确定,调用setMeasuredDimension保存计算结果;
2) MeasureSpec:view的测量模式MeasureSpec封装的是父容器传递给子容器的布局要求,MeasureSpec是由父view的MeasureSpec和子view的LayoutParams(定义view的layout_width\layout_height)通过计算得出子view的测量要求。
MeasureSpec是由一个32位整型数组成,高2位是mode,后面30位是size。
1) mode一共有三种模式:UNSPECIFIED:父容器对子容器没有任何限制,任意大;EXACTLY:父容器为子容器设置了尺寸,子容器应该服务这些边界;AT_MOST:子容器可以声指指定大小内的任意大小,最大不得超过指定大小。
2) measure会遍历整棵view树,测量每个view真实尺寸,ViewGrou向它内部的每个子view发送measure命令,子view在onMeasure()函数来测量自己的尺寸,再调用setMeasuredDimension()方法将测量结果保存至View的mMeasuredWidth和mMeasureHeight中,单位是像素。

2.2 layout

layout的主要作用 :根据子视图的大小以及布局参数将View树放到合适的位置上。
ViewGroup:1)根据parent传递的位置信息,设置自己的位置;2)根据自己的layout规则,为每一个子view计算出准确的位置,并将layout流程传递给子view
View:根据parent传递的位置信息,设置自己的位置

2.3 draw

根据measure, layout得到的参数将视图显示在屏幕上,子类不能修改此方法;
绘制步骤:
1)绘制背景(drawBackground)
2)保存canvas的layer,准备fading;
3)绘制view的content(onDraw方法);
4)绘制children(dispatchDraw方法);
5)绘制fading edges,再还原layer;
6)绘制装饰器,如scrollBar

四、性能方面

1、 耗电太多怎么优化

1.1 应用耗电主要体现在以下几方面

1) 常规使用:大数据量的传输(IO),不停的在网络间切换(网络请求操作);解析大量的文本数据;
2) 对设备的频繁唤醒:在Android中AlarmManager可唤醒设备,WakeLock能让CPU保持唤醒状态,这两者不恰当使用,没有合理释放掉,将会使系统长时间无法进入休眠,导致高耗电;
3) GPS:对GPS设备的使用控制,GPS实时性所以电量消耗也比较严重;
4) 传感器和屏幕;

1.2 优化途径

1) 在需要网络连接的程序中,先检查网络连接是否正常,若没有网络连接,就不需要执行相应程序;在蜂窝移动网络下,批量执行网络请求,尽量避免频繁间隔网络请求;应该尽量减少移动网络下数据传输,多在WiFi环境下传输数据;
2) 使用效率高的数据格式和解析方法,如JSON和Protobuf;
3) 在进行大数据下载时,尽量使用GZIP方式下载;
4) 回收Java对象,特别是较大的java对象,使用reset方法,对定位要求不太高的不要使用GPS定位,使用wifi和移动网络cell定位;尽量不要使用浮点运算;获取屏幕尺寸等信息可以使用缓存技术;使用AlarmManager定时启动服务替代使用sleep方式的定时任务。
5)使用JobScheduler来管理后台工作,将一些不是特别紧急的任务放到更合适的时机批量处理;
6) 监控电池当前的电量和充放电状态,在程序中做相应参数设置以减少耗电量。

2. 怎么统计crash

可集成第三方sdk进行统计,如友盟、腾讯Bugly,可以分析出每个版本出现的bug数,每个bug出现的时间、频次、机型、应用版本、bug出现的位置;计算bug率可以通过计算bug的次数/应用启动次数

3. 怎么减少用户流量消耗

3.1 流量怎么监控

1) Android Studio自带的Network Monitor可看出时间段内网络请求数量及访问速率;
2) 使用代理工具,如Wireshark, Fiddler, Charles,可以查看每一次网络请求,传输的数据量;

3.2 怎么优化手机流量使用

1) 使用GZIP压缩进行数据传输,不仅可以减少流量消耗,还可减少传输时间;
2) 使用IP直连,而不用域名,省去DNS解析过程;
3) 图片方面:图片使用WebP格式,可节省流量;使用压缩图,图片按需加载,只有用户查看大图时才加载原图;图片上传时,采用分片传输,每个分片失败重传,根据网络类型及传输过程中的变化动态修改分片大小;
4) 协议层优化,Http1.1引入了“持久连接”,多个请求被复用,无需重建TCP连接;Http2引入了“多头”、头信息压缩、服务器推送等;
5) 合并网络请求,减少请求次数(这样头信息仅需上传一次,减少流量也节省了资耗);
6) 网络缓存,对服务端返回数据缓存,设定有效时间,也可以使用第三方库如Okhttp, Volley
7) 断点续传,重试策略,Protocol Buffer;避免客户端轮询,使用服务器推送方式,数据采用增量更新,应用可设置仅在wifi环境下升级应用;善用缓存服务;在WIFI环境下下载文件;使用离线地图;暂时关闭同步;压缩数据

4. 方法数超过65535的解决办法

4.1 出现原因

1) android中单个dex文件能包含的最大方法数65535,类的数量也有这个限制,这包含android framework, 依赖的jar包及应用本身的代码中所有的方法。
2) 超过了LinearAlloc缓冲区大小,应用在安装时,系统会通过dexopt来优化dex文件,在优化过程中dexopt采用了一个固定大小的缓冲区来存储应用中所有的方法信息,该缓冲区就是LinearAlloc。它在新版本中是8mb或16mb,但在android2.2,2.3上只有5mb。有时方法数没超,但是存储空间超过了5mb,dexopt程序会报错,导致安装失败。

4.2 解决方案

1) 删除无用的代码和第三方库
2) 利用插件来动态加载部分dex
3) multidex解决方案(可从apk中加载多个dex文件)
使用方法:build.gradle中添加multidex的依赖;
让自定义的Application继承MultidexApplication(在attachBaseContext方法中加入multiDex.install(this)),或是在mainfest文件的Application直接设成MultidexApplication。

4.3 multidex存在的问题

1) 应用启动速度会降低,由于应用启动时会加载额外的dex文件,将会造成启动速度除低,还可能造成ANR,所以尽可能第二个dex不要太大;
2) 由于dalvik LinearAlloc的bug,这可能导致multidex的应用无法在android 4.0之前的手机上运行。

4.4 针对multidex的问题,可改进的地方

1) 启动时间过长
Application.attachBaseContext是在Application.onCreate()之前运行的,如果首次启动Dalvik虚拟机会对classes.dex执行dexopt操作,生成ODEX文件,此过程非常耗时,而执行MultiDex.install()必然会再次对class2.dex执行dexopt等操作,所以如果首次启动应用可以将MultiDex.install(context)放在子线程中执行;
2) ANR/Crash
在classes2.dex没有加载完,程序调用了classes2.dex中的类或方法,关于MultixDex编译过程相关的任务主要有:
collectDebugMultiDexComponents:扫描AndroidMainfest.xml中的application, activity, receiver, provider,service等相关类,将信息写入mainfest_keep.txt中;
shinkDebugMultiDexComponents:再压缩,根据proguard规则及mainfest_keep.txt文件进一步优化mainfest_keep.txt,将没有用到的类删除,最终生成componentClasses.jar文件;
createDebugMainDexClassList: 上一步生成的componentClasses.jar文件中的类,递归扫描这些类所有相关的依赖类,最终形成maindexlist.txt文件,最终打包进classes.dex中。
如果遇到找不到类的情况,只需要将该类的完整路径添加到maindexlist.text中即可。

5. anr

5.1 android怎么定义应用出现ANR

1) 应用在5秒内未响应用户的输入事件;
2) BroadcastReceiver未在10秒内完成相关的处理;

5.2 出现场景

1) 主线程阻塞、挂起、死循环;应用进程的其他线程CPU占用率高,使主线程无法抢占CPU时间片;
2) 其他应用进程抢占CPU时间片,使当前应用无法抢占到;其他进程CPU占用率高;当前应用进程进行进程间通信请求其他进程,其他进程操作长时间没有反馈等;

5.3 从哪可以查看到引起的原因

当前运行的程序发生ANR时,会将函数的堆栈信息输出到/data/anr/trace.txt文件中,最新的ANR信息在最开始部分;
后台的ANR一般不弹框,可以在开发者选项里勾选“应用程序无响应”即可对后台ANR也进行弹窗显示。

5.4 如何避免

1) 使用AsyncTask或是Thread/HandlerThread来进行耗时操作,还可设置Thread的优先级设置成Process.THREAD_PRIORITY_BACKGROUND;
2) 使用Handler处理工作线程结果,而不使用Thread.wait()或Thread.sleep()来阻塞主线程;
3) 在onCreate和onResume回调中尽量避免耗时操作;
4) BoardCastReceiver的onReceive代码要尽量减少耗时,可使用IntentService处理。

6. listview优化

6.1 每次getView执行时间最长不得超过多少否则给用户卡顿感觉

1S内屏幕大概绘制30帧,用户才会觉得它比较流畅,每帧可用时间为33.33ms,每一屏一般要6个ListItem,加上一个重用的convertView:33.33ms/7=4.76ms,即每个getView要在4.76ms内完成工作才会较流畅。但每个getView间的调用会有一定间隔,所以留给getView使用的时间应该控制在4ms内,否则滑动ListView会有卡顿的感觉。

6.2 优化方法

1) 使用ViewHolder+converView模式,这样避免每次在调用getView时,都重新inflate一个view,通过findViewById实例化数据;
2) adapter中有耗时操作,异步加载;
3) 为图片设置缓存;图片加载可使用第三主库如Glide, Picasso
4) ListView滑动时停止加载图片和分页加载;
5) 在adapter的getView中尽可能减少逻辑判断;
6) 将ListView的scrollingCache和animateCache的属性设置为false;
7) 尽可能减少List Item的Layout层次,如可用RelativeLayout替换LinearLaout

7. bitmap怎么避免OOM

7.1 加载大图时,可先对图片进行压缩

在一个很小的ImageView上显示一张大图,是不值当的。BitmapFactory提供了多个解析方法(dcodeByteArray, decodeFile, decodeResource)用于创建Bitmap对象,它提供了一个可选的BitmapFactory.Options参数,将inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存,返回值不再是一个bitmap,而是null,但BitmapFactory.Options的outWidth, outHeight和outMineType属性都会被赋值,此时可根据图片情况进行压缩。能过设置BitmapFactory.Options中的inSampleSize值就可以实现。

7.2 使用图片缓存技术

LrcCache来缓存图片,LruCache

7.3 选择RGB_565或是ARGB_4444的编码格式,ARGB_8888的编码格式占4个字节。

7.4 android系统怎么判断发生OOM

在Android2.x系统: dalvik allocated + external allocated + 新分配的大小 >= getMemoryClass值时就会发生OOM
在Android4.x系统废除了external的计数器bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= getMemoryClass()就会发生OOM;

7.5 其他避免OOM办法

使用轻量级数据结构,如用SparseArray代替HashMap, int代替Integer等;少用枚举;
内存对象复用,ListView/recyclerView使用viewholder;用StringBuilder/Stringbuffer代替String字符串拼接;减少内存泄漏

8. android UI性能优化

8.1 卡顿(Jack)

安卓设备的屏幕刷新率一般是60帧每秒,所要要渲染的内容能在16ms内完成,每丢一帧,用户就会感觉动画在跳动。

8.2 Choreographer帧率检测方案

8.2.1 VSync信号及作用

Android系统一帧绘制完成,显示器会发出一个垂直同步信息(vertical synchronization)VSync信号,显示器通常以固定频率进行刷新,这个刷新率就是VSync信号产生的频率。为解决GPU和视频控制器不同步,GPU有一个垂直同步(V-Sync)来通知界面进行渲染、绘制,每一次同步周期为16.6ms,代表一帧的刷频率,一次界面渲染会回调doFrame方法,如果两次doFrame之间的间隔大于16.6ms,说明发生了卡顿,可保存两次doFrame时间进行相减再除以刷新频率,这样算出来的结果就是两次doFrame的掉帧数。

8.2.2 Choreographer怎么检测掉帧

Choregrapher对VSync信号做了监听,当有VSync消息的时候会执行onVsync,最终走到Choregrapher的doFrame,这里会将callback队列中取出runnable进行绘制。
通过Choreographer设置它的FrameCallback,在每一帧被渲染时记录下它开始渲染的时间,在下一帧渲染过程中判断是否出现掉帧。
3)Android的一个显示周期可表现为以下几部分:
CPU+GPU绘制帧数据,绘制结束的数据存放在缓冲区BufferQueue中;
SurfaceFlinger从BufferQueue中取数据并计算;
Choreographer完成最终绘制;

9. android app启动过程及优化

9.1 App应用启动方式

9.1.1 冷启动

后台没有被启动应用的进程,系统会重新创建一个新的进程分配给当前应用(AMS调用startProcessLocked()方法创建新进程)。它会先创建和初始化Application类,再将进程和指定的Applciation绑定起来,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。
Launcher是一个进程,被启动的应用是另一个进程,这里涉及到跨进程调用,Launcher通知Binder通过ActivityManagerService来启动另一个应用Activity.
具体可控制流程:
Application的构造方法——》attachBaseContext() ——》onCreate() ——》Activity构造方法->onCreate() –> 配置主题中背景 –》 onStart() –> onResume() –> 测量布局绘制显示界面。

9.1.2 热启动

后台已有被启动应用的进程,会直接从已有的进程中来启动应用,不会再走Application,直接到MainActivity就行。
调用application线程对象中的scheduleLaunchActivity()发送一个LAUNCH_ACTIVITY消息到消息队列中,通过handleLaunchActivity()来处理该消息。

9.2 测量应用启动时间

应用启动时间是指从点击应用启动图标开始创建出一个进程直到看到界面上第一帧。
在命令行中运行命令:adb shell am start -W [packageName]/[packageName.MainActivity]
会有三个时间,ThisTime(调用过程中最后一个Activity启动时间到这个Activity的startActivityAndWait调用结束)、WaitTime(startActivityAndWait方法的耗时)、TotalTime(调用过程中第一个Activity的启动时间到最后一个Activity的startActivityAndWait结束)

9.3 app启动的优化(冷启动)

1) Application的创建过程中尽量少进行耗时操作
2) 如果用到SharePreference,尽量在异步线程中操作
3) 首次启动的Activity减少布局层次,并在生命周期回调的方法尽量减少耗时操作
4) 为主界面单独写一个主题style,并设置成启动图,然后在启动的Activity设置成主题,在Activity加载布局前把主题重新设置回来。

10. apk 瘦包机制

android基本功_第4张图片
上图是一般的apk安装包含的文件,如果加入混淆等还会有proguard.cfg、project.properties等文件,从图中可以看出AndroidManifest.xml、META-INF这些本身就很小没有必要做进一步压缩的文件,而其它文件或者文件夹都可以考虑进行优化,从而减小APK的体积。下面具体说说android apk的一些瘦包实用技巧:

10.1 使用lint减小resources.arsc体积

使用lint除一些无用的代码、无用的资源、无用的变量等;还可以借助lint消除一些隐藏的bug,从页减小resources.arsc体积,用法是:Android Studio→Inspect Code…对工程做静态代码检查;

10.2 代码混淆减少classes.dex大小

代码混淆可以减小该文件的大小,因为混淆后的代码将较长的文件名、实例、变量、方法名等等做了简化,从而实现字节长度上的优化;

10.3 压缩资源

1) 使用一些小图片代替大图,有些适配图片可能只需要保留一套如xxhdpi,有些背景图片用代码实现,用.9.png;
2) 使用tinypng压缩图片;
3) 对图片质量要求不是很严格,可以考虑不带alpha值的jpg图片、同等质量下文件更小的webP图片格式;
4) 借助微信提供的AndResGuard资源文件混淆工具对资源文件做混淆,进一步压缩资源文件所占用的空间;

10.4 去掉一些不用的适配

1) 如果只需要支持中文,可在build.grade中添加resConfigs “zh”去除无用的语言资源;
2) 去掉一些so包支持
一种CPU架构 = 一种ABI = 一种对应的SO库;
现在手机市场总共支持以下七种不同的CPU架构:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64, 这7种CPU类型对应的SO库的文件夹名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的.so文件,64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,所以应用不需要支持很多设备,建议实际工作的配置是只保留armable、armable-x86下的so文件,当然so包是向下兼容的,如果提供了其他cpu类型的文件夹,也需要在相应的文件夹里补全所有的so包,要不手机到时适配找不到合适的so包导致crash。

10.5 减小或慎用第三方依赖库

如果只使用第三方依赖库的少部分功能,可以考虑只提取少部分代码,而不是直接把第三库全部引入;
用较少的库替换大库;

10.6 插件化和动态加载

上面提到的so包,可以只提供一套,在应用运行时,需要用到so包的地方,可以从服务器下载so包,再动态加载;

10.7 使用7zip打包

五、源码或实现原理

1. 消息机制

handler消息机制,它可以将一个任务切换到handler所在的线程中去执行;handler需要MessageQueue\Looper\Message的支持;各自的任务是:
Handler–负责消息的发送、接收处理;
MessageQueue–消息队列,一个消息存储单位,经常需要进行增减,内部使用单链表结构;
Looper–消息循环,不停地从MessageQueue中取消息,如有新消息就会立即处理,否则一直阻塞;
Message–消息载体。

1.1 Handler是怎么接收到消息

1) Looper的prepare()会为当前线程创建一个Looper实例,该实例会创建一个消息队列MessageQueue对象,将线程设为当前线程;最后将该实例保存在当前线程中;
2) 构建Handler时,会先获取到当前Handler所在线程的Looper并得到其中的MessageQueue;
2) 使用Handler发送消息时,会将一个Message保存到当前线程Looper中的MessageQueue中;
3) 调用loop()方法会开启消息循环,不断从MessageQueue中取消息,当收到消息后,会调用msg.target.dispatchMessage(msg);来处理消息;最终会调用Handler的handlerMessage进行处理消息;

1.2 Handler是怎么发送消息

Handler的post一个Runnable对象和sendMessage一个Message对象

2. binder

2.1 Android为什么要采用Binder作为IPC机制

1)性能方面:Binder数据拷贝只需一次,而管道、消息队列、Socket都需要2次,共享内存不需要拷贝,性能解度,Binder仅次于共享内存;
2) 稳定性方面:Binder是基于C/S架构,架构清晰,稳定性好。共享内存实现方式复杂,没有客户和服务端之别,需要充分考虑访问临界资源的并发同步问题,否则会出现死锁等问题,故Binder稳定性优于共享内存;
3) 安全方面:传统的IPC接收方无法获得对方进程可靠的UID/PID,无法鉴别对方身份。而Binder机制的Server端可根据权限控制策略,判断UID/PID是否满足访问权限。

2.2 实现方法

1) 在服务端新建需要的AIDL类(声明以.aidl为后缀的接口类,并声明相应的方法),保存后会在gen目录下生成相应的java类,它是系统为我们声明的aidl接口生成的Binder类,继承自android.os.IInterface,自己也还是一个接口。
2) 编写服务端Service类,新建一个.Stub对象,在onBind方法中返回定义的aidl的Binder对象。
3) 在服务端注册服务;
4) 在客户端新建ServiceConnection对象,重写onServiceConnected方法和onServiceDisconnected方法,在onServiceConnected方法可以获取到服务端的Binder对象,从而可以调用服务端的方法进行通信。
5) 在客户端绑定service对象

3. LeankCanary检测内存泄漏原理

1) RefWatcher.watch()会以监控对象来创建一个KeyedWeakReference弱引用对象;
2) 在AndroidWatchExecutor的后台线程里,来检查弱引用是否被清除,没有则执行一次GC;
3) 若弱引用对象仍没有被清除,说明内存泄漏了,系统导出hprof文件,保存在app的文件系统目录下;
4) HeapAnalyzerService启动一个单独进程,使用HeapAnalyzer来分析hprof文件,它使用了另一个开源库HAHA.
5) HeapAnalyzer通过查找KeyedWeakReference弱引用对象来查找内在泄漏;
6) HeapAnalyzer计算KeyedWeakReference所引用对象的最短强引用路径,来分析内存泄漏,并构建出对象引用链出来;
7) 内存泄漏信息送回给DisplayLeakService,它是运行在app进程的一个服务,再在设备通知栏显示内存泄漏信息。

六、其他

1. 双亲委派机制

1.1 类加载器的作用

类加载器(class loader):用来加载Java类到Java虚拟机中。Java源程序(.java)在经过Java编译器后被转换成Java字节代码(.class)。类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例。

1.2 什么叫双亲委派

某个特定类的加载器在接到加载类的请求时,先将加载任务委托给父类加载器,依次递归(如果没有继续向上寻找父类加载器),如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成加载任务,才自己加载;
android中加载类的顺序:
自定义类加载器CustomClassLoader > 应用程序类加载器AppClassLoader > 扩展类加载器 ExtClassLoader > 启动类加载器(BootStrapClassLoader)

1.3 双亲委派的意义

防止内存中出现多份同样的字节码;只有当使用该class时才会去装载,一个classloader只会装载同一个class一次。

2. 子线程和UI线程

2.1 android为什么不能在子线程更新UI

因为Android的UI线程访问是非线程安全,没有加锁,这样在多个线程访问UI是不安全的, 所以Android中规定只能在UI线程中访问UI。

2.2 子线程一定不能更新UI线程么

当访问UI控件时,android是在ViewRootImpl的checkThread方法中检测当前线程是否是主线程,如果不是会报错。而ViewRootImpl的在WindowManagerGlobal的addView方法中创建,而addView方法是在在onResume方法回调之后。所以如果在onCreate方法中创建了子线程并访问UI,ViewRootImpl还没创建,无法检测当前线程是否是UI线程,程序可以在子线程中访问UI线程。

3. RxBus和EventBus

3.1 EventBus作为一个消息总线,主要有三个元素

1) Event:要发送的事件对象
2) Publisher: 事件发布者,用于通知Subscriber有事件发生。直接调用eventBus.post(Object)可以在任意线程任意位置发送事件,根据post函数参数类型,会自动调用订阅相应类型事件的函数。
3) Subscriber: 事件订阅者,接收特定事件,所有事件订阅都是以onEvent开头的函数,有onEvent, onEventMainThread, onEventBackgroundThread, onEventAsync这四个。与ThreadMode有关。

3.2 RxBus,结合Rxajva的便利

先要定义一个RxBus的类:
里边包含一个单例RxBus类对象:private static volatile RxBus instance;
一个final类型的Subject对象:bus = new SerilizedSubject<>(PublishSubject.create());
一个发送事件的方法:public void post(Object o){bus.onNext(o);}
一个接收事件的方法:public Observable toObservable(Class eventType){return bus.ofType(eventType);}
Subject同时充当了Observer和Observable角色,Subject是非线程安全,所以将它转成SerilizedSubject,包装成线程安全的Subject.

4.RecyclerView和listview的比较

1) ViewHolder模式————ListView通过创建ViewHolder来提升性能不是必须的,RecyclerView的Adapter必须至少一个ViewHolder,必须遵循ViewHolder设计模式;
2) Item布局————ListView只能实现垂直线性布局,RecyclerView可以实现垂直、水平、瀑布流等;
3) Item动画,ListView没有提供增删Item的动画,RecyclerView可以在RecyclerView.ItemAnimator来为条目增加动画效果;
4) 设置数据源————ListView对Adapter封装了一些数据,如ArrayAdapter, CursorAdapter,而RecyclerView.Adapter则必须实现RecyclerView.Adapter
5) 分隔线————ListView可以直接设置android:divider设置分割线,RecyclerView必须使用RecyclerView.ItemDecoration来设置;
6) 点击响应————ListView提供了AdapterView.OnItemClickListener接口,RecyclerView必须使用RecyclerView.OnItemTouchListener来响应条目触摸事件;
7) 缓存————ListView采用两级缓存,RecyclerView采用四级缓存
ListView的缓存,先判断数据源数据是否改变,a)没有则从mActiveViews中通过匹配pos读取view缓存;b)有则从mScrapViews中读取view缓存;再通过mAdapter.getView(pos, ScrapView, parent)将缓存用于getView(),若可重用,则避免createView耗时,无法避免bindView耗时;
RecyclerView的缓存,a) 从mAttachedScrap中通过匹配pos获取holder缓存,成功直接显示,b) 不成功从mCacheViews中匹配pos获取holder缓存,c) 不成功再从mViewCacheExtension中自定义获取holder缓存;d) 不成功再从mRecycledViewPool中获取holder缓存;成功则清除holder所有标志位,不成功则调用mAdapter.createViewHolder(parent, type)
ListView通过pos获取view, pos–>view
RecyclerView通过pos获取viewHolder,pos–>(view, viewHolder, flag)

5. Serializable和Parcelable的比较

5.1 设计作用

Serializable: 标记接口,为了保存对象的属性到本地文件、数据库、网络流、rmi以方便数据传输;
Parcelable:为了在程序内不同组件间及Android程序间高效的传输数据而设计,这些数据仅在内存中存在;

5.2 效率方面

Parcelable性能比Serializable好,因为后者在反射过程频繁GC
Parcelabe: 适合在内存中数据传输,如activity间传输数据;

5.3 使用方面

Serializable: 只需要实现Serializable接口
Parcelabe:需要实现writeToParcel, describeContents函数及静太CREATOR变量

6. java静态内部类和内部类区别

6.1 持有引用

非静态内部类会隐式持有一个外围类的引用,而静态内部类不会持有外部类的引用;

6.2 访问外部类

1) 非静态内部类能访问外围类的一切成员,包括私有变量,外部类不能直接访问内部类的成员,但可通过内部类的实例访问;静态内部类不能访问外围类的非公开成员,因为它们是两个不同的类

6.3 构造内部类

非静态内部类的构造需要借助外部类的实例进行构造:

OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();

静态内部类可以直接进行实例化:

OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();

7. TCP/IP五层通信体系结构

应用层(应用程序数据)》传输层(Segment/Datagram)传输的是TCP数据或是UDP数据 》网络层(Packet,路由) 》数据链路层(Frame,以太网交换机) 》物理层(Bit,集线器,双绞线)

你可能感兴趣的:(android专题)