Android面试高频知识点

持续更新、、、

一 Activity的生命周期 ?

Activity的生命周期 ,包括典型的生命周期和异常情况下的生命周期,以及singletop模式和simgletask模式的生命周期。

典型生命周期包括c s r p s d 即onCreate,onStart,onResume,onPause,onStop,onDestroy ,注意特定场景下的生命周期,比如打开新的activity 时, 旧activity 调用onPause ,新activity 调用c s r ,再回调旧activity的 onStop,如果回到旧activity 则调用onRestart -> onStart -> onResume。

异常情况下的生命周期分析,比如屏幕旋转时,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时在onStop前调用onSaveInstanceState方法保存数据,而新activity 调用c s r。

singletop模式和simgletask模式的生命周期,如果要启动的Activity已经存在,则只调用onNewIntent方法,否则重新创建,调用 c s r。

 

二 Activity 的四种启动模式?

1-标准模式,onCreate、onStart、onResume都会被调用。比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的任务栈中。

2-singleTop:栈顶复用模式。在这种模式下,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时它的onNewIntent方法会被回调,否则重新创建。

3-singleTask,在这种模式下,只要Activity在一个栈中存在,那么多次启动此Activity都不会重新创建实例,系统也会回调其onNewIntent方法,否则重新创建。

4-singleInstance模式,单独开启一个任务栈。

 

 

三  activity四种启动模式的应用场景?

1.standard模式, mainfest中没有配置启动模式就默认为标准模式。

2.singleTop模式, 登录页面、WXPayEntryActivity、推送通知栏等。

3.singleTask模式,程序模块逻辑入口:主页面、WebView页面、扫一扫页面、电商中:购物界面,确认订单界面,付款界面等。

4.singleInstance模式, 系统Launcher、锁屏键、来电显示等系统应用。

 

四 什么是activity 的任务栈?

TaskAffinity属性的值就是任务栈的名字,默认为应用包名,如果为某个activity指定TaskAffinity属性,则表示开启新的任务栈。当TaskAffinity和singleTask启动模式配对使用的时候,待启动的Activity会运行在名字和TaskAffinity相同的任务栈。

当TaskAffinity和allowTaskReparenting结合的时候,会导致任务栈的转移。比如现在有2个应用A和B, 应用A启动了应用B的Activity C,然后按Home键回到桌面,然后再单击应用B的桌面图标,这个时候并不是启动了应用B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中。

 

五 Service的两种状态?

启动状态:当应用组件(如 Activity)通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响,除非手动调用才能停止服务, 服务不会将结果返回给调用方,无法交互。

绑定状态:当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务允许组件与服务进行交互、发送请求、获取结果,甚至是可以进程间通信 (IPC) 。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会自动销毁。

注意,如果服务将执行任何耗时事件,则应在服务内创建新线程来完成这项工作,简而言之,Service里面的耗时操作应该另起线程执行。

 

六 Service的两种状态互相可以转换吗?

1. 先绑定服务后启动服务,那么绑定服务将会转为启动服务运行,这时如果之前绑定的宿主(Activity)被销毁了,也不会影响服务的运行,直到收到调用停止服务或者内存不足时才会销毁该服务。

2.先启动服务后绑定服务并不会转为绑定服务,但是还是会与宿主绑定,只是即使宿主解除绑定后,服务依然按启动服务的生命周期在后台运行,直到调用了stopService()或是服务本身调用了stopSelf()方法抑或内存不足时才会销毁服务。这种先启动,后绑定服务,用于音乐播放器控制歌曲。

 

七 Service生命周期

第一次调用startService方法时,onCreate方法、onStartCommand方法将依次被调用,而多次调用startService时,只有onStartCommand方法被调用,最后我们调用stopService方法停止服务时onDestory方法被回调,这就是启动状态下Service的执行周期。

è¿éåå¾çæè¿°

 

八 如何保证服务不被杀死?

1.因内存资源不足而杀死Service
这种情况比较容易处理,可将onStartCommand() 方法的返回值设为 START_STICKY或START_REDELIVER_INTENT ,该值表示服务在内存资源紧张时被杀死后,在内存资源足够时再恢复。

2.也可将Service设置为前台服务,这样就有比较高的优先级,在内存资源紧张时也不会被杀掉。

3.可以在 onDestory() 中发送广播重新启动,这样杀死服务后会立即启动。当然onDestory() 方法在某些情况下不会执行。

4.我们可开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。

 

九 IntentService作用及原理?

