Android面试摘录二

来源:互联网 发布:sql好学吗 编辑:程序博客网 时间:2024/06/05 13:22

Android面试及其汇总操作二

ContentProvider实现原理

  1. ContentProvider是什么?
    由于在应用程序的内部的数据存储方式不统一,有的使用SQLite,有的使用XML等,在共享自己数据的时候通常要调用者了解内部的数据存储方式才能够知道如何调用,所以ContentProvider这个类主要提供一个统一的公开接口供其它应用程序调用,而不管数据在应用程序的内部的存储方式是什么。

  2. URI在ContentProvider有什么用?
    ContentProvider通过URI来发布自己的共享数据的接口。
    具体URI格式如下:
    Schema(协议):Android中定义好的一个标准,也就是web中协议一样。ContentProvider的Scheme的协议已经由Android规定好了:content://
    Authority(主机名):ContentProvider对应地址,相当于web中域名一样。
    Path(路径):数据在主机中对应的位置,相当与web中域名对应的路径一样
    下面分别是一个contentProvider对应的URI和web对应的URI
    ContentProvider:content://blackdog.myprovider/users/blackdog
    Web:http://www.baidu.com/index.html

service和Activity的交互方式

  1. 通过广播的方式
    在Activity端注册一个广播监听器,Service端的操作通过广播来传递自身的消息
    优缺点:实现非常简单,可以胜任简单级的应用。缺点是收到系统限制,系统会优先发送系统广播,因此我们自定义的广播可能会延迟,还有就是广播接收器不能处理耗时操作,否则会出现ANR

  2. 通过文件共享的方式
    Activity和Service使用同一个文件来达到传递的目的。可以使用SharedPreferences或IO方式。不过注意的是读写问题,不可一方读,一方写或同时写。
    优缺点:这种操作想linux中的管道操作一样,非常方面。但是这种方式也有缺点,当写入的数据量非常大或较为复杂的适合,就有可能导致写入和写出数据不一致的错误,同时因为经过了一个中转站,因此这样子更加耗时。

  3. Messenger交互Handler
    Messenger应用一个Handler对象,别人能够通过他来发送消息(使用Messenger.send(Message)方法),这个类允许跨进程通信。在服务端使用Handler创建一个Messenger,客户端只要获得这个服务端的Messenger对象就可以与服务端通信了。也就是说我们可以把Messenger当作Client端与Service端的传话筒。
    优缺点:
    通过Messenger来实现Activity和Service交互,稍微升入掉我们就可以知道,其实Messenger也是通过AIDL来实现。对于前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就是平时使用Handler和Thread通信一个道理

  4. Service中自定义接口交互
    自定义接口,然后创建一个Binder的子类来实现这个方法,接着Activity通过BindService来获取Binder对象,这样子Activity就可以通过Binder与Service通信了
    优缺点:这种方法简单实用,拓展性强,但是缺有一个缺陷,比如说需要延迟一些再开始获取Service端的数据,从而无法实现零开始同步。

  5. AIDL交互
    原理:属于Android的IPC机制,常用语跨进程通信,主要实现原理基于底层的Binder机制。
    优缺点:AIDL使用较为复杂,但是效率高,拓展性好。同时很多系统服务就是用这种方式完成应用程序通信的。

UI线程与非UI线程的交互

  1. Handler方式
    前面有讲

  2. Activity.runOnUiThread(Runnable)
    这种方法较简单,看过底层代码知道,其实就是调用了Activity中mHandler.post(Runnable),在非UI线程中调用Activity的runOnUiThread(Runnable)即可。

  3. View.Post(Runnable)
    这种方法的底层是使用了AttachInfo中的mHandler.post(Runnable)方法
    在非UI线程中调用View的post(Runnable)方法

  4. AsyncTask
    AsyncTask是Android专门为UI线程与非UI线程交互设计的,通过AsyncTask,我们可以非常方便的实现UI线程与非UI线程的通信。但是注意了,只能在UI线程中创建AsyncTask,并且AsyncTask的execute()方法只能调用一次,否者会报错
    AsyncTask

