Android面试总结--Android篇

相关文章:Android面试总结–Java篇

Activity生命周期

Android面试总结--Android篇_第1张图片

图中需要注意一下几点:

1.Activity实例是由系统自动创建,并在不同的状态期间回调相应的方法。一个最简单的完整的Activity生命周期会按照如下顺序回调:onCreate -> onStart -> onResume -> onPause -> onStop -> onDestroy。称之为entire lifetime。

2.当执行onStart回调方法时,Activity开始被用户所见(也就是说,onCreate时用户是看不到此Activity的,那用户看到的是哪个?当然是此Activity之前的那个Activity),一直到onStop之前,此阶段Activity都是被用户可见,称之为visible lifetime。

3.当执行到onResume回调方法时,Activity可以响应用户交互,一直到onPause方法之前,此阶段Activity称之为foreground lifetime。

在实际应用场景中,假设A Activity位于栈顶,此时用户操作,从A Activity跳转到B Activity。那么对AB来说,具体会回调哪些生命周期中的方法呢?回调方法的具体回调顺序又是怎么样的呢?

开始时,A被实例化,执行的回调有A:onCreate -> A:onStart -> A:onResume

当用户点击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


横竖屏切换时Activity的生命周期变化?

1、如果自己没有配置android:ConfigChanges,这时默认让系统处理,就会重建Activity,此时Activity的生命周期会走一遍,其中onSaveInstanceState() 与onRestoreIntanceState()
资源相关的系统配置发生改变或者资源不足:例如屏幕旋转、切换系统语言,当前Activity会销毁,并且在onStop之前回调onSaveInstanceState保存数据,在重新创建Activity的时候在onStart之后回调onRestoreInstanceState。其中Bundle数据会传到onCreate(不一定有数据)和onRestoreInstanceState(一定有数据)。 用户或者程序员主动去销毁一个Activity的时候不会回调,其他情况都会调用,来保存界面信息。如代码中finish()或用户按下back,不会回调。

Android面试总结--Android篇_第2张图片

2、如果设置android:configChanges=“orientation|keyboardHidden|screenSize”>,此时Activity的生命周期不会重走一遍,Activity不会重建,只会回调onConfigurationChanged方法。


Activity任务栈

任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.


栈与队列的区别:

  1. 队列先进先出,栈先进后出
  2. 对插入和删除操作的"限定"。 栈是限定只能在表的一端进行插入和删除操作的线性表。 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。
  3. 遍历数据速度不同

Activity启动模式

启动模式可在AndroidManifest.xml中,通过标签的android:launchMode属性设置

  • standard模式

    • 特点:1.Activity的默认启动模式
      2.每启动一个Activity就会在栈顶创建一个新的实例。例如:闹钟程序
    • 缺点:当Activity已经位于栈顶时,而再次启动Activity时还需要在创建一个新的实例,不能直接复用。
  • singleTop模式

    • 特点:该模式会判断要启动的Activity实例是否位于栈顶,如果位于栈顶直接复用,否则创建新的实例。 例如:浏览器的书签
    • 缺点:如果Activity并未处于栈顶位置,则可能还会创建多个实例。
  • singleTask模式

    • 特点:使Activity在整个应用程序中只有一个实例。每次启动Activity时系统首先检查栈中是否存在当前Activity实例,如果存在则直接复用,并把当前Activity之上所有实例全部出栈。例如:浏览器主界面
  • singleInstance模式

    • 特点:该模式的Activity会启动一个新的任务栈来管理Activity实例,并且该实例在整个系统中只有一个。无论从那个任务栈中启动该Activity,都会是该Activity所在的任务栈转移到前台,从而使Activity显示。主要作用是为了在不同程序中共享一个Activity实例。

Intent Flags:

Flags有很多,比如:

Intent.FLAG_ACTIVITY_NEW_TASK 相当于singleTask

Intent. FLAG_ACTIVITY_CLEAR_TOP 相当于singleTop


Activity启动流程

  • app启动的过程有两种情况,第一种是从桌面launcher上点击相应的应用图标,第二种是在activity中通过调用startActivity来启动一个新的activity。
  • 我们创建一个新的项目,默认的根activity都是MainActivity,而所有的activity都是保存在堆栈中的,我们启动一个新的activity就会放在上一个activity上面,而我们从桌面点击应用图标的时候,由于launcher本身也是一个应用,当我们点击图标的时候,系统就会调用startActivitySately(),一般情况下,我们所启动的activity的相关信息都会保存在intent中,比如action,category等等。
  • 我们在安装这个应用的时候,系统也会启动一个PackageManagerService的管理服务,这个管理服务会对AndroidManifest.xml文件进行解析,从而得到应用程序中的相关信息,比如service,activity,Broadcast等等,然后获得相关组件的信息。
  • 当我们点击应用图标的时候,就会调用startActivitySately()方法,而这个方法内部则是调用startActivty(),而startActivity()方法最终还是会调用startActivityForResult()这个方法。而在startActivityForResult()这个方法。因为startActivityForResult()方法是有返回结果的,所以系统就直接给一个-1,就表示不需要结果返回了。
  • 而startActivityForResult()这个方法实际是通过Instrumentation类中的execStartActivity()方法来启动activity,Instrumentation这个类主要作用就是监控程序和系统之间的交互。而在这个execStartActivity()方法中会获取ActivityManagerService的代理对象,通过这个代理对象进行启动activity。
  • 启动就会调用一个checkStartActivityResult()方法,如果说没有在配置清单中配置有这个组件,就会在这个方法中抛出异常了
  • 当然最后调用的是Application.scheduleLaunchActivity()进行启动activity,而这个方法中通过获取得到一个ActivityClientRecord对象,而这个ActivityClientRecord通过handler来进行消息的发送,系统内部会将每一个activity组件使用ActivityClientRecord对象来进行描述,而ActivityClientRecord对象中保存有一个LoaderApk对象,通过这个对象调用handleLaunchActivity来启动activity组件,而页面的生命周期方法也就是在这个方法中进行调用

