首页 > 代码库 > 一些面试基本知识(Android篇一)
一些面试基本知识(Android篇一)
Android
Activity设计模式之MVC模式
参考博客、文章 http://www.cnblogs.com/liqw/p/4175325.html
MVC即Model-View-Controller
M:逻辑模型。 负责建立数据结构和相应的行为操作处理。
V:视图模型。 负责在屏幕上渲染出相应的图形信息展示给用户看。
C:控制器。 负责截获用户的按键和屏幕触摸等事件,协调Model对象和View对象。
用户与视图交互,视图接受并反馈用户的动作;视图把用户的请求传给相应的控制器,由控制器决定调用哪个模型,然后由模型调用相应的业务逻辑对用户请求进行加工处理,如果需要返回数据,模型会把相应的数据返回给控制器,由控制器调用相应的视图,最终由视图格式化和渲染返回的数据,对于返回的数据完全可以增加用户体验效果展现给用户。
一个模型可以有多个视图,一个视图可以有多个控制器,一个控制器也可以有多个模型。
1. 模型(Model)
Model是一个应用系统的核心部分,代表了该系统实际要实现的所有功能处理。
比如:在视频播放器中,模型代表一个视频数据库及播放视频的程序函数代码;在拍照应用中,模型代表一个照片数据库,及看图片时的程序函数代码。在一个电话应用中,Model代表一个电话号码簿,以及拨打电话和发送短信的程序函数代码。
Model在values目录下通过xml文件格式生成,也可以通过硬编码的方式直接Java代码生成。==View和Model是通过桥梁Adapter来连接起来。==
2. 视图 (View)
View是软件应用传送给用户的一个反馈结果。它代表软件应用中的图形展示、声音播放、触觉反馈等职责。视图的根节点是应用程序的自身窗口。比如,视频播放器中可能包含当前播放的画面,这个画面就是一个视图。另一个视图组件可能是该视频的文字标题。再一个就是一些播放按键,比如:Stop、Start、Pause等按钮。
View在layout目录下通过xml文件格式生成,用findViewById()获取;也可以通过硬编码的方式直接Java代码生成。
3. 控制器 (Controller)
Controller在软件应用负责对外部事件的响应,包括:键盘敲击、屏幕触摸、电话呼入等。Controller实现了一个事件队列,每一个外部事件均在事件队列中被唯一标识。框架依次将事件从队列中移出并派发出去。
Android中最典型MVC是ListView,要显示的数据是Model,界面中的ListView是View,控制数据怎样在ListView中显示是Controller。
Activity生命周期、启动模式
参考书:《第一行Android代码》
Android是使用任务栈(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。在默认情况下,每当我们启动一个新的活动,他就会在返回栈中入栈,并处于栈顶的位置。而每当我们按下Back键或调用finsh()方法去销毁一个活动时,处于栈顶的活动会出栈。
活动在其生命周期中最多会有四种状态:运行状态、暂停状态、停止状态、销毁状态。
Activity类中定义了七个回调方法,覆盖了活动生命周期的每一个环节:
1. ==onCreate()==:活动第一次创建时调用,完成活动的初始化操作,比如加载布局、绑定事件等。
2. ==onStart()==:活动由不可见变为可见时调用。
3. ==onResume()==:活动准备好和用户进行交互时调用。此时活动一定位于返回栈的栈顶,并且处于运行状态。
4. ==onPause()== :系统准备去启动或者恢复另一个活动时调用。通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据。但这个方法执行速度一定要快,不然会影响到新的栈顶活动的使用。
5. ==onStop()==: 活动完全不可见的时候调用。
6. ==onDestroy()== :活动被销毁前调用,之后活动的状态将变为销毁状态。
7. ==onRestart()== :活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
活动的启动模式分为四种:standard,singleTop,singleTask,singleInstance.
可以根据实际的需求为Activity设置对应的启动模式,从而可以避免创建大量重复的Activity等问题。
设置Activity的启动模式,只需要在AndroidManifest.xml里对应的标签设置Android:launchMode属性,例如:
<activity
android:name=".A1"
android:launchMode="standard" />
==standard==:
默认模式,不用写配置。系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建一个新的实例。因此,在standard模式下,可以有多个相同的实例,也允许多个相同Activity叠加。退出时,按Back依次按栈顺序退出。
==singleTop==:
可以有多个实例,但是不允许多个Activity叠加。即,如果Activity在栈顶的时候,启动相同Activity不会创建新的实例,而会调用其onNewInstant方法。
==singleTask==:
只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例;若存在,则会把task中在其之上的其他Activity destroy掉并调用他的onNewInstant方法。
==singleInstance==:
启用一个新的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,解决了共享活动实例的问题。即 只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。
例子:有活动a,b,c,设置b为singleInstance,按顺序打开a->b->c,然后再退出。其顺序为c->a->b.原理很简单,因为a,c是存放在同一返回栈中的,在c里退出直接到a,在a里退出该栈清空。跳到b的返回栈中继续退出。
点击事件传递dispatch,intercept,onTouchEvent
参考文章、博客: http://blog.csdn.net/morgan_xww/article/details/9372285/
http://www.cnblogs.com/lwbqqyumidi/p/3500997.html
跟touch事件相关的3个方法:
public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event
public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event
public boolean onTouchEvent(MotionEvent ev); //用来处理event
事件分发:public boolean dispatchTouchEvent(MotionEvent ev)
当有监听到事件时,首先由Activity的捕获到,进入事件分发处理流程。
如果事件分发返回true,表示改事件在本层不再进行分发且已经在事件分发自身中被消费了。至此,事件已经完结。
如果事件分发返回 false,表明事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。
事件拦截:public boolean onInterceptTouchEvent(MotionEvent ev)
如果 onInterceptTouchEvent 返回 true,则表示将事件进行拦截,并将拦截到的事件交由本层控件 的 onTouchEvent 进行处理;
如果返回结果是false;则表示不对事件进行拦截,事件得以成功分发到子View。并由子View的dispatchTouchEvent进行处理。
如果返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,交由子View的dispatchTouchEvent进行处理。
事件响应:public boolean onTouchEvent(MotionEvent ev)
如果onTouchEvent返回true,表示onTouchEvent处理完事件后消费了此次事件。此时事件终结,将不会进行后续的冒泡。
如果onTouchEvent返回false,事件在onTouchEvent中处理后继续向上层View冒泡,且有上层View的onTouchEvent进行处理。
如果返回super.onTouchEvent(ev),则默认处理的逻辑和返回false时相同。
service,两种启动方式及特点,如何与activity通信
参考博客、文章:http://www.jianshu.com/p/2fb6eb14fdec
==采用start的方式开启服务==
步骤:
- 定义一个类继承Service
- 在Manifest.xml文件中配置该Service
- 使用Context的startService(Intent)方法启动
- 不再使用时,调用stopService(Intent)方法停止
用这种start方式启动的Service的生命周期如下:
onCreate()—>onStartCommand()(onStart()方法已过时) —> onDestory()
说明: 如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStart()和onStartCommand()。
服务停止的时候调用 onDestory()。服务只会被停止一次。
特点: 一旦服务开启跟调用者(开启者)就没有任何关系了。
开启者退出了,开启者挂了,服务还在后台长期的运行。
开启者不能调用服务里面的方法。
==采用bind的方式开启服务==
步骤:
- 定义一个类继承Service
- 在Manifest.xml文件中配置该Service
- 使用Context的bindService(Intent, ServiceConnection, int)方法启动
- 不再使用时,调用unbindService(ServiceConnection)方法停止
用这种start方式启动的Service的生命周期如下:
onCreate()—>onStartCommand()(onStart()方法已过时) —> onDestory()
说明: 如果服务已经开启,不会重复的执行onCreate(), 而是会调用onStart()和onStartCommand()。
服务停止的时候调用 onDestory()。服务只会被停止一次。
特点: 一旦服务开启跟调用者(开启者)就没有任何关系了。
开启者退出了,开启者挂了,服务还在后台长期的运行。
开启者不能调用服务里面的方法。
Handler-Looper-MessageQueue,AsyncTask原理相关
参考:《第一行代码》郭霖
参考: http://www.cnblogs.com/devinzhang/archive/2012/02/13/2350070.html
由于Android不允许在子线程中进行UI操作,因为UI是线程不安全的,如果想要更新UI则必须在主线程中进行,否则就会异常。Android提供的一套异步消息处理机制,完美的解决在子线程中进行UI操作的问题。
Android中的异步消息处理主要由四个部分组成:Message、Handler、MessageQueue和Looper。
Message:
Message是在线程之间传递的消息,他可以在内部携带少量的信息,用于在不同线程之间交换数据。比如what字段、arg1、arg2带一些整型数据,obj字段携带一个Object对象。
Handler
Handler顾名思义就是处理者的意思,它主要是用于发送和处理消息的,发送消息一般使用Handler的sendMessage()方法,而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage()方法中。
MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程只有一个MessageQueue对象。
Looper
Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程也只有一个Looper对象。
==异步消息处理流程:==
1. 主线程中创建一个Handler对象,并重写handleMessage()方法
2. 当子线程需要进行UI操作时,就创建一个Message对象,并通过handler.sendMessage()将这条消息发送出去
3. 这条消息被添加到MessageQueue的队列中等待被处理
4. Looper一直尝试从MessageQueue中提出待处理消息,分发会Handler的handleMessage()方法中。
==AsyncTask:==
AsyncTask是Android提供的轻量级异步类,可以直接继承。其实现原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已。
由于AsyncTask是一个抽象类,要使用必须要创建一个子类去继承他,并提供三个泛型参数,同时重载几个方法:
AsyncTask定义了三种泛型类型Params,Progress和Result
- Params:在执行AsyncTask时需要传入的参数,可用于在后台任务中使用(doInBackground方法的参数类型)如HTTP请求的URL
- Progress:后台任务执行时,如果需要在界面上显示当前的进度,则指定进度类型
- Result:后台任务的返回结果类型
例子:一个最简单的自定义AsyncTask:
class DownloadTask extends AsyncTask<void,Integer,Boolean>{
}
//Params为void,表示在执行AsyncTask的时候不需要传入参数给后台任务
//Progress为Integer,表示用整型数据来作为进度显示单位
//Result为Boolean,表示使用布尔型数据来反馈执行结果
目前自定义的DownloadTask还是一个空任务,还需要重写AsyncTask中的几个方法才能完成对任务的定制。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:
- doInBackground(Params…): 这个方法的所有代码都会在后台子线程中运行,比较耗时的操作都可以放在这里。任务一旦完成就可以通过return语句来将任务的执行结果返回(这里返回类型就是当初给定的第三个泛型参数)。注意这里不能直接操作UI.在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
- onPostExecute(Result):
当后台任务执行完毕并通过return语句返回时,这个方法就会被调用。返回的数据会作为参数传递到此方法中,相当于Handler 处理UI的方式。可以利用返回的数据进行一些UI操作。此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话你还得重写以下这三个方法,但不是必须的:
- onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
- onProgressUpdate(Progress…) 当后台任务中调用了publicProgress(Progress…)方法后,这个方法很快就会被调用。可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
- onCancelled() 用户调用取消时,要做的操作
AsyncTask的局限性
AsyncTask的优点在于执行完后台任务后可以很方便的更新UI,然而使用它存在着诸多的限制。先抛开内存泄漏问题,使用AsyncTask主要存在以下局限性:
- 在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;
- 在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
- AsyncTask对象必须在主线程中创建
- AsyncTask对象的execute方法必须在主线程中调用
- 一个AsyncTask对象只能调用一次execute方法
一个简单的实例见: http://www.cnblogs.com/absfree/p/5357678.html
动画相关
一、Drawable Animation
也就是所谓的帧动画,Frame动画。指通过指定每一帧的图片和播放时间,有序的进行播放而形成动画效果。可以理解成多张图片播放,图片不能过大。
二、View Animation
视图动画,也就是所谓补间动画,Tween动画。指通过指定View的初始状态、变化时间、方式,通过一系列的算法去进行图形变换,从而形成动画效果,主要有Alpha、Scale、Translate、Rotate四种效果。注意:只是在视图层实现了动画效果,并没有真正改变View的属性。view的实际位置还是移动前的位置
三、Property Animation
属性动画,通过不断的改变View的属性,不断的重绘而形成动画效果。相比于视图动画,View的属性是真正改变了。注意:Android 3.0(API 11)以上才支持。
作者:AudienL
链接:https://www.zhihu.com/question/19703349/answer/153065511
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
binder机制
这里只对Binder机制进行一个简要的概括,详细binder的介绍在这里就不展开了。
http://www.linuxidc.com/Linux/2012-07/66195.htm
Binder是Android系统中的一种IPC进程间通信结构。
在Android中,每一个应用程序都运行在独立的进程中,这也保证了当其中一个程序出现异常而不会影响另一个应用程序的正常运转。在许多情况下,我们activity都会与各种系统的service打交道,很显然,我们写的程序中activity与系统service肯定不是同一个进程,但是它们之间是怎样实现通信的呢?Binder是android中一种实现进程间通信(IPC)的方式之一。Binder属于一个驱动,工作在linux层面,运行在内核态,它的操作完成是基于一段内存。所以我们开发的程序中对binder的使用都是通过系统的调用来完成的。
Binder的整个设计是C/S结构,Binder架构由服务端,binder驱动,客户端三个部分构成。 客户端进程通过binder驱动获取服务端进程的代理,并通过向这个代理接口方法中读写数据来完成进程间的数据通信。
为什么Android选用Binder来实现进程间通信?
1. 安全。Android是一个开放式的平台,所以确保应用程序安全是很重要的。每个进程都会被Android系统分配UID和PID,其中进程的UID是可用来鉴别进程身份。不像传统的在数据包里加入UID,这就让那些恶意进程无法直接和其他进程通信,保证了进程间通信的安全性。
1. 高效。socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。在手机这种资源紧张的情况下很重要。
BroadCast动态、静态注册
参考:http://blog.csdn.net/q908555281/article/details/48541939
在Android中,Broadcast是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver是对发送出来的 Broadcast进行过滤接受并响应的一类组件。下面将详细的阐述如何发送Broadcast和使用BroadcastReceiver过滤接收的过程:
首先在需要发送信息的地方,把要发送的信息和用于过滤的信息(如Action、Category)装入一个Intent对象,然后通过调用 Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把 Intent对象以广播方式发送出去。
当Intent发送以后,所有已经注册的BroadcastReceiver会检查注册时的IntentFilter是否与发送的Intent相匹配,若匹配则就会调用BroadcastReceiver的onReceive()方法。所以当我们定义一个BroadcastReceiver的时候,都需要实现onReceive()方法。
注册BroadcastReceiver有两种方式:
1. 静态注册
静态的在AndroidManifest.xml中用标签生命注册,并在标签内用标签设置过滤器。
<receiver android:name=".Receiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
2. 动态注册
动态的在代码中先定义并设置好一个 IntentFilter 对象,然后在需要注册的地方调Context.registerReceiver()方法,如果取消时就调用 Context.unregisterReceiver()方法。
UpdateBroadcast broadcast= new UpdateBroadcast();
IntentFilter filter = new IntentFilter("com.unit.UPDATE");
registerReceiver(broadcast, filter);
1.动态注册的广播 永远要快于 静态注册的广播,不管静态注册的优先级设置的多高,不管动态注册的优先级有多低>\
2.动态注册广播不是 常驻型广播 ,也就是说广播跟随activity的生命周期。注意: 在activity结束前,移除广播接收器。
静态注册是常驻型 ,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。
一些面试基本知识(Android篇一)