IntentService继承自Service,用于在后台执行耗时的异步任务,当任务完成后会自动停止。注意这里是自动停止。它拥有较高的优先级,不易被系统杀死(继承自Service的缘故),因此比较适合执行一些高优先级的异步任务。它的实现原理是HandlerThread和Handler,而HandlerThread是自带Looper的的Thread。自带Looper的的Thread有什么好处呢? 当开启子线程时,如果想在里面创建自己的Handler,是会报错的,因为该子线程无法获取到Looper。所以如果想在子线程里面创建自己的Handler,需要自己调用Looper.prepare()方法获取Looper,然后调用Looper.loop进行消息循环,以及退出循环的处理。而HandlerThread正好帮我们完成了这些工作,这就是HandlerThread的好处。

 

十 BroadcastReciever作用,使用场景,实现原理?

作用:

1.不同的程序之间的数据传输与共享,比如说拦截系统短信,拦截骚扰电话等。

2.起到了一个通知的作用,比如在service中要通知主程序,更新主程序的UI等

使用场景:

1.同一app内部的同一组件内的通信

2.同一app内部的不同组件之间的通信

3.同一app具有多个进程的不同组件之间的通信

4.不同app之间的组件之间通信

5.Android系统在特定情况下与App之间的通信

原理:

1.广播接收者通过Binder机制向AMS(Activity Manager Service)注册

2.广播发送者通过binder机制向AMS发送广播

3.AMS根据相应条件(IntentFilter/Permission等)在已注册列表中,查找合适的广播接收者,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中

4.消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法

静态注册与动态注册的区别:

1.注册的地方不同

2.动态注册时,当activity退出,就接收不到广播了,静态注册即使app退出,仍然能接收到广播。但是自Android3.1开始,对于系统广播,如果App进程已经退出,将不能接收到广播,对于自定义的广播,可以通过覆写flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能接收到广播。

BroadcastReceiver注意事项:

在onResume()注册、onPause()注销,因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行,从而导致内存泄露。

 

十一 Fragment 声明周期,其与Activity之间如何传值?

生命周期为:

1.Activity向Fragment传值:

在Activity中创建对象fragment,通过调用fragment.setArguments()传递到fragment中,然后在Fragment中通过调用getArguments()得到bundle对象,就能得到里面的值。

2.Fragment向Activity传值:

第一种,在Activity中调用getFragmentManager()得到fragmentManager,,调用findFragmentByTag(tag)或者通过findFragmentById(id)。

第二种,通过回调的方式,定义一个接口(可以在Fragment类中定义),接口中有一个空的方法,在fragment中需要的时候调用接口的方法,值可以作为参数放在这个方法中,然后让Activity实现这个接口,必然会重写这个方法,这样值就传到了Activity中。

3.Fragment与Fragment之间如何传值:

第一种:通过findFragmentByTag得到另一个的Fragment的对象,这样就可以调用另一个的方法了。
第二种:通过接口回调的方式。
第三种:通过setArgumentsgetArguments的方式。

FragmentPagerAdapter与FragmentStatePagerAdapter的区别?

FragmentPagerAdapter适用于Fragment页面少的情况,FragmentStatePagerAdapter适用于Fragment页面多的情况(页面多时回收内存)。

 

十二 Binder机制

Android为什么采用Binder做为IPC机制?

1.性能更强:Binder 只需要一次数据拷贝,性能上仅次于共享内存,而Socket/管道/消息队列需要2次拷贝。

2.更稳定:共享内存虽然无需拷贝,但是控制复杂,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的(基于 C/S 架构)。

3.更安全:传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。

Linux 下的传统 IPC 通信原理?

通常的做法是消息发送方调用 copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区,如下图:

Android面试高频知识点_第1张图片

这种传统的 IPC 通信方式有两个问题:

1.性能低下,一内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝。

2.接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。

动态内核可加载模块机制?

传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块可以被单独编译,但是不能独立运行。它在运行时被链接到内核,作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。而在 Android 系统中,这个内核模块就叫 Binder 驱动(Binder Dirver)。

内存映射?

 知道了上面的LKM机制,Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?这就不得不提到 Linux 下的另一个概念:内存映射。内存映射通过 mmap() 来实现。内存映射简单的讲就是将用户空间的内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。

Binder IPC通信过程如下:

Android面试高频知识点_第2张图片

 

十三 View滑动的几种方式?

通过三种方式可以实现View的滑动:第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;第二种是动画中的一种,view动画,也叫补间动画,另外属性动画也可以;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。

scrollTo/scrollBy:操作简单,适合对View内容的滑动;

 动画:操作简单,主要适用于没有交互的View和实现复杂的动画效果;

改变布局参数:操作稍微复杂,适用于有交互的View。

弹性滑动的原理及实现?

它们都有一个共同思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成,弹性滑动的具体实现方式有很多,比如通过Scroller、Handler#postDelayed以及Thread#sleep等。

 

十四 View的事件分发机制?

点击事件达到顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这样的:如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理,这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用,否则onTouchEvent会被调用。也就是说,如果都提供的话,onTouch会屏蔽掉onTouchEvent。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。如果顶级ViewGroup不拦截事件,则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。