Service生命周期

Android面试总结--Android篇_第3张图片

  • 被启动的服务的生命周期:如果一个Service被某个Activity 调用 Context.startService 方法启动,那么不管是否有Activity使用bindService绑定或unbindService解除绑定到该Service,该Service都在后台运行。如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。

  • 被绑定的服务的生命周期:如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。

  • 被启动又被绑定的服务的生命周期:如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,Service的onStart便会调用多少次。调用unbindService将不会停止Service,而必须调用 stopService 或 Service的 stopSelf 来停止服务。

  • 当服务被停止时清除服务:当一个Service被终止(1、调用stopService;2、调用stopSelf;3、不再有绑定的连接(没有被启动))时,onDestroy方法将会被调用,在这里你应当做一些清除工作,如停止在Service中创建并运行的线程。

生命周期方法简单介绍

  • startService()
    作用:启动Service服务
    手动调用startService()后,自动调用内部方法:onCreate()、onStartCommand()
    如果一个service被startService多次启动,onCreate()只会调用一次
    onStartCommand()调用次数=startService()次数

  • stopService()
    作用:关闭Service服务
    手动调用stopService()后,自动调用内部方法:onDestory()
    如果一个service被启动且被绑定,如果没有在绑定的前提下stopService()是无法停止服务的。

  • bindService()
    作用:绑定Service服务
    手动调用bindService()后,自动调用内部方法:onCreate()、onBind()

  • unbindService()
    作用:解绑Service服务
    手动调用unbindService()后,自动调用内部方法:onCreate()、onBind()、onDestory()

注意:

startService()和stopService()只能开启和关闭Service,无法操作Service;

bindService()和unbindService()可以操作Service

startService开启的Service,调用者退出后Service仍然存在;

BindService开启的Service,调用者退出后,Service随着调用者销毁。


介绍Service

Service一般分为两种:

  • 本地服务, Local Service 用于应用程序内部。在Service可以调用Context.startService()启动,调用Context.stopService()结束。 在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次 stopService()来停止。

  • 远程服务, Remote Service 用于android系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服 务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加 载它。
    提供给可被其他应用复用,比如定义一个天气预报服务,提供与其他应用调用即可。


1、线程间通信

我们知道线程是CPU调度的最小单位。在Android中主线程是不能够做耗时操作的,子线程是不能够更新UI的。而线程间通信的方式有很多,比如广播,Eventbus,接口回掉,在Android中主要是使用handler。handler通过调用sendmessage方法,将保存消息的Message发送到Messagequeue中,而looper对象不断的调用loop方法,从messageueue中取出message,交给handler处理,从而完成线程间通信。


2、Android线程池

Android中常见的线程池有四种,FixedThreadPoolCachedThreadPoolScheduledThreadPoolSingleThreadExecutor

  • FixedThreadPool

线程池是通过Executors的new FixedThreadPool方法来创建。它的特点是该线程池中的线程数量是固定的。即使线程处于闲置的状态,它们也不会被回收,除非线程池被关闭。当所有的线程都处于活跃状态的时候,新任务就处于队列中等待线程来处理。注意,FixedThreadPool只有核心线程,没有非核心线程。

  • CachedThreadPool

线程池是通过Executors的new CachedThreadPool进行创建的。它是一种线程数目不固定的线程池,它没有核心线程,只有非核心线程,当线程池中的线程都处于活跃状态,就会创建新的线程来处理新的任务。否则就会利用闲置的线程来处理新的任务。线程池中的线程都有超时机制,这个超时机制时长是60s,超过这个时间,闲置的线程就会被回收。这种线程池适合处理大量并且耗时较少的任务。这里得说一下,CachedThreadPool的任务队列,基本都是空的。

  • ScheduledThreadPool

线程池是通过Executors的new ScheduledThreadPool进行创建的,它的核心线程是固定的,但是非核心线程数是不固定的,并且当非核心线程一处于空闲状态,就立即被回收。这种线程适合执行定时任务和具有固定周期的重复任务。

  • SingleThreadExecutor

线程池是通过Executors的new SingleThreadExecutor方法来创建的,这类线程池中只有一个核心线程,也没有非核心线程,这就确保了所有任务能够在同一个线程并且按照顺序来执行,这样就不需要考虑线程同步的问题。


3、AsyncTask的工作原理

AsyncTask是Android本身提供的一种轻量级的异步任务类。它可以在线程池中执行后台任务,然后把执行的进度和最终的结果传递给主线程更新UI。实际上,AsyncTask内部是封装了Thread和Handler。虽然AsyncTask很方便的执行后台任务,以及在主线程上更新UI,但是,AsyncTask并不合适进行特别耗时的后台操作,对于特别耗时的任务,个人还是建议使用线程池。

AsyncTask提供有4个核心方法