View的绘制过程

View的绘制过程包括三个阶段:measure,layout,draw.
1. Measure过程:计算视图的大小,其中相关的方法有三个:
public final void measure(int widthMeasureSpec,int heightMeasureSpec)
protected final void setMeasureDimension(int measureWidth,int measureHeight)
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec);
其中measure和setMeasureDimension方法是final的,我们不用重写,需要重写的是onMeasure方法。maesure方法会调用onMeasure,onMeasure方法会调用setMeasureDimension
需要注意的是:在重写onMeasure方法的时候,要调用setMeasureDimension或super.onMeasure方法来设置自身的mMeasureWidth和mMeasureHeight,否则,会抛异常。
MeasureSpec的三种模式:

  1. EXACTLY:代表父视图希望子视图的大小应该由specSize的值来决定.
  2. AT_MOST:表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize.
  3. UNSPECIFIED:表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况很少见。
    Mode和Size的值由widthMeasureSpec和heightMeasureSpec决定,widthMeasureSpec和heightMeasureSpce来自父视图,这两个由父视图经过计算后传递给子视图,说明一定程度上父视图是决定子视图的大小。最外层的根视图的值是通过getRootMeasureSpec方法获得的,如果根视图的的值是MATCH_PARENT或者固定的值specMode值是MeasureSpec.EXACTLY。WRAP_CONTENT则是AT_MOST
    Measure是个复杂的过程,因为一个布局中一般都会包含多个子视图,每个视图都需要经历一次measure过程。ViewGroup中定义了一个measureChindren方法来测量子视图的大小,在这个方法里面遍历当前布局下的所以子视图,然后逐个调用measureChild方法来测量相应子视图的大小。
  4. Layout过程:确定视图的位置.layout是从view的layout方法开始的。
    layout方法中调用了onLayout方法,这个方法是个空方法,需要子类去实现。
  5. Draw过程:调用了measure和layout后,最终会发起draw过程并且最后调用draw方法,在draw方法中会调用onDraw方法,这个方法需要子类去实现。

java sleep,wait,notify的区别

sleep方法来之Thread类,而wait,notify方法来自Object类。
sleep():
使当前线程暂停执行一段时间,让其他线程有机会继续执行,但它并释放对象锁。也就是如果由Synchronized同步块,其他线程仍然不能访问共享资源。Sleep()可以使低优先级的线程,同优先级,高优先级的线程有执行的机会。
wait():
使当前线程暂停执行并释放对象锁,让其他线程可以进入到同步块中,当前线程被放入对象等待池中。
当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中。只有锁标志等待池中的线程能够获取锁标志,如果锁标志等待吃中没有线程,则notify()不起作用。
notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中

如何定位和解决Andorid的内存溢出问题