处理滑动冲突的场景及解决方法?

Android面试高频知识点_第3张图片

如上面的滑动冲突场景,外部方向和内部方向不一致,先根据滑动距离判断是水平滑动还是竖直滑动,然后有两种拦截法,外部拦截法和内部拦截法。

外部拦截法:

所谓外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            boolean intercepted = false;
            int x = (int) event.getX();
            int y = (int) event.getY();
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                intercepted = false;//这里不能拦截,否则后续点击事件都被拦截。
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (父容器需要当前点击事件) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                intercepted = false;//默认不拦截
                break;
            }
            default:
                break;
              }
              mLastXIntercept = x;
              mLastYIntercept = y;
              return intercepted;
  }

内部拦截法:

内部拦截法是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显复杂。它的伪代码如下,我们需要重写子元素的dispatchTouchEvent方法:
 

public boolean dispatchTouchEvent(MotionEvent event) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                parent.requestDisallowInterceptTouchEvent(true);//父容器不允许拦截
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    int deltaX = x - mLastX;
                    int deltaY = y - mLastY;
                    if (父容器需要此类点击事件)) {
                        parent.requestDisallowInterceptTouchEvent(false);//父容器允许拦截
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    break;
                }
                default:
                    break;
                }

                mLastX = x;
                mLastY = y;
                return super.dispatchTouchEvent(event);
        }

除了子元素需要做处理以外,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisal-lowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
 

public boolean onInterceptTouchEvent(MotionEvent event) {
            int action = event.getAction();
            if (action == MotionEvent.ACTION_DOWN) {
                return false;
              } else {
                  return true;
              }
    }

 

十五 View的工作流程,measure过程、layout过程、draw过程?

MeasureSpec有三种模式:EXACTLY对应布局文件中控件宽高的固定值或Match_Parent;AT_MOST对应布局文件中控件宽高的Wrap_Content;UNSPECIFIED一般用于系统内部。子元素的MeasureSpec与父容器的MeasureSpec和子元素本身的LayoutParams有关,此外还和View的margin及padding有关。

当View采用固定宽/高,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式。当View的宽/高是match_parent时,如果父容器是精准模式,那么View也是精准模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。当View的宽/高是wrap_content时,不管父容器的模式是精准还是最大化,View的模式总是最大化并且大小不能超过父容器的剩余空间。

为什么获取View的宽高会为0?怎么解决?

如果在Activity的回调里面获取View的宽高会为0,比如在onCreate,onStart,onResume里面是获取不到view宽高的。原因是因为View的绘制由ViewrootImp来完成的,而ViewrootImp是在onResume方法回调之后创建的,所以在onResume之前无法获取。解决办法是在onWindowFocusChanged回调里面获取,还可以在view.post(runnable)里面和ViewTreeObserver的回调里面获取。

 

十六 自定义View?

直接继承View重写onDraw方法

这种方法主要用于实现一些不规则的效果,一般需要重写onDraw方法。采用这种方式需要自己支持wrap_content,并且padding也需要自己处理。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
                  
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                  
	int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
                  
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
             
     int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
                  
	int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 
	//需要考虑wrap_content的情况                 
	if (widthSpecMode == MeasureSpec.AT_MOST
 && heightSpecMode ==
                  MeasureSpec.AT_MOST) {
                   
 setMeasuredDimension(200, 200);
                  
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
                    		 	
setMeasuredDimension(200, heightSpecSize);
                  
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
                    		
setMeasuredDimension(widthSpecSize, 200);
                  
}
              }

              
@Override
              
protected void onDraw(Canvas canvas) {
                  
	super.onDraw(canvas);
  
	//需要把padding考虑进来                
	final int paddingLeft = getPaddingLeft();
                  
	final int paddingRight = getPaddingLeft();
                  
	final int paddingTop = getPaddingLeft();
                  
	final int paddingBottom = getPaddingLeft();
                  
	int width = getWidth() - paddingLeft - paddingRight;
                  
	int height = getHeight() - paddingTop - paddingBottom;
                  
	int radius = Math.min(width, height) / 2;
                  
	canvas.drawCircle(paddingLeft + width / 2, 
paddingTop + height / 2,
   radius, mPaint);
              
}
          
}

如果直接继承特定的View,比如TextView,则不需要考虑wrap_content,padding。

 

OkHttp流程?

Okhttp流程,这里只说异步请求,如果同时满足两个条件(请求任务小于64和请求主机数小于5)则加入running异步请求队列,任务放入线程池并执行,获取响应并回调成功或失败,并且将刚才的任务从running异步请求队列删除。然后继续判断同时满足两个条件,则从ready异步请求队列里取最早的 任务放入线程池执行,重复上面的步骤。

OkHttp缓存?