1、onPreExecute():该方法在主线程中执行,在执行异步任务之前会被调用,一般用于一些准备工作。

2、doInBackground(String… params):这个方法是在线程池中执行,此方法用于执行异步任务。在这个方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgressUpdate方法,另外,任务的结果返回给onPostExecute方法。

3、onProgressUpdate(Object… values):该方法在主线程中执行,主要用于任务进度更新的时候,该方法会被调用。

4、onPostExecute(Long aLong):在主线程中执行,在异步任务执行完毕之后,该方法会被调用,该方法的参数及为后台的返回结果。

除了这几个方法之外还有一些不太常用的方法,如onCancelled(),在异步任务取消的情况下,该方法会被调用。

源码可以知道从上面的execute方法内部调用的是executeOnExecutor()方法,即executeOnExecutor(sDefaultExecutor, params);而sDefaultExecutor实际上是一个串行的线程池。而onPreExecute()方法在这里就会被调用了。

接着看这个线程池。AsyncTask的执行是排队执行的,因为有关键字synchronized,而AsyncTask的Params参数就封装成为FutureTask类,FutureTask这个类是一个并发类,在这里它充当了Runnable的作用。

接着FutureTask会交给SerialExecutor的execute方法去处理,而SerialExecutor的executor方法首先就会将FutureTask添加到mTasks队列中,如果这个时候没有任务,就会调用scheduleNext()方法,执行下一个任务。如果有任务的话,则执行完毕后最后在调用scheduleNext()执行下一个任务。直到所有任务被执行完毕。

而AsyncTask的构造方法中有一个call()方法,而这个方法由于会被FutureTask的run方法执行,所以最终这个call方法会在线程池中执行。

而doInBackground这个方法就是在这里被调用的。我们好好研究一下这个call()方法。mTaskInvoked.set(true)表示当前任务已经执行过了。接着执行doInBackground方法,最后将结果通过postResult(result)方法进行传递。

postResult()方法中通过sHandler来发送消息,sHandler的中通过消息的类型来判断一个MESSAGE_POST_RESULT,这种情况就是调用onPostExecute(result)方法或者是onCancelled(result)。另一种消息类型是MESSAGE_POST_PROGRESS则调用更新进度onProgressUpdate。


4、Binder的工作机制

  • 直观来说,Binder是Android中的一个类,它实现了IBinder接口,
  • 从IPC的角度来说,Binder是Android中的一种跨进程通信的一种方式,同时还可以理解为是一种虚拟的物理设备,它的设备驱动是dev/binder/。
  • 从Framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。
  • 从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据;

我们先来了解一下这个类中每个方法的含义:

  • DESCRIPTOR:Binder的唯一标识,一般用于当前Binder的类名表示。

  • asInterface(android.os.IBinder obj):用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转化过程是区分进程的,如果客户端和服务端位于同一个进程,那么这个方法返回的是服务端的stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

  • asBinder():用于返回当前Binder对象。

  • onTransact:该方法运行在服务端的Binder线程池中,当客户端发起跨进程通信请求的时候,远程请求通过系统底层封装后交给该方法处理。注意这个方法public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数,然后执行目标方法。当目标方法执行完毕后,就像reply中写入返回值。这个方法的执行过程就是这样的。如果这个方法返回false,客户端是会请求失败的,所以我们可以在这个方法中做一些安全验证。

Binder的工作机制但是要注意一些问题

  • 1、当客户端发起请求时,由于当前线程会被挂起,直到服务端返回数据,如果这个远程方法很耗时的话,那么是不能够在UI线程,也就是主线程中发起这个远程请求的。

  • 2、由于Service的Binder方法运行在线程池中,所以Binder方法不管是耗时还是不耗时都应该采用同步的方式,因为它已经运行在一个线程中了。

Binder机制具体有两层含义:

  • Binder是一种跨进程通信(IPC,Inter-Process Communication)的手段;
  • Binder是一种远程过程调用(RPC,Remote Procedure Call)的手段。

Android跨进程通信AIDL详解

Android-IPC之Binder机制简介

另一种理解:

Binde机制简单理解:

在Android系统的Binder机制中,是有Client,Service,ServiceManager,Binder驱动程序组成的,其中Client,service,Service Manager运行在用户空间,Binder驱动程序是运行在内核空间的。而Binder就是把这4种组件粘合在一块的粘合剂,其中核心的组件就是Binder驱动程序,Service Manager提供辅助管理的功能,而Client和Service正是在Binder驱动程序和Service Manager提供的基础设施上实现C/S 之间的通信。其中Binder驱动程序提供设备文件/dev/binder与用户控件进行交互,
Client、Service,Service Manager通过open和ioctl文件操作相应的方法与Binder驱动程序进行通信。而Client和Service之间的进程间通信是通过Binder驱动程序间接实现的。而Binder Manager是一个守护进程,用来管理Service,并向Client提供查询Service接口的能力。


5、View的绘制流程

Android自定义view,我们都知道实现有三部曲:onMeasure()onLayout()onDraw()

View的绘制流程是从viewRoot的perfromTraversal方法开始的。它经过measure,layout,draw方法才能够将view绘制出来。其中 measure是测量宽高的,layout是确定view在父容器上的摆布位置的,draw是将view绘制到屏幕上的。

Measure:

view的测量是需要MeasureSpc(测量规格),它代表一个32位int值,高2位代表SpecMode(测量模式),低(30)位的代表SpecSize(某种测量模式下的规格大小)。而一组SpecMode和SpeSize可以打包为一个MeasureSpec,反之,MeasureSpec可以解包得到SpecMode和SpeSize的值。SpecMode有三类:

  • unSpecified:父容器不对view有任何限制,要多大有多大。一般系统用这个多。

  • Exactly:父容器已经检测出view所需要的精确大小,这个时候,view的大小就是SpecSize所指定的值,它对应着layout布局中的math_parent或者是具体的数值

  • At_most:父容器指定了一个可变大小的SpecSize,view的大小不能够大于这个值,它对应这布局中的wrap_content.
    对于普通的view,它的MeasureSpec是由父容器的MeasureSpec和自身的layoutParam共同决定的,一旦MeasureSpec确定后,onMeasure就可以确定view的宽高了。

View的measure过程:

onMeasure方法中有个setMeasureDimenSion方法来设置view的宽高测量值,而setMeasureDimenSion有个getDefaultSize()方法作为参数。一般情况下,我们只需要关注at_most和exactly两种情况,getDefaultSize的返回值就是measureSpec中的SpecSize,而这个值基本就是view测量后的大小。而UnSpecified这种情况,一般是系统内部的测量过程,它是需要考虑view的背景这些因素的。

前面说的是view的测量过程,而viewGroup的measure过程:

对于viewGroup来说,除了完成自己的measure过程以外,还要遍历去调用子类的measure方法,各个子元素在递归执行这个过程,viewGroup是一个抽象的类,没有提供有onMeasure方法,但是提供了一个measureChildren的方法。measureChild方法的思想就是取出子元素的layoutParams,然后通过getChildMeasureSpec来常见子元素的MeasureSpec,然后子元素在measure方法进行测量。由于viewGroup子类有不同的布局方式,导致他们的测量细节不一样,所以viewGroup不能象view一样调用onMeasure方法进行测量。

注意:在activity的生命周期中是没有办法正确的获取view的宽高的,原因就是view没有测量完。

1、在onWindowFocuschanged方法中获取 ----该方法含义是view已经初始化完毕。

2、View.post()方法,将消息队列的尾部。

3、使用viewTreeObserver的回调来完成。

4、通过view.measure方式手动测量。

View的Measure流程图:
Android面试总结--Android篇_第4张图片

onLayout

普通的view的话,可以通过setFrame方法来的到view四个顶点的位置,也就确定了view在父容器的位置,接着就调用onLayout方法,该方法是父容器确定子元素的位置。

onDraw

该方法就是将view绘制到屏幕上。分以下几步

  • 绘制背景;
  • 绘制自己;
  • 绘制child;
  • 绘制装饰;

总结一下View的绘制流程

  • 第一步:OnMeasure():测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。
  • 第二步:OnLayout():确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。
  • 第三步:OnDraw():绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:
    • ①、绘制视图的背景;
    • ②、保存画布的图层(Layer);
    • ③、绘制View的内容;
    • ④、绘制View子视图,如果没有就不用;
    • ⑤、还原图层(Layer);
    • ⑥、绘制滚动条。

自定义控件:

  • 1、组合控件。这种自定义控件不需要我们自己绘制,而是使用原生控件组合成的新控件。如标题栏。
  • 2、继承原有的控件。这种自定义控件在原生控件提供的方法外,可以自己添加一些方法。如制作圆角,圆形图片。
  • 3、完全自定义控件:这个View上所展现的内容全部都是我们自己绘制出来的。比如说制作水波纹进度条。

Android中性能优化

由于手机硬件的限制,内存和CPU都无法像pc一样具有超大的内存,Android手机上,过多的使用内存,会容易导致oom,过多的使用CPU资源,会导致手机卡顿,甚至导致anr。我主要是从一下几部分进行优化:

布局优化绘制优化内存泄漏优化响应速度优化listview优化bitmap优化线程优化

  • 布局优化:工具 hierarchyviewer,解决方式

    • 1、删除无用的空间和层级。

    • 2、选择性能较低的viewgroup,如Relativelayout,如果可以选择Relativelayout也可以使用LinearLayout,就优先使用LinearLayout,因为相对来说Relativelayout功能较为复杂,会占用更多的CPU资源。

    • 3、使用标签重用布局,减少层级,进行预加载,使用的时候才加载。

  • 绘制优化

    • 绘制优化指view在ondraw方法中避免大量的耗时操作,由于ondraw方法可能会被频繁的调用。

    • 1、ondraw方法中不要创建新的局部变量,ondraw方法被频繁的调用,很容易引起GC。

    • 2、ondraw方法不要做耗时操作。

  • 内存优化:参考内存泄漏

    • 单例模式

      context的使用,单列中传入的是activity的context,在关闭activity时,activity的内存无法被回收,因为单列持有activity的引用

      在context的使用上,应该传入application的context到单列模式中,这样就保证了单列的生命周期跟application的生命周期一样

    • Handler、AnycTask、Thread、Runable使用引起的内存泄漏

      Android使用Handler造成内存泄露的分析及解决方法

    • 非静态内部类或者匿名内部类都有隐式持有外部类(通常是Activity)引起的内存泄漏;

      通常Handler、AnycTask、Thread等内存泄漏都是由于创建了非静态内部类(或匿名内部类)并由子线程持有并执行耗时操作,导致Handler的生命周期比外部类的生命周期长;

    • BraodcastReceiver、File、Cursor等资源的使用未及时关闭

      应该在使用完成时主动销毁,或者在Activity销毁时注销BraodcastReceiver

    • MVP潜在的内存泄漏

      通常Presenter要同时持有View和Model的引用,如果Activity退出的时候,Presenter正在处理一些耗时操作,那么Presenter的生命周期会比Activity长,导致Activity无法回收。因此,我们在使用MVP架构的时候要做好生命周期的维护,在View被销毁的时候要通知Presenter释放其持有

  • 响应优化