Android的内存溢出的原因:
1. 内存泄露导致
由于程序的失误,长期保持某些资源的引用,垃圾回收器无法回收它,从而导致内存泄露的发生。
Android中常见的就是Activity被引用在调用finish之后却没有被释放,第二次打开Activity又重新创建,这样的内存泄露不断的发生,则会导致内存的溢出。
2、 占用内存较多的对象
保存多个耗用内存过大的对象(如Bitmap)或加载单个超大的图片,造成内存超出限制
常见的内存泄露问题以及解决方案:
1、 引用没释放造成的内存泄露
A、 注册没取消造成的内存泄露
这种Android内存泄露比纯java的内存泄漏更严重,因为其他一些Android程序可能引用系统的Android程序的对象(比如注册机制)。既是Android程序已经结束了,但是别的应用程序仍然还有对Android程序的某个对象的引用,泄露的内存依然不能被垃圾回收。
B、 集合中对象没清理造成的内存泄露
通常我们把一些对象的引用加入到集合中,当我们不需要该对象的时,并没有把它的引用从集合中清理掉,这样这个集合就会越累越大。如果这个集合是static的话 那情况就更加严重了。
C、 static
static是java关键字,当他修饰成员的时候,那么这个变量就属于该类,而不是该类的实例。
D、 线程(内部类的使用)
线程产生的内存泄露的主要原因在于线程的生命周期不可控。如果我们的线程是Activity的内部类,所以这个线程会保留一份外部的Activity的引用,在这个线程没有结束的时候,是不会释放这个引用的,因此就造成了内存泄漏。
如果非静态内部类的方法中,存在生命周期大于其所在的类。那就有问题了。比如:AsyncTask,Handler,这两个类都似乎方便开发者执行异步任务,但是这两个都跳出了Activity/Fragment的生命周期。
避免引用造成的内存泄漏:
A、 应该尽量避免static变量成员变量引用资源耗费过多的实例,比如Context
B、 Context尽量使用ApplicationContext,因为Application的Context的生命周期较长,引用他不会出现内存泄露的问题
C、 使用的周期是否在Activity周期内,如果超出,必须使用Application;常见的情况有:AsyncTask,Thread,第三方库初始化。但是有些情景只能用activity:比如说:对话框,各种View,startActivity等。总而言之,要尽可能使用Application
D、 使用WeakReference代替强引用。
E、 将线程的内部类,改为静态内部类。因为非静态内部类会持有一个所属类的实例,如果所属类的实例已经结束生命周期,但是内部类的方法仍在执行,就会持有其主体的引用,也就是造成了内部类的所属类的对象得不到释放。静态类编译后和 非内部类一样,有自己独立的类名。不会悄悄引用所属类的实例,所以不容易泄露。
F、 如果 需要应用Activity,使用弱引用。
2. 资源对象没关闭造成的内存泄露
资源性对象比如(Cursor,File文件等)往往都使用了缓冲,我们在不适用的时候,应该及时关闭他们,一边他们的缓冲能及时回收内存。而不是等待GC来处理,他们的缓冲不仅存在与java虚拟机内,还存在与java虚拟机外。如果我们仅仅是把它的引用置空(既设置为null),而不关闭它,往往会造成内存泄露。因为有些资源性对象,比如SQLite,Cursot(在析构方法finalize(),如果我们没有关闭它,它自己close),如果我们没有关闭它,系统在回收它的时候才会关闭它,这样的效率就太低了。而且android数据库中对Cursor资源的是有限制个数的,如果不及时close掉它,会导致其他地方无法或者这些资源。
3. 不良代码造成的压力
有些代码本身不造成内存泄露,但是他们,或是对没使用的内存没进行及时的释放,或是没有有效的利用已有的对象而是频繁的申请内存,对内存的回收和分配会造成较大的影响的,迫使虚拟机为应用程序分配更多的内存,造成一些不必要的开销。
A、 Bitmap使用后没调用recycle()
Bitmap对象在不使用的时候,应该先调用recycle()方法来释放内存,然后才设置为null,因为调用这个方法会释放Bitmap的占用的内存。
B、 在构造Adapter时,没有使用缓存的converView
在使用BaseAdapter,ArrayAdapter,SimpleAdapter,SimpleCursorAdapter的时候,在实现getView方法的时候,如果我们不使用converView,那么每次都会重新实例化一个View对象,这样既浪费时间,也会造成内存垃圾,给GC带来压力,虚拟机不得不分配更多的内存,造成不必要的内存开支。因此最好使用创建Holder的方法来缓存converView。或者用谷歌推荐的RecyclerView来代替ListView。RecyclerView的RecyclerAdapter封装好的Holder的,但是有一点就是ListView有onItemClickListener,RecyclerView缺没有,要自己去实现。
4、 占用内存较多的对象(图片过大)造成内存溢出及其解决方案
1、 等比例缩小图片
尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些方法在完成了decode后,最终都是调用java层的createBitmap方法来完成的,需要消耗更多内存。因此,改进先通过BitmapFactory.decodeStream方法,创建出一个Bitmap,再将其设为ImageView的source,因为decodeStream方法是直接调用native(jni)来完成图片的decode的,从而节省了java层的空间。
有时候,我们显示的区域较少,没有必要将整个图片的加载出来,只要加载一个用户可接受的最小比例的图片即可。
通常的做法是先
A、设置BitmapFactory.Options.inJustDecodeBounds = true
B、通过Bitmap.decodeXXX(xxx,options)方法来加载图片的高和宽到options去(此时获取到的Bitmap为null)
C、计算图片的缩放比例,然后设置options.inSampleSize = (计算出来的缩放比例)
D、设置options.inJustDecodeBounds = false,然后通过Bitmap.decodeXXX(xxx,options)方法来加载图片,此时的图片则是我们缩放过的图片。
2、对图片才使用软引用,及时的进行recycle()操作
在使用大量的图片的时候,很可能会超过java堆内存限制。因此在用完Bitmap时 要记得调用recycle()。软引用再的好处是在内存不足的时候才会回收它,而不想弱引用,一但被垃圾回收发现就会被回收
2、 看页面中是否存在大的图片,比如说背景图之类的,去除xml的相关设置,改在程序中设置背景图。
3、 在页面切换时尽可能少的重复使用一些代码。比如:重复调用数据库,反复调用某些对象等。
4、 使用多进程,把图片加载这种耗内存的操作放在一个新的进程中。