1.与服务端协商,由服务端决定缓存方式。服务端返回的Response头部有Cache-Control:maxage,OkHttp默认不使用缓存,需要new Cache()对象传入OkhttpClient。这样就使用了Okhttp内置的缓存。

2.自定义网络拦截器。 如果无法与服务器协商缓存,由客户端实现。自定义网络拦截器,在头部添加Cache-Control:maxage,然后调用调用addNetworkInterceptor。这种方式的缺点:由于Okhttp采用单例,全局使用一个拦截链,某些数据需要长时间缓存,比如视频缓冲2个小时,实时新闻缓存1分钟,这种拦截器方式就有缺陷。

3.使用Cache-Control类。由于Cache-Control类只针对单个Request,因此可以对某个Request单独设置缓存时间。同时,还可以给某个Request设置强制使用缓存(Cache.ForceCache)或强制使用网络(Cache.FORCE_NETWORK)。

 

 

Android面试高频知识点_第4张图片

 

OkHttp拦截器原理?

1.自定义拦截器,比如日志拦截器。

2.RetryAndFollowUpInterceptor,根据返回的响应决定是否重试或重定向。

3.BridgeInterceptor,修改Request(),修改Response()。

4.CacheInterceptor,是否使用缓存。

5.ConnectInterceptor,建立Socket连接。Socket连接池复用,最多5个空闲,最长5分钟。

6.CallServerInterceptor,建立TCP连接,与服务器通信。

 

 

Glide缓存原理?

当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。

 

常见的内存泄露?

1.单例导致的内存泄露,创建单例时如果传入了Activity的context,只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。

解决方法:能使用Application的Context就不要使用Activity的Context,Application的生命周期伴随着整个进程的周期。

2.使用非静态内部类创建了静态实例,静态实例的生命周期和应用ClassLoader同级别,又因为该静态实例又会隐式持有其外部类的引用,所以导致其外部类无法正常释放,出现了泄漏问题。

解决方法:将非静态内部类修改为静态内部类。(静态内部类不会隐式持有外部类)

3.使用非静态内部类创建异步任务或Runnable,异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。

Android面试高频知识点_第5张图片

解决方法:使用静态内部类,避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务,避免任务在后台执行浪费资源。

4.非静态的Handler导致的内存泄露,非静态匿名内部类的实例持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。

解决方法:

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息。

5.资源未关闭造成的内存泄漏,对于使用了BraodcastReceiver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

 

RecyclerView的四级缓存?

public final class Recycler {
        //第一级缓存,两个scrap。scrap是用来保存被rv移除掉但最近又马上要使用的缓存,比如说rv中自带item的动画效果。一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中。
        final ArrayList mAttachedScrap = new ArrayList<>();
        ArrayList mChangedScrap = null;


         //第二级缓存,cacheView。因为rv会认为刚被移出屏幕的viewholder可能接下来马上就会使用到,所以不会立即设置为无效viewholer,会将它们保存到cached中,但又不能将所有移除屏幕的viewholder都视为有效viewholer,所以它的默认容量只有2个。可以通过setViewCacheSize改变这个大小。
        final ArrayList mCachedViews = new ArrayList();


         //第三级缓存,cacheExtension。自定义缓存,一般用不到。
        private ViewCacheExtension mViewCacheExtension;


         //第四级缓存,recyclerPool。这个缓存保存的对象就是那些无效的viewholer,pool一般会和cached配合使用,这么来说,cached存不下的会被保存到pool中毕竟cached默认容量大小只有2,但是pool容量也是有限的。当保存满之后再有viewholder到来的话就只能会无情抛弃掉,它也有一个默认的容量大小为5。
        RecycledViewPool mRecyclerPool;
}

 

WebView优化

1.创建Webview耗时,可在使用Webview之前进行创建。

2.DNS解析耗时,直接使用ip地址。

3.资源文件加载耗时,导致白屏时间长,采用cdn加速,缩短加载时间。

4.html布局复杂且嵌套导致耗时,可优化布局,减少嵌套。

5.开启缓存。

6.体积大且固定的资源文件放在本地,为Webview另外开启进程等。

 

apk体积缩小优化

1.将svg转换成png,替换原来套图

2.so库配置,大部分真机为arm结构,使用 'armabi'即可.

3.去掉其他国家的语言,只配置中文。

4.移除其他无用资源,代码混淆。

 

网络请求框架对比

Fresco

1. 5.0一下使用匿名共享内存(gc无影响),5.0+使用Java堆内存。

2.三级缓存:内存缓存(Bitmap缓存+未解码图片缓存)+磁盘缓存

3.apk增加3M

4.使用不方便,侵入性高

5.Facebook主导

picasso

Square

120KB

ARGB8888

全尺寸缓存

Glide

Google

400KB

ARGB565

裁剪后缓冲

 

 

 

 

你可能感兴趣的:(安卓开发进阶系列)