主线程不能做耗时操作,触摸事件5s,广播10s,service20s。

  • listview优化

    • 1、getview方法中避免耗时操作。

    • 2、view的复用和viewholder的使用。

    • 3、滑动不适合开启异步加载。

    • 4、分页处理数据。

    • 5、图片使用三级缓存。

  • Bitmap优化

    • 1、等比例压缩图片。

    • 2、不用的图片,及时recycler掉

  • 线程优化

线程优化的思想是使用线程池来管理和复用线程,避免程序中有大量的Thread,同时可以控制线程的并发数,避免相互抢占资源而导致线程阻塞。

  • 其他优化

    • 1、少用枚举,枚举占用空间大。

    • 2、使用Android特有的数据结构,如SparseArray来代替hashMap。

    • 3、适当的使用软引用和弱引用。


ContentProvider

概念:

  • ContentProvider是android四大组件之一,需要在Mainfest中注册

  • ContentProvider为跨进程访问数据提供接口(通讯录、闹钟、图库、视频、联系人等等)

  • ContentProvider提供数据存储的统一的接口(并不能实际存储数据)

  • 数据的更新通知:使用ContentResolver

Android面试总结--Android篇_第5张图片

ContentProvider优缺点:
优点:

  • 为数据访问提供统一的接口
  • 跨进程访问

缺点:

  • 无法单独使用,必须与其它的存储方式结合使用(因为它不能存储数据,而只是一个接口)

原理:

ContentProvider的底层原理 = Android中的Binder机制

URI定义

URI = content://com.ailian.provider/User/1

  • 主题名Schema:URI格式的前缀(固定);
  • 授权信息(Authority):唯一标识符
  • 表名(Path):数据库的表名
  • 记录(ID):表中指定的某条记录(不指定则返回全部)

深入理解Android之View的绘制流程


BroadcastReceiver

  • 四大组件之一,(唯一一个可以不用再Mainfest注册的,因为它支持动态注册)

  • 动态注册和静态注册

    Android面试总结--Android篇_第6张图片

静态注册在App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中

原理:

Android中的广播使用了设计模式中的观察者模式

BroadcastReceiver模型中有3个角色:

  • 消息订阅者(广播接收者)
  • 消息发布者(广播发布者)
  • 消息中心(AMS,即Activity Manager Service)

Android面试总结--Android篇_第7张图片

生命周期

BroadcastReceiver的生命周期与onReceive方法一致

调用流程

1、广播接收器接收到相应广播后,会自动回调 onReceive() 方法

2、一般情况下,onReceive方法会涉及 与 其他组件之间的交互,如发送Notification、启动Service等

3、默认情况下,广播接收器运行在 UI 线程,因此,onReceive()方法不能执行耗时操作,否则将导致ANR

广播的类型

  • 普通广播(Normal Broadcast)
    如:自定义广播等
  • 系统广播(System Broadcast)
    如:开机、网络变化、蓝牙状态变化、电量变化
  • 有序广播(Ordered Broadcast)

使用方式:

sendOrderedBroadcast(intent);

注意点:

1、有序是针对广播接收者而言的;

2、广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)

(1)、按照Priority属性值从大到下排序;
(2)、Priority属性相同者,动态注册的广播优先;

特点:
1、接收广播按顺序接收

先接收的广播接收者可以对广播进行截断(可以使用abortBroadCast来拦截),即后接收的广播接收者不再接收到此广播;

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

  • 粘性广播(Sticky Broadcast)

由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。

  • App应用内广播(Local Broadcast)

应用内广播是指广播的发送者和接收者都同属于一个App,使用方式:

1、注册广播时将exported属性设置为false(exported对于有intent-filter情况下默认值为true),使得非本App内部发出的此广播不被接收;

2、在广播发送和接收时,增设相应权限permission,用于权限验证;

3、发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中

注意

对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

  • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
  • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
  • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
  • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

HttpClient与HttpUrlConnection的区别

此处延伸:Volley里用的哪种请求方式(Volley2.3前HttpClient,2.3后HttpUrlConnection)

  • 首先HttpClient和HttpUrlConnection 这两种方式都支持Https协议,都是以流的形式进行上传或者下载数据,也可以说是以流的形式进行数据的传输,还有ipv6,以及连接池等功能。
  • HttpClient这个拥有非常多的API,所以如果想要进行扩展的话,并且不破坏它的兼容性的话,很难进行扩展,也就是这个原因,Google在Android6.0的时候,直接就弃用了这个HttpClient.
    而HttpUrlConnection相对来说就是比较轻量级了,API比较少,容易扩展,并且能够满足Android大部分的数据传输。
  • 比较经典的一个框架volley,在2.3版本以前都是使用HttpClient,在2.3以后就使用了HttpUrlConnection。