静态成员类,非静态成员类有什么区别

静态成员类:
这个类的实例化不需要其所在的外部类,,它不会持有外部类的引用,因此不会发生内存泄露,如果它的外部类是非静态的,则不可以访问外部类的对象。
非静态成员类:
这个类的实例化需要其所在的外部类,会持有一个外部类的引用,如果它的生命周期超过类外部类,则会发生内存泄露,因此在确定了它的生命周期比外部类大的时候,因考虑使用静态成员类

Android中处理奔溃异常

通常在开发的时候,我们难免会遇到一种情况,程序运行着突然因为一些未处理的异常而使应用程序出现Force close(强制退出的按钮 ),首先这个对话框不是特别美观,给用户的体验也不好。如果你的项目上线后遇到这种情况就更加让人捉急,因为你不知道是哪里除了问题。因此我们就需要自己完善未处理异常的情况。当异常发生的时候,把异常发生信息(cpu架构,android版本,权限获取等)发送到服务端,以便可以快速的定位异常发生的位置,同时,我们也可以自定义一些个性化的界面来美化奔溃后的对话框或界面。
UncaughtExceptionHandler简介
线程未捕获异常处理器,用来处理未捕获异常。如果程序出现了为捕获异常,默认会弹出系统中强制关闭的对话框。我们需要实现这个接口,并注册为程序中默认为捕获异常处理。这样,当未捕获异常发生后,就可以做一些个性化的异常处理操作。

service的生命周期

1、 started service
被开启的service是通过其他组件调用startService来创建的。
这种service可以无限的运行下去,除非调用了显示的调用stopSelf()方法或者其他组件调用stopService()才能停止它,当service停止时系统则会销毁它。
它的生命周期
onCreate() -> onStartCommand() –服务正在运行->onDestory()-服务停止了。
它完整的生命周期
从onCreate()开始到onDestory()结束,因此,我们应该在onCreate()方法中做初始化工作,在onDestory()方法中释放资源。
比如说:音乐播放的service在onCreat()方法中创建音乐播放的线程,在onDestory()方法中停止这个线程。
onStartCommand()
在onStartCommand()方法中处理请求。
注意的是:Service的onCreate()方法在生命周期中只会执行一次,如果通过startService开启一个已经开启的Service的话,那它的onCreate()方法将不会被调用。
2、 bound service
被绑定的service是其他组件调用bindService()来创建的。
客户可以通过IBinder接口来和service进行通信。
客户也可以通过unBinderService()方法来关闭这里连接。
注:一个service可以同时和多个客户绑定当多个客户都解除绑定之后,只有所有的客户端和service都解除绑定后,系统会销毁service.
它的生命周期
onCreate->onBind-服务正在运行并且和客户绑定在一起->onUnbind->onDestory
完整生命周期
除了其中的onStartCommand()和onBind + onUnbind()不同而已,其他的与startService一样
onBind(),onUnbind()
客户端可以通过onBind来获取与service通信的IBinder对象,完成与service的通信。然后千万记住在不需要的service的时候,一定要调用onUnbind方法,否者会造成内存泄露。
3、 (started + bound) service
可以存在一个客户端通过startService方式来开启service,另外一个客户端通过bindService方式来绑定service。这个时候,需要注意的是,如果通过stopService或stopSelf()关闭service的时候,但还有客户端通过bind方式与service绑定的话,service是不会destory的,同时如果所有通过bind方式绑定的service全部都解除绑定后(既unbind),但没有显式的调用stopService或stopSelf()的话,service也不会destory.

