目录
内容持续更新中…
内存相关
1 导致内存泄漏的原因有哪些?
内存泄漏的根本原因是:长生命周期的对象持有短生命周期的对象,短生命周期的对象就无法及时释放。
造成内存泄漏的原因有:
(1) 单例造成的内存泄漏
因为某些单例的初始化需要引入一个Context变量,在调用单例时会传入当前类的实例,而单例的对象是静态的,那么它将与Application拥有一样长的生命周期,如果当前类需要销毁,由于单例持有该类的引用,所以无法销毁,从而造成内存泄漏,代码如下:
public class Singleton{
private Singleton(Context context){
}
private static volatile Singleton instance = null;
public static Singleton getInstance(Context context){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton(context);
}
}
}
return instance;
}
}
解决方法:使用getApplicationContext或者对Activity的引用使用弱引用。
(2)非静态内部类实例化一个静态的对象
非静态内部类隐性持有外部类的对象,如果将内部类实例化为一个静态对象,那么它将与Application拥有一样长的生命周期,如果外部类需要销毁,由于内部类持有该类的引用,所以无法销毁,从而造成内存泄漏,代码示例如下:
public class MyActivity extends Activity{
private static InnerClass innerclass = new InnerClass();
protected void onCreate(Bundle savedInstanceState){
super.onCreate(saveInstanceState);
}
private class InnerClass{
}
}
解决办法有两个,一个是不要把对象实例化成静态的;另外一个是把内部类改为静态内部类,静态内部类不会隐性持有外部类对象
(3) Handler/Runnable造成的内存泄漏
Handler是一个非静态内部类,它隐性地持有外部类的对象。如果外部类需要结束,但消息队列中还有消息未处理完,则Handler不会释放外部类的对象,从而造成内存泄漏,代码示例如下:
Handler handler = new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);
}
};
解决方法:
- 将Handler/Runnable改为静态的,如果是Handler,则在onDestroy中调用removeCallbackAndMessage(null)方法。
- 如果Handler/Runnable持有外部类的对象,则应该改成弱引用。
(4)资源未关闭造成的内存泄漏
如使用流资源,比如InputStream,Database等,还有就是BroadcastReceiver未取消注册等。
2 导致内存溢出的原因有哪些?
内存溢出通俗理解就是内存不够用,内存溢出有以下几种原因:
- 从Java虚拟机角度来说
- Java堆溢出
Java堆用于存储对象实例的。只要不停创建对象,然后保证对象到GC Roots之间有可达路径来避免垃圾回收机制回收这些对象,那么对象数量达到最大堆容量后就会出现内存溢出,所以内存泄漏可能造成内存溢出。- 虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,则抛出StackOverflowError异常;如果虚拟机在扩展时无法请求到足够的内存空间,则抛出OutOfMemoryError异常。- 方法区和运行时常量池溢出
这类溢出经常出现在使用的第三方软件中的BUG。- 本地直接内存溢出
- 从App实际应用来说
- 图片造成内存溢出
- 内存泄漏导致的内存溢出
- ListView未复用ConvertView导致重复创建Item布局最终可能导致内存溢出
3 如何避免OOM?
- Bitmap优化
- 压缩图片尺寸,避免使用大图片
- 设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
- 使用StringBuilder替代String
有时候代码中会需要使用到大量的字符串拼接操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”。- 可以复用系统自带的资源,如字符串、图片、颜色、样式等;ListView和GridView中可以使用ConvertView复用子组件。
- 避免内存泄漏从而避免内存溢出
因为内存泄漏也可能导致内存溢出。- 避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC,甚至内存抖动
Android原理相关
1 理解Activity、View、Window三者之间的关系
可以说,Window加载在Acitivy上,View加载在Window上,它们之间的逻辑关系如下:
- Activity构造的时候会初始化一个Window对象,准确说是PhoneWindow对象。
- 这个PhoneWindow有一个DecorView,DecorView有一个唯一的子View,它是垂直的LinearLayout类型,包含两个子View,一个是TitleView,另一个是ContentView.
- ContentView是一个FrameLayout布局,通过setContentView方法来添加View。比如TextView、Button等。
- 这些View的事件监听是由WindowManagerService来接受消息,并且回调Activity函数,比如onClickListener,onKeyDown等。
具体可参考下图
2 Handler机制(消息机制、Handler原理)
可参考文章:Android消息机制
简单回答:
Handler是用来发送消息和处理消息的。Handler流程中最主要的有四个对象:Handler、Message、MessageQueue和Looper。我们将要发送的消息封装到Message里,然后通过Handler将消息发送到MessageQueue中,Looper不停地从MessageQueue里取出消息交给Handler进行处理,从而实现了线程之间的通信。
详细的回答:
Handler是用来发送消息和处理消息的,所以Handler的机制主要表现为消息机制。Handler流程中最主要的有四个对象:Handler、Message、MessageQueue和Looper。从消息发送到处理可分为以下四个阶段:
- 准备阶段
在子线程中Looper.prepare()方法或者在主线程中调用Looper.prepareMainLooper()方法创建当前的Looper对象。Looper通过loop()方法获取到当前线程的Looper并启动循环,从MessageQueue不断提取Message,如果MessageQueue没有消息,则处于阻塞状态- 发送消息阶段
使用当前线程创建的Handler对象在其它线程通过调用sendMessage()发送Message到MessageQueue中,MessageQueue收到消息,然后插入新的Message并唤醒阻塞。- 获取消息
Looper被唤醒,则重新检查MessageQueue并获取新插入的Message。Looper获取Message后,通过Message的target,即Handler调用dispatchMessage方法分发获取到的消息,然后Handler使用handleMessage方法处理消息。- 阻塞等待
当MessageQueue没有消息时,重新进入阻塞状态。
3 Android事件分发
当发生了一个事件时,会将该事件进行传递,从父控件往子控件传递,如果有控件拦截该事件,则消费该事件并不再往下传递;如果没有拦截该事件,则继续往下传递;如果最终的子控件也没有消费该事件,则往上一级回传该事件,直到被消费为止;如果最终都没有被消费,则该事件被抛弃。
- 红色箭头方向表示事件分发方向
- 绿色箭头方向表示事件回传方向。
【四大组件】Activity相关
1 说说Activity的启动模式
Activity的启动模式包含四种:Standard,SingleTop,SingleTask,SingleInstance.
- Standard:Android默认的模式。每次启动Activity,无论Activity栈中是否已经有这个Activity的实例,系统都会创建一个新的Activity实例。
- SingleTop:当一个SingleTop模式的Activity处于栈顶时,再去启动它时,系统不会再去创建一个Activity实例,而是直接复用这个实例;如果不在栈顶,则系统会重新创建新的实例。
- SingleTask:位于栈顶,则直接复用;如果不位于栈顶,则将其上的所有Activity实例出栈,然后再复用。
- SingleInstance:会重新创建一个单独的任务栈。
2 Activity的生命周期有哪些?Activity在什么时候可见在什么时候不可见。
Activity生命周期包括:onCreate、onStart,onRestart、onResume,onPause,onStop,onDestroy。Activity在onStart到onPause之间都是可见的,其它不可见(可见和是否处于前台是不一样,处于前台是从onResume到onPause,而且google官方文档也是将这两种状态分开的,在Activity源码也可以看到这两个状态是不一样的)。
3 Activity各个阶段可以做哪些事?
- onCreate:该方法在Activity被创建时回调,它是Activity生命周期的第一个方法。我们在创建Activity时一般都需要重写该方法,然后在该方法中做一些初始化的操作,如通过setContentView设置界面布局的资源,初始化所需要的组件信息等。
- onStart:此方法被回调时表示Activity正在启动,此时Activity已处于可见状态,只是还没有在前台显示,因此无法与用户进行交互。可以简单理解为Activity已显示而我们无法看见罢了。
- onResume:当此方法回调时,则说明Activity已在前台可见,可与用户交互了,onResume方法和onStart方法相似的地方在于,此时的Activity已经可见,只是区别在于onStart被回调时Activity还处于后台,无法与用户进行交互;而onResume被回调时Activity已经处于前台,可以与用户进行交互。如上述流程图所示,在Activity被停止后(onPause和onStop被调用),重新回到前台也会调用onResume方法,因此可以在onResume方法中初始化一些资源。
- onPause:调用此方法时Activity被暂停,一般onStop方法会紧接着被调用。但也有特殊情况,如流程图所示,另外的Activity2来到前台而使当前Activity1转入到后台的情况,当然这里的另外的Activity2必须设置成窗口模式使得Activity处于可见状态,此时按返回键则会直接调用onResume方法。在onpause方法中,我们可以做一些数据的存储或者动画停止或者资源回收操作(比较少做此操作),但是不能做耗时操作,因为耗时操作可能会影响到新的Activity的显示,因为旧的Activity的onPause方法执行完成后,新的Activity的onResume方法才会被执行。
- onStop:一般是在onPause被调用,且Activity完全不可见时被调用。同样的,在onStop方法中可以做一些资源释放操作(比较少做此操作)。
- onRestart:表示Activity被重新启动,当Activity由不可见状态变为可见状态时该方法被回调。
- onDestroy:这是Activity生命周期的最后一个方法,执行Activity的销毁操作,一般我们在这里进行回收工作和最终的资源释放。
【四大组件】Service
1 Service可以分几类
- 按照服务所在地分为:远程服务与本地服务。
- 按照是否可以执行耗时操作可以分为:IntentService和普通Service
2 InentService与普通Service的区别
- intentService可以执行耗时操作。在IntentService里的onHandleIntent里做耗时操作,且该方法里,每次都只执行一条任务,在所有任务都执行完后,会自动关闭服务。
- 普通Service,一般情况下是不能做耗时操作的,它是运行在UI线程中的。
3 Service启动方式
Service启动方式分为两种:
- startService:通过startService启动后,service会一直无限期运行下去,只有外部调用了stopService()或stopSelf()方法时,该Service才会停止运行并销毁。且Service里面的方法不能被调用
- bindService:通过bindService启动后,Service可以和开启者通信,而且开启者被销毁时,Service会自动解绑,也可以通过方法unbindService方法解绑。
4 Service两种启动方式的生命周期
- onCreate()
如果service没被创建过,调用startService()后会执行onCreate()回调; 如果service已处于运行中,调用startService()不会执行onCreate()方法。- onStartCommand()
如果多次执行了Context的startService()方法,那么Service的onStartCommand()方法也会相应的多次调用。onStartCommand()方法很重要,我们在该方法中根据传入的Intent参数进行实际的操作,比如会在此处创建一个线程用于下载数据或播放音乐等。- onBind()
Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。当一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法(如果是startService则不会调用此方法)。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信- onDestory()
在销毁的时候会执行Service该方法。
【四大组件】BroadcastReceiver
1 广播有几种?
目前广播只有3种,粘性广播已被移除。
- 普通广播
普通广播在发出之后,所有广播接收器几乎都会在同一时间内接收到该广播。- 有序广播
广播发出后,同一时刻,只会有一个广播接收器能接收到这条广播消息,当这个广播接收器逻辑执行完毕后,广播才会继续传递。它是根据优先级priority来确定谁先接收的,priority数值越大,优先级越高。- 本地广播
本地广播只能在应用程序内部执行,使用LocalBroadcastManager来对广播进行管理。
2 广播有几种注册形式?区别在哪里?
- 静态注册:常驻系统,不受组件生命周期影响,即便应用退出,还是可以接收到广播,耗电、占内存。
- 动态注册:非常驻,跟随组件的生命周期变化,组件结束,广播结束。在组件结束前,需要先移除广播,否则容易造成内存泄漏。
【四大组件】ContentProvider
1 为什么要使用ContentProvider?
contentProvider为存储和读取数据提供了统一的接口,其它进程如果需要访问该进程的数据,就可以通过该接口访问,实现了数据的共享。
2 使用ContentProvider的步骤
- 新建一个继承ContentProvider的类
- 在manifest里面配置ContentProvider,并给其添加authorities属性
- 声明并实例化一个UriMatcher对象
- 在静态代码块中添加Uri
- 然后暴露需要暴露的方法
Fragment相关
1 Fragment的生命周期有哪些?
解释如下:
- onAttach():Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
- onCreate():Fragment被创建时调用。
- onCreateView():创建Fragment的布局。
- onActivityCreated():当Activity完成onCreate()时调用。
- onStart():当Fragment可见时调用。
- onResume():当Fragment可见且可交互时调用。
- onPause():当Fragment不可交互但可见时调用。
- onStop():当Fragment不可见时调用。
- onDestroyView():当Fragment的UI从视图结构中移除时调用。
- onDestroy():销毁Fragment时调用。
- onDetach():当Fragment和Activity解除关联时调用。
2 Fragment和Activity的生命周期是如何对应的
可以参考如下图:
3 Activity向Fragment传递数据
- 获取Fragment对象,并调用Fragment方法,如Fragment中成员变量的setXX()方法。
- 通过setArguments传参数,通过getArguments获取参数
4 Fragment向Activity传递数据
- 使用SharedPreference
- 使用文件
- 在Fragment中定义接口,Activity实现该接口
public interface OnFragmentInteractionListener {
void onItemClick(String str); //将str从Fragment传递给Activity
}
5 Fragment有几种加载方式
Fragment有两种加载方式,分别是
- 静态加载:直接在XML布局里面声明,如普通控件一样
- 动态加载:动态加载有两种方法
(1)通过使用replace方法:replace方法每次都会将当前的Fragment移除并销毁,然后加载新的Fragment,这样会调用Fragment的各个生命周期方法,如果Fragment里的数据比较多,布局比较复杂,这样加载会比较慢,可能比较卡。
(2)通过使用add、show、hide来显示,则Fragment的生命周期都不会被调用,但是界面数据可能不是最新的,要自己手动刷新。
Android功能优化相关
1 Android如何实现进程保活
- 双进程互相唤醒
- 1像素Activity
在手机屏幕黑屏时,我们启动一个1像素的Activity,其占用内存很小毕竟只有1像素嘛,无形中减小了内存的回收几率,在屏幕亮的时候就关闭该页面。
2 Android的冷启动与热启动
冷启动:启动时,后台没有该应用的进程,系统会重新创建一个进程分配给该应用。
热启动:后台有该应用的进程,再次启动时不需要重新创建进程。所以不会走Application这步,直接走的MainActivity。
3 为什么冷启动会有白屏黑屏问题?如何解决?
因为在加载主题样式Theme中的windowBackground等属性给Activity发生在onCreate/onStart/onResume方法之前,而windowBackground背景被设置成了白色或者黑色,所以我们进入app的第一个界面时会先白屏一会儿。解决方法有:
- 设置windowBackground背景跟启动页的背景相同。
- 禁用Preview Window,即设置windowDisablePreview为true
- 给Activity设置一个透明的主题
Android进程与线程相关
1 Android进程间(IPC)通信
Android进程间通信方式有以下几种:
- Bundle实现进程间通信
- 使用文件
- AIDL
- ContentProvider
- Messenger
- Socket
2 ThreadLocal的理解
- ThreadLocal不是为了解决线程安全而提出的,存放在ThreadLocal的变量是当前线程本身独一无二的变量,其它线程不能访问。
- ThreadLocal的设计原理是将ThreadLocalMap的引用作为Thread的一个属性,利用当前ThreadLocal作为Key,保存的变量值作为value保存在当前线程的ThreadLocalMap中,所以ThreadlocalMap是伴随Thread本身存在而存在的。
3 与主线程通信的几种方式
- 通过Handler机制
- 使用runOnUiThread方法
- View.post(Runnable r)方法
- AsyncTask方法
4 子线程之间通信方式
- 使用wait()、notify()、notifyAll()三个方法
- Handler方法
设计模式相关
1 Android中常见的设计模式有哪些?
- 单例模式
- Builder模式
- 观察者模式
- 原型模式(克隆模式)
- 策略模式
- 责任链模式
2 手写一个单例模式
public class Singleton{
private volatile static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
3 这些常见的模式在哪些地方使用到了,举下例。
- 单例模式:单例模式在ActivityManager、LocalBroadcastManager等系统服务中使用。
- Builder模式:在okHttp3中大量使用,如OkHttpClient,Request等。还有如AlertDialog等。
- 观察者模式:广播机制,EventBus,事件监听等
- 原型模式:Bundle类,Intent类、OkHttpClient等
- 策略模式:Adapter
- 责任链模式:OkHttpClient中的拦截器
动画相关
1. Android有哪几种动画?
Android一共有3种动画:逐帧动画、补间动画和属性动画。
2. 这些动画的原理是什么?
- 逐帧动画
原理:通过一系列静态图片依次播放,利用人眼“视觉暂留”的原理实现动画。
- 补间动画
原理:补间动画就是指开发者指定动画的开始和结束时的“关键帧”,而动画变化的“中间帧”由系统计算并补齐。补间动画有4种方式:淡入淡出(alpha),位移(translate),缩放(scale),旋转(rotate)
- 属性动画
是对于对象属性的变化。它是通过不断地对值进行操作来实现的,初始值和结束值之间的动画过度就是由ValueAnimator这个类来计算。它的内部使用一种时间循环的机制来计算动画过渡的,我们只需要将初始值和结束值提供给ValueAnimator,并告诉它动画所需要运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过度到结束值的相关。
3. 属性动画和补间动画的优缺点是什么?
- 属性动画真正实现了View的移动,补间动画对View的移动更像是在不同的地方绘制了一个影子,实际的对象还是处于原来的地方。
- 属性动画可能会使Activity无法释放而造成内存泄漏,而补间动画没有。因此,使用属性动画时切记在Activity执行onStop时顺便将动画停止。
- xml文件实现补间动画,复用效率极高,在Activity切换、窗口弹出等情景中有着很好的效果。
其他
1 实现弹窗有哪几种方式?
- AlertDialog
- Activity实现弹窗,设置Activity的theme为Theme.Dialog
- 使用popupWindow实现弹窗
(1)写一个弹窗的xml布局,然后加载该布局
(2)声明并实例化一个PopupWindow对象
(3)设置PopupWindow的相关属性
2 什么情况下会出现ANR?
- 按键和触摸事件5s内没被处理。
- BroadcastReceiver事件在规定的时间内没有处理完成。前台广播为10s,后台广播为60s
- Service,前台20s,后台200s未完成启动
3 如何避免ANR?
- 不要在主线程里进行耗时操作,如网络请求,数据库查询等
- 不要在BroadcastReceiver的onReceive()方法中执行耗时操作,如果要执行耗时操作,可以开线程或者使用IntentService。
- 主线程中避免死锁的发生。
4 ListView和RecyclerView的缓存对比
- 层级不同。ListView是两级缓存,RecyclerView是四级缓存
- ListView缓存两级缓存:
(1)mActiveViews:用于屏幕内ItemView快速重用
(2)mScrapViews:用于缓存离开屏幕的ItemView,目的是让进入屏幕的ItemView复用- RecyclerView缓存四级缓存:
(1)mAttachedScrap:用于屏幕内的ItemView快速复用
(2)mCacheViews:默认上限为2,缓存屏幕外的两个ItemView
(3)mViewCacheExtension:用户定制的缓存
(4)mRecyclerPool:默认上限为5,主要用于存储ViewHolder,可供多个RecyclerView使用
- 缓存不同。
- RecyclerView的缓存可以抽象理解为View+ViewHolder+flag。
- ListView缓存View。
ListView获取缓存的流程:
RecyclerView获取缓存流程
5、RecyclerView与ListView的对比优缺点。
- ListView只支持垂直布局,而RecyclerView支持线性布局(垂直、水平)、网格布局和瀑布流布局
- ListView和RecyclerView的缓存机制不同
- RecyclerView封装好了ViewHolder,而ListView需要自己写。
- RecyclerView可以局部刷新,只需要调用notifyItemChange(),而ListView则没有实现局部刷新,如果需要局部刷新则需要自己写。
- ListView可以通过addHeaderView()和addFooterView()给布局添加头部Item和底部Item;但RecyclerView并没有这两个方法,需要头部和底部则需要自己在adapter里面编写
- RecyclerView里封装好了自己的动画效果,而ListView里则没有
- ListView里面有各种点击监听,如onItemClickListener()、onItemLongClickListener()等;但RecyclerView里面则没有,它只提供了一个addOnItemTouchListener()监听,其它监听效果则需要自己去实现。
6 ListView优化
- convertView的使用,主要优化加载布局问题
- ViewHolder的使用,主要优化getView方法中控件绑定问题
- 数据优化,可以分页加载
- 对于图片,使用缓存机制;如果加载的是网络图片,则滑动时不要加载图片,等到滑动结束后再加图片;压缩图片尺寸。
- 减少ItemView的布局层级
- getView中避免大量创建对象
- getView中尽量少做耗时操作
7 什么是图片的三级缓存?
图片的三级缓存指的是:
- 内存缓存
- 本地缓存
- 网络缓存
三级缓存原理:首次加载APP时,通过网络请求将获取到的图片保存到内存和本地SD卡中,之后再次运行时,优先访问内存中的图片缓存,如果内存中没有,则再加载本地SD卡中的缓存,如果本地缓存中也没有,则再进行网络请求访问。