java虚拟机和Dalvik虚拟机的区别

  • Java虚拟机

    1、java虚拟机基于栈。 基于栈的机器必须使用指令来载入和操作栈上数据,所需指令更多更多。

    2、java虚拟机运行的是java字节码。(java类会被编译成一个或多个字节码.class文件)
  • Dalvik虚拟机

    1、dalvik虚拟机是基于寄存器的

    2、Dalvik运行的是自定义的.dex字节码格式。(java类被编译成.class文件后,会通过一个dx工具将所有的.class文件转换成一个.dex文件,然后dalvik虚拟机会从其中读取指令和数据

    3、常量池已被修改为只使用32位的索引,以 简化解释器。

    4、一个应用,一个虚拟机实例,一个进程(所有android应用的线程都是对应一个linux线程,都运行在自己的沙盒中,不同的应用在不同的进程中运行。每个android dalvik应用程序都被赋予了一个独立的linux PID(app_*))

进程保活

当前业界的Android进程保活手段主要分为黑、白、灰 三种,其大致的实现思路如下:

  • 黑色保活
    所谓黑色保活,就是利用不同的app进程使用广播来进行相互唤醒。举个3个比较常见的场景:
    • 场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒app
    • 场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的场景3
    • 场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差不多)
  • 白色保活
    白色保活手段非常简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。如下方的LBE和QQ音乐这样:
  • 灰色保活
    灰色保活,这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。那么如何利用系统的漏洞呢,大致的实现思路和代码如下:
    • 思路一:API < 18,启动前台Service时直接传入new Notification();
    • 思路二:API >= 18,同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理

熟悉Android系统的童鞋都知道,系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app。这套杀进程回收内存的机制就叫 Low Memory Killer ,它是基于Linux内核的 OOM Killer(Out-Of-Memory killer)机制诞生。
进程的重要性,划分5级:

前台进程 (Foreground process)

可见进程 (Visible process)

服务进程 (Service process)

后台进程 (Background process)

空进程 (Empty process)

了解完 Low Memory Killer,再科普一下oom_adj。什么是oom_adj?它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收。对于oom_adj的作用,你只需要记住以下几点即可:
进程的oom_adj越大,表示此进程优先级越低,越容易被杀回收;越小,表示进程优先级越高,越不容易被杀回收
普通app进程的oom_adj>=0,系统进程的oom_adj才可能<0
有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运,为了尽量避免被杀,还是老老实实去做好优化工作吧。
所以,进程保活的根本方案终究还是回到了性能优化上,进程永生不死终究是个彻头彻尾的伪命题!


Context的理解

  • Context是一个抽象基类。在翻译为上下文,也可以理解为环境,是提供一些程序的运行环境基础信息。
  • Context下有两个子类:ContextWrapper是上下文功能的封装类,而ContextImpl则是上下文功能的实现类
    • ContextWrapper又有三个直接的子类, ContextThemeWrapper、Service和Application
      • ContextThemeWrapper是一个带主题的封装类,而它有一个直接子类就是Activity,所以Activity和Service以及Application的Context是不一样的,只有Activity需要主题,Service不需要主题。
    • Context一共有三种类型,分别是Application、Activity和Service。这三个类虽然分别各种承担着不同的作用,但它们都属于Context的一种,而它们具体Context的功能则是由ContextImpl类去实现的,因此在绝大多数场景下,Activity、Service和Application这三种类型的Context都是可以通用的。不过有几种场景比较特殊,比如启动Activity,还有弹出Dialog。出于安全原因的考虑,Android是不允许Activity或Dialog凭空出现的,一个Activity的启动必须要建立在另一个Activity的基础之上,也就是以此形成的返回栈。而Dialog则必须在一个Activity上面弹出(除非是System Alert类型的Dialog),因此在这种场景下,我们只能使用Activity类型的Context,否则将会出错。
    • 每一个Activity和Service以及Application的Context都是一个新的ContextImpl对象 getApplication()用来获取Application实例的,但是这个方法只有在Activity和Service中才能调用的到。那么也许在绝大多数情况下我们都是在Activity或者Service中使用Application的,但是如果在一些其它的场景,比如BroadcastReceiver中也想获得Application的实例,这时就可以借助getApplicationContext()方法.
      getApplicationContext()比getApplication()方法的作用域会更广一些,任何一个Context的实例,只要调用getApplicationContext()方法都可以拿到我们的Application对象。

getApplicationContext()和getApplication()方法得到的对象都是同一个application对象,只是对象的类型不一样。
Context数量 = Activity数量 + Service数量 + 1 (1为Application)


怎么在Service中创建Dialog对话框

1.在我们取得Dialog对象后,需给它设置类型,即:

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)

2.在Manifest中加上权限:



理解Activity,View,Window三者关系

这里先来个算是比较恰当的比喻来形容下它们的关系吧。Activity像一个工匠(控制单元),Window像窗户(承载模型),View像窗花(显示视图)LayoutInflater像剪刀,Xml配置像窗花图纸。

  • 1:Activity构造的时候会初始化一个Window,准确的说是PhoneWindow。
  • 2:这个PhoneWindow有一个“ViewRoot”,这个“ViewRoot”是一个View或者说ViewGroup,是最初始的根视图。
  • 3:“ViewRoot”通过addView方法来一个个的添加View。比如TextView,Button等
  • 4:这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。比如onClickListener,onKeyDown等。

View、ViewGroup事件分发机制

  • 1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
  • 2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
  • 3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
  • 4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
  • 5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
  • 6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
  • 7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

Android onTouch事件传递机制


Android中跨进程通讯的几种方式