IntentService

简介
IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService和启动传统的Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且每次执行一个,知道执行完毕。
总结下它的特点
1、 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intent。
2、 它创建一个工作队列,来组个发送intent给onHandleIntent()
3、 不需要主动调用stopSelf()方法来结束服务,因为在所有的intent被处理完后,系统会自动关闭服务
4、 默认实现的onBind()返回null
5、 默认实现的onStartCommand()的目的是将intent插入到工作队列中
底层其实是使用Handler来实现的。

广播发送方式有几种,有什么区别

广播的分类:
1、 普通广播:
接受者接受到的顺序是无序的,几乎同时到达。可以使用一下方式发送:
sendBroadcast(intent) / sendBroadcast(intent,receiverPermission);
2、 系统广播:
Android系统中内置了多个系统广播,执拗设置到手机的基本操作,基本上都会发出相应的系统广播。如开机启动,网络状态的改变,拍照,屏幕关闭与开启,电量不足等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action。我们可以注册这些广播来监听系统变换
3、 有序广播
接收者按优先级的高低来接收这个广播,并且接收到有序广播的接收者可以截断广播的发送。先后顺序判定的标准是:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性的值大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。
他的发送方式是:
sendOrderedBroadcast(intent,receiverPermission,…)
4、 粘性广播:已不推荐使用了
5、 本地广播
本地广播可以理解成一种局部广播的形式,广播的发送者和接收者都要在同一个app内。
非本地广播可能存在的安全隐患:
A、 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并且处理
B、 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播的具体信息。
常见的增加安全性的方案是:
A、 对于同一个App内部发送和接收广播,将exported属性设置为false,使得非本App内部发出的广播不被接收
B、 在广播发送和接收时都增加上相应的permission,用于权限验证
C、 发送广播的时候,指定广播接收者的报名,具体是通过intent.setPackage(packageName)指定的,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收者中
本地广播相比与非本地广播的优势:
1、 安全性高
2、 更加高效

Android的基本框架结构

1、 Application
Android装配一个核心应用程序集合,包括电子邮件客户端,SMS程序等。所有的应用程序都是用java编程语言写的
2、 Application Framework
提供系统服务的一层,提供许多管理器,比如说Activity Manager,Window Manager等
3、 Libraries
Android包含一个C/C++库的集合,供Android系统的各个组件使用。这些功能通过Android的应用框架(Appliation framework)暴露给开发者,下面是一些核心库
1、 系统C库:标准C系统库的BSD衍生,调整为嵌入式linux设备
2、 媒体库:基于PacketVideo的OpenCore,这些库支持播放和录制许多流行的音频和许多流行的视频格式,以及静态图像文件,包括MPEG4,AAC,PNG,JPG等
3、 界面管理:广利范文显示子系统和无缝组合多个应用程序的二维和三维图形层
4、 LibWebCore:新式的web浏览器引擎
5、 SGL:基本的2D图形引擎
6、 3D库:基于OpenGL ES 1.0APIs的实现,库使用硬件3D加入或包含高度优化的3D软件光栅
7、 FreeType:位图和矢量字体渲染
8、 SQLite:所有应用程序都可以使用的强大而轻量级的关系型数据库
4、 Android Runtime
Android运行时,提供虚拟机
5、 Linux Kernel
Android基于Linux2.6提供核心系统服务,例如:安全,内存管理,进程管理,网络堆栈,驱动模型等。