Android 跨进程通信,像intent,contentProvider,广播,service都可以跨进程通信。

  • intent:这种跨进程方式并不是访问内存的形式,它需要传递一个uri,比如说打电话。
  • contentProvider:这种形式,是使用数据共享的形式进行数据共享。
  • service:远程服务,aidl
  • 广播

Android中的几种动画

总的来说,Android动画可以分为两类,最初的传统动画和Android3.0 之后出现的属性动画;
传统动画又包括 帧动画(Frame Animation)和补间动画(Tweened Animation)

1、Tween Animation:

  • 补间动画(视图动画)特性
    • 渐变动画支持4种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、不透明(Alpha)
    • 只是显示的位置变动,View的实际位置未改变,表现为View移动到其他地方,点击事件仍在原处才可响应。
    • 组合使用步骤较复杂。
    • View Animation也是指此动画。
    • 可以结合插值器(Interpolator)完整复杂动画,Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。

通过Animation类和AnimationUtils配合实现的。动画效果可以预先配置在res/anim目录下的xml文件中。

  • 补间动画优缺点
    • 缺点:当平移动画执行完停止最后的位置,结果焦点还在原来的位置(控件属性未改变)。
    • 优点:相较于帧动画,补间动画更为连贯自然。

2、Frame Animation:

这种动画(也叫Frame动画、帧动画)其实可以划分到视图动画的类别,专门用来一个一个的显示Drawable的resources,就像放幻灯片一样,

  • 帧动画特性
    • 用于生成连续的GIF动画。
    • Drawable Animation也是指此动画。
  • 帧动画优缺点
    • 缺点:效果单一,逐帧播放需要很多图片,占用空间较大
    • 优点:制作简单。

3、Property Animation:

  • 属性动画特性
    • 支持对所有View能更新的属性的动画(需要属性的set/get方法)。
    • 更改的是View的实际属性,不影响其动画执行后所在位置的正常使用。
    • Android 3.0(API 11)及以后的功能。
  • 属性动画的优缺点
    • 缺点:向下兼容的问题。
    • 优点:易定制,效果强。

4、视频动画

由UI设计师提供MP4视频,通过Android的VideoView播放完成的动画效果;

5、Lottie动画

由UI设计师通过AE的Lottie插件生成json数据并提供给Android或IOS开发者,由开发者集成完成的动画;


Handler的原理

Android中主线程是不能进行耗时操作的,子线程是不能进行更新UI的。所以就有了handler,它的作用就是实现线程之间的通信。
handler整个流程中,主要有四个对象,handler,Message,MessageQueue,Looper。当应用创建的时候,就会在主线程中创建handler对象,
我们通过要传送的消息保存到Message中,handler通过调用sendMessage方法将Message发送到MessageQueue中,Looper对象就会不断的调用loop()方法
不断的从MessageQueue中取出Message交给handler进行处理。从而实现线程之间的通信。

Android 之 Handler 机制

Android热修复原理

Android 热修复原理篇及几大方案比较

Android插件化原理

android架构设计之插件化、组件化


Handler造成内存泄漏的原因和解决方案(AnycTask、Thread、Runable同理)

Handler造成内存泄漏的原因:

  • 当使用非静态内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
  • 如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收

对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。
解决方案:

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

    • 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
    • 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
  • 方法二:将Handler声明为静态类。
    PS:在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。
    静态类不持有外部类的对象,所以你的Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference)。

static class MyHandler extends Handler
    {
        WeakReference mWeakReference;
        public MyHandler(Activity activity) 
        {
            mWeakReference=new WeakReference(activity);
        }
        @Override
        public void handleMessage(Message msg)
        {
            final Activity activity=mWeakReference.get();
            if(activity!=null)
            {
                if (msg.what == 1)
                {
                    noteBookAdapter.notifyDataSetChanged();
                }
            }
        }
    }

PS:什么是WeakReference?

  WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。

总结:使用非静态内部类(匿名内部类)时,都会隐式地持有其外部类的引用(比如:Activity),如果此时内部类的生命周期比引用的外部类的生命周期长时(比如内部类开启子线程执行耗时任务),就会造成内存泄漏;
最常见的如Handler、Thread、AsyncTask、EventBus、RxAndroid


Android加载大图、多图如何避免OOM

//获取系统分配给每个APP最高可使用的内存大小
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
  • 图片压缩

    • 压缩后的图片大小应该和用来展示它的控件大小相近,在一个很小的ImageView上显示一张超大的图片不会带来任何视觉上的好处,但却会占用我们相当多宝贵的内存,而且在性能上还可能会带来负面影响

    • BitmapFactory这个类提供了多个解析方法(decodeByteArray, decodeFile, decodeResource等)用于创建Bitmap对象,我们应该根据图片的来源选择合适的方法。比如SD卡中的图片可以使用decodeFile方法,网络上的图片可以使用decodeStream方法,资源文件中的图片可以使用decodeResource方法

    • BitmapFactory提供了一个可选的BitmapFactory.Options参数,nJustDecodeBounds属性设置为true时系统不会真正的给bitmap分配内存,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值,但是需要注意压缩完成图片后需要将nJustDecodeBounds设置回false,这样才能获取到bitmap

      BitmapFactory.Options options = new BitmapFactory.Options();
      //设置BitmapFactory.decodeResource不分配内存
      options.inJustDecodeBounds = true; 
      //这个方法其实是有返回值的就是Bitmap,只是由于设置了inJustDecodeBounds=true,所以这时的返回值是null;
      BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 
      //BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值
      int imageHeight = options.outHeight; 
      int imageWidth = options.outWidth; 
      String imageType = options.outMimeType;
      
    • 图片压缩的比例是通过BitmapFactory.Options的inSampleSize来设置的,比如我们有一张2048*1536像素的图片,将inSampleSize的值设置为4,就可以把这张图片压缩成512*384像素,由于Height和Width都压缩了4倍
      ,那么压缩后图片的内存大小是原来的1/16,

    • 那么inSampleSize的值设置多少才合适呢?下面提供一种比较常用的方法:

      /**
       * @param options
       * @param reqWidth -- 目标ImageView的Width
       * @param reqHeight -- 目标ImageView的Height
       * @return
       */
      public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { 
          // 源图片的高度和宽度 
          final int height = options.outHeight; 
          final int width = options.outWidth; 
          int inSampleSize = 1; 
          if (height > reqHeight || width > reqWidth) { 
              // 计算出实际宽高和目标宽高的比率 
              final int heightRatio = Math.round((float) height / (float) reqHeight); 
              final int widthRatio = Math.round((float) width / (float) reqWidth); 
              // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 
              // 一定都会大于等于目标的宽和高。 
              inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 
          } 
          return inSampleSize; 
      } 
      
      
    
    
  • 图片缓存技术

    • 内存缓存

    对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除
    下面是一个使用 LruCache 来缓存图片的例子:

    主要步骤:
     1、要先设置缓存图片的内存大小,我这里设置为手机内存的1/8;
     2、LruCache里面的键值对分别是URL和对应的图片;
     3、重写了一个叫做sizeOf的方法,返回的是图片数量;
    private LruCache mMemoryCache; 
    
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 
        // LruCache通过构造函数传入缓存值,以KB为单位。 
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
        // 使用最大可用内存值的1/8作为缓存的大小。缓存大小可以自己确定
        int cacheSize = maxMemory / 8; 
        mMemoryCache = new LruCache(cacheSize) { 
            @Override 
            protected int sizeOf(String key, Bitmap bitmap) { 
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。 
                return bitmap.getByteCount() / 1024; 
            } 
        }; 
    } 
     
    public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
        if (getBitmapFromMemCache(key) == null) { 
            mMemoryCache.put(key, bitmap); 
        } 
    } 
     
    public Bitmap getBitmapFromMemCache(String key) { 
        return mMemoryCache.get(key); 
    }
    
    
    • 磁盘缓存

    磁盘缓存没什么好说的直接将图片保存到SDCard上即可;

  • 软引用

    只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象。

    private Map> imageMap  = new HashMap>();
    获取:
    SoftReference reference = imageMap.get(key);
    Bitmap bitmap = reference.get();
    

两者的比较说到这里,有必要来进行一下比较了。网上有很多人使用软引用加载图片的多 ,但是现在已经不再推荐使用这种方式了,
(1)因为从
Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,
这让软引用和弱引用变得不再可靠。
(2)另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,
因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃,
所以我这里用得是LruCache来缓存图片,当存储Image的大小大于LruCache设定的值,系统自动释放内存,
这个类是3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。


加载超级巨大的图片方案(不能压缩)

  • BitmapRegionDecoder:主要用于显示图片的某一块矩形区域,如果你需要显示某个图片的指定区域,那么这个类非常合适。

    • BitmapRegionDecoder提供了一系列的newInstance方法来构造对象,支持传入文件路径,文件描述符,文件的inputstrem等
BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);
  • 上述解决了传入我们需要处理的图片,那么接下来就是显示指定的区域
bitmapRegionDecoder.decodeRegion(rect, options);
参数一很明显是一个rect,参数二是BitmapFactory.Options,你可以控制图片的inSampleSize,inPreferredConfig等

设计模式

《Android源码设计模式解析》读书笔记——Android中你应该知道的设计模式

设计模式在Android源码中的运用

  • 创建型

    • 单例模式—Application、LayoutInflater、WindowsManagerService、ActivityManagerService
    • Builder模式—AlertDialog.Builder、StringBuilder、StringBuffer、Notification
    • 原型模式—Intent
    • 工厂方法模式—Activity的各种生命周期、ArrayList和HashSet
    • 抽象工厂模式—MediaPlayer
  • 行为型

    • 策略模式—动画插值器LinearInterpolator
    • 状态模式—Wi-Fi管理
    • 责任链模式—View事件的分发处理
    • 解释器模式—PackageParser
    • 命令模式—PackageHandler
    • 观察者模式—BaseAdapter、EventBus、Otto、AndroidEventBus
    • 备忘录模式—onSaveInstanceState和onRestoreInstanceState
    • 迭代器模式—Cursor
    • 模板方法模式—AsyncTask;Activity的生命周期
    • 访问者模式—ButterKnife、Dagger、Retrofit
    • 中介者模式—Keyguard解锁屏
  • 结构型

    • 代理模式—ActivityManagerProxy代理类
    • 组合模式—View和ViewGroup的嵌套组合
    • 适配器模式—ListView、GridView、RecyclerView
    • 装饰模式—Stream
    • 享元模式—String
    • 外观模式—Context
    • 桥接模式—Window与WindowManager

工厂方法模式和抽象工厂模式区别

简单工厂模式

工厂方法模式

抽象工厂模式

相关文章:Android面试总结–Java篇

你可能感兴趣的:(Android)