安卓面试基础知识总结

1   Activity

1.1 Activity的概念

是Android应用层开发的四大组件之一,主要负责和用户交互部分,有自己的生命周期,在其上可以布置按钮,文本框等各种控件,简单来说就是Android的UI部分。

1.2 Activity与View的区别

1)  Activity是四大组件中唯一一个用来和用户进行交互的组件。可以说Activity就是android的视图层。

2)  如果再细化,Activity相当于视图层中的控制层,是用来控制和管理View的,真正用来显示和处理事件的实际上是View。

3)  每个Activity内部都有一个Window对象, Window对象包含了一个DecorView(实际上就是FrameLayout),我们通过setContentView给Activity设置显示的View实际上都是加到了DecorView中。

1.3 Activity生命周期

1.3.1 生命周期主干

1.3.2 其他中转方法

1.4 Activity启动模式

1.4.1 四种启动模式

1.4.2 配置样例

1.5 Activity启动方法

1)   在一个Activity中调用startActivity()方法。

直接启动Activity,不带请求码。

2)   在一个Activity中调用startActivityForResult()方法。

带请求码启动Activity。

1.6 请求码与响应码

2   BroadcastReceiver

2.1 概念

BroadcastReceiver也就是“广播接收者”的意思,顾名思义,它就是用来接收来自系统和应用中的广播。

2.2 应用场景

在Android系统中,广播体现在方方面面:

eg:

1.   当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能;

2.   当锁屏或者点亮屏幕时就会产生一条广播,接收这条广播就可以实现一些暂停或者开启一些耗电进程的功能。

3.   当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示和保存数据等操作;

4.   当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户及时保存进度;

2.3 注册

2.3.1 静态注册

2.3.1.1   概念

静态注册是在AndroidManifest.xml文件中配置的。

2.3.2 动态注册

2.3.2.1   概念

动态注册需要在代码中动态的指定广播地址并注册。

2.3.2.2   需要注意的事项

RegisterReceiver是android.content.ContextWrapper类中的方法,Activity和Service都继承了ContextWrapper,所以可以直接调用。在实际应用中,我们在Activity或Service中注册了一个BroadcastReceiver,当这个Activity或Service被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,需要在特定的地方执行解除注册操作:生命周期的onDestroy()。

有部分广播接受者,涉及到用户的敏感内容,需要在权限文件中声明。如开机完成的广播,用户电量变化的广播,用户网络状态发生改变的广播

2.3.3 生命周期

1.   广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁

2.   广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框

3.   最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉

4.   耗时的较长的工作最好放在服务中完成

3   Service

3.1 概念

服务是看不到界面的,,就是一个没有界面的Activity, 并且长期在后台运行的一个组件.。

3.2 为什么用服务?

进程优先级, 回收时是从5~1, 从低到高

Foreground process 前台进程

Visible process 可视进程

Service process 服务进程

Background process 后台进程

Empty process 空进程

回收的优先级: 先回收空进程, 一个一个回收的, 当内存够用时, 不再回收空进程. 如果空进程回收完毕后, 内存还是不够用时, 继续向上一个一个的回收后台进程. 依次类推.

当系统内存不够用时, 需要回收服务进程时, 当系统内存又够用时, 会重新启动服务. 当用户去手动的把服务关闭时, 服务不会再重启了

3.3 作用

由于ANR对Activity和BroadcastReceiver响应时间的限制(Activity对事件响应不超过5秒,BroadcastReceiver执行不超过10秒),使得在其中都不适合执行较耗时操作,这样像网络、数据库、复杂计算这类耗时操作的执行就需要一个组件来承担。Service作为Android四大组件之一,其功能之一就是耗时操作的执行,主要功能如下:

a. 执行需要长时间运行的操作,这个操作不与用户进行交互,如网络下载、大文件I/O、复杂计算、监听手机状态。

b. 应用内或应用间数据通信,Android每个应用程序都在自己的dalvik虚拟机中运行,一个应用是不允许访问其他应用的内存信息的,为此Android引入了Content Provider在不同应用间共享数据,BroadcastReceiver广播信息给不同应用程序,但Content Provider更多用于数据的共享,BroadcastReceiver广播的信息会被所有应用接收较耗费系统资源,对于两个应用间动态的进行交互还需要通过Service来完成。

3.4 启动方式

3.4.1 直接启动

Activity开启完服务后就不管服务了. Activity和服务没有关系. startService开启的服务, 只有stopService可以关闭

3.4.2 绑定启动

绑定服务, 生命周期方法会执行: onUnbind -> onDestory 服务销毁了.

在activity中调用service中的方法.

步骤:

调用bindService方法绑定服务

1.   在Activity中定义一个连接桥的内部类, 会在bindService方法传递给service.

2.   在service服务中onBind方法中返回一个IBinder接口对象.

3.   在service类中定义一个IBinder的内部实现类, 在onBind方法返回.

4.   当onBinder方法返回完对象后, activity中连接桥里的onServiceConnected会被调用, 其中形参IBinder service就是service类中onBind返回的对象.

5.   activity得到了service中的内部类对象, 点击按钮是调用内部类中的forwardBuyTicket方法, 此方法会转调服务中buyTicket方法.

3.5 生命周期

Service的生命周期 (适用于2.1及以上)

1. 被startService的

无论是否有任何活动绑定到该Service,都在后台运行。

onCreate(若需要) -> onStart(int id, Bundle args).  多次startService,则onStart调用多次,但不会创建多个Service实例,只需要一次stop。该Service一直后台运行,直到stopService或者自己的stopSelf()或者资源不足由平台结束。

2. 被bindService的

调用bindService绑定,连接建立服务一直运行。未被startService只是BindService,则onCreate()执行,onStart(int,Bund,le)不被调用;这种情况下绑定被解除,平台就可以清除该Service(连接销毁后,会导致解除,解除后就会销毁)。

3. 被启动又被绑定

类似startService的生命周期,onCreate onStart都会调用。

4. 停止服务时

stopService时显式onDestroy()。或不再有绑定(没有启动时)时隐式调用。有bind情况下stopService()不起作用。

4   ContentProvider

4.1 ContentProvider

 数据库在Android当中是私有的,当然这些数据包括文件数据和数据库数据以及一些其他类型的数据。

 不能将数据库设为WORLD_READABLE,每个数据库都只能创建它的包访问,

 这意味着只有由创建数据库的进程可访问它。如果需要在进程间传递数据,

 则可以使用AIDL/Binder或创建一个ContentProvider,但是不能跨越进程/包边界直接来使用数据库。

 一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。

 也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去

 外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,

 重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据,

 当然,中间也会涉及一些权限的问题。下边列举一些较常见的接口,这些接口如下所示。

·  query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。

·  insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。

·  update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。

·  delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

4.2 ContentResolver

外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用的 ContentResolver实例。

 ContentResolver提供的接口和ContentProvider中需要实现的接口对应,主要有以下几个。

Ø  query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。

Ø  insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。

Ø  update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。

Ø  delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

4.3 ContentObserver

在注册,翻译成中文就是内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理。ContentObserver一般和系统或第三方程序提供的Provider一起使用,这些Provider一般情况下会有一个Uri,然后ContentObserver就去监听这些Uri数据的变化,然后做出相应的处理。

4.4 ContentProvider和ContentResolver中用到的Uri

在ContentProvider和 ContentResolver当中用到了Uri的形式通常有两种,一种是指定全部数据,另一种是指定某个ID的数据。

我们看下面的例子。

·  content://contacts/people/  这个Uri指定的就是全部的联系人数据。

·  content://contacts/people/1 这个Uri指定的是ID为1的联系人的数据。

在上边两个类中用到的Uri一般由3部分组成。

·  第一部分是方案:"content://" 这部分永远不变

·  第二部分是授权:"contacts"

·  第二部分是路径:"people/","people/1"(如果没有指定ID,那么表示返回全部)。

由于URI通常比较长,而且有时候容易出错,且难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串的使用,例如下边的代码:

Contacts.People.CONTENT_URI (联系人的URI)。

在我们的实例MyProvider中是如下定义的:

public static final String AUTHORITY="com.teleca.PeopleProvider";

public static final String PATH_SINGLE="people/#";

public static final String PATH_MULTIPLE="people";

public static final Uri content_URI=Uri.parse("content://"+AUTHORITY+"/"+PATH_MULTIPLE);

5   Service如何向Activity传递数据

一个Android程序可以由多个Activity和Servier组成,在这些程序组件之间传递数据的方法有以下几种,每种方法都有其特定的使用途径。

5.1 原始数据类型

在Activity/Servier之间传递临时性的原始数据,可以使用Intent的putExtras方法来传递数据。

若传递的数据需要长久保存,则使用SharedPreference类来完成。

5.2 传递对象

当在Activity/Servier之间传递不需要长久保存的对象时,可以使用以下几种途径:

(1)通过Application类,每个Android应用程序都有一个Application类。当你在程序的AndroidManifest.xml中给Application设定一个名字时,你的程序中就必须有一个Application的子类。这个Application子类会被Android自动实例化,并且是一个全局性的类,它的生命周期和程序的生命周期相同,你可以把一些全局性的对象保存在Application类中。Application类可以通过getApplication()获得。

(2)通过HashMap of WeakReferences传递对象。当一个Activity需要向另外一个Activity传递对象时,可以使用一个关键字把对象存在一个HashMap中,并把这个关键字通过Internt的Extras发给目标Activity,目标Activity接到该关键字后使用该关键字把对象从HashMap中取出。

5.3 在Activity/Service之间传递需要长久保存的对象时,可以使用以下的方式

1.  Application Preferences

2.  Files

3.  contentProviders

4.  SQLite DB

6   AsyncTask

6.1 底层处理

底层使用本地线程池机制:

1.   核心线程数:线程池中保存的线程数,包括空闲线程,默认为5个

2.   线程池中允许的最大线程数,固定为128个+10个阻塞线程

3.   当线程数大于核心线程数时,如果线程池中中线程数大于核心线程数5超过一秒事,终止多余的线程,保留五个核心线程数。

4.   执行前用于保持任务的队列,此队列仅保持execute方法提交的Runnable任务,固定容量为10

5.   执行程序创建新线程时使用的工厂

6.2 AsyncTask介绍

Android的AsyncTask比Handler更轻量级一些(只是代码上轻量一些,而实际上要比handler更耗资源),适用于简单的异步处理。

首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。

Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。

AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。

AsyncTask定义了三种泛型类型 Params,Progress和Result。

Params 启动任务执行的输入参数,比如HTTP请求的URL。

Progress 后台任务执行的百分比。

Result 后台执行任务最终返回的结果,比如String。

使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。

onPostExecute(Result)  相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回

有必要的话你还得重写以下这三个方法,但不是必须的:

onProgressUpdate(Progress…)   可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。

onPreExecute()这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。

onCancelled() 用户调用取消时,要做的操作

使用AsyncTask类,以下是几条必须遵守的准则:

Task的实例必须在UI thread中创建;

execute方法必须在UI thread中调用;

不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;

该task只能被执行一次,否则多次调用时将会出现异常;

6.3 AsyncTask实现的原理和适用的优缺点

AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.

使用的优点:

简单,快捷

过程可控     

使用的缺点:

在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.

7   Handler

7.1 Handler异步实现的原理和适用的优缺点

1.   Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。

2.   Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。

3.   Message Queue(消息队列):用来存放线程放入的消息。

4.   线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。

在Handler 异步实现时,涉及到 Handler, Looper, Message,MessageQueue四个对象,实现异步的流程是主线程启动Thread(子线程)运行并生成Message放到MessageQueue,Looper从MessageQueue中获取Message并传递给Handler,Handler逐个获取Looper中的Message,并进行UI变更。

使用的优点:

结构清晰,功能定义明确

对于多个后台任务时,简单,清晰

使用的缺点:

在单个后台异步

处理时,显得代码过多,结构过于复杂(相对性)

7.2 Handler介绍

Handler主要接受子线程发送的数据, 并用此数据配合主线程更新UI.

当应用程序启动时,Android首先会开启一个主线程, 主线程为管理界面中的UI控件,进行事件分发,更新UI只能在主线程中更新,子线程中操作是危险的。这个时候,Handler就需要出来解决这个复杂的问题。由于Handler运行在主线程中(UI线程中),它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象(里面包含数据), 把这些消息放入主线程队列中,配合主线程进行更新UI。

7.3 Handler的特点

Handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中,

它有两个作用:

(1)安排消息或Runnable 在某个主线程中某个地方执行

(2)安排一个动作在不同的线程中执行

Handler中分发消息的一些方法

post(Runnable)

postAtTime(Runnable,long)

postDelayed(Runnable long)

sendEmptyMessage(int)

sendMessage(Message)

sendMessageAtTime(Message,long)

sendMessageDelayed(Message,long)

以上post类方法允许你排列一个Runnable对象到主线程队列中,

sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新.

7.4 综上所述

数据简单使用

AsyncTask:实现代码简单,数据量多且复杂使用

handler+thread :相比较AsyncTask来说能更好的利用系统资源且高效

8   ListView

8.1 ListView优化

8.1.1 简介

在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。

8.1.2 getview的加载方法一般有以下三种种方式:

1.   最慢的加载方式是每一次都重新定义一个View载入布局,再加载数据

2.   正确的加载方式是当convertView不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建,然后加载数据

3.   最快的方式是定义一个ViewHolder,将convetView的tag设置为ViewHolder,不为空时重新使用即可

8.1.3 补充

当处理一些耗时的资源加载的时候需要做到以下几点,以使你的加载更快更平滑:

1.   适配器在界面主线程中进行修改

2.   可以在任何地方获取数据但应该在另外一个地方请求数据

3.   在主界面的线程中提交适配器的变化并调用notifyDataSetChanged()方法

8.2 ListView的图文混合显示

8.2.1 异步加载图片基本思想:

1.先从内存缓存中获取图片显示(内存缓冲)

2.获取不到的话从SD卡里获取(SD卡缓冲)

3.都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)

8.2.2 具体优化

优化一:先从内存中加载,没有则开启线程从SD卡或网络中获取,这里注意从SD卡获取图片是放在子线程里执行的,否则快速滑屏的话会不够流畅。

优化二:在adapter里新建一个busy变量,表示listview是否处于滑动状态,如果是滑动状态则仅从内存中获取图片,没有的话无需再开启线程去外存或网络获取图片。

优化三:利用线程池在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存

资料与源码实现(见附件)

8.2.3 关于图片错位的处理

图片错位问题的本质源于我们的listview使用了缓存convertView,假设一种场景,一个listview一屏显示九个item,那么在拉出第十个item的时候,事实上该item是重复使用了第一个item,也就是说在第一个item从网络中下载图片并最终要显示的时候其实该item已经不在当前显示区域内了,此时显示的后果将是在可能在第十个item上输出图像,这就导致了图片错位的问题。所以解决之道在于可见则显示,不可见则不显示。在ImageLoader里有个imageViews的map对象,就是用于保存当前显示区域图像对应的url集,在显示前判断处理一下即可。

9   JNI

9.1 快速智能开发步骤

1.   配置NDK目录结构(只需一次)

2.   添加本地支持:右键选中工程,Android Tools—>Add native support

3.   将cpp代码改成c代码,注意Android.mk文件也要修改,刷新工程,删除obj目录

4.   声明本地方法,实现对应c代码实现:Javah+全类名 生成本地方法标头文件,把头文件剪切到jni目录下,c代码引用头文件,实现头文件里的方法。

5.   实现对应的c代码

6.   交叉编译,一锤子敲下去

7.   使用静态代码块,引用库函数,调用方法

9.2 开发中常见的JNI问题

9.2.1 错误一

10-31 06:42:33.645: E/AndroidRuntime(805): java.lang.UnsatisfiedLinkError: Native method not found: com.example.ndk2.MainActivity.hello_From_C:()Ljava/lang/String;

1.   引入的函数库名字不对,或者没有引入

2.   java方法和c方法不对应  

3.   部署的平台有问题

9.2.2 错误二

当前工程报错,但是没有任何文件有错误,有可能Android.mk有问题

在Android.mk文件中 不要用到全角空格或者回车

9.2.3 错误3

在C代码中有编译时异常 , 在控制台上会提示

9.2.4 错误4

10-31 06:53:23.165: A/libc(2075): Fatal signal 11 (SIGSEGV) at 0x476a415c (code=2), thread 20## (om.example.ndk2)

下面打印一大堆debug信息 

C代码中 有运行时异常

9.3 应用场景

1.   输出日志

2.   收集用户反馈信息

3.   用户登录

4.   加密算法

5.   电商数据加密

10 静默安装

10.1  方式一:定制ROM

Google的安全策略要求任何应用应该提示APK安装包的权限,对于一些内置特定厂商应用,可以跳过安装过程的信息加快安装,或者运营商强制安装。

10.2  方式二:查看系统源码

10.2.1 基本原理

在窗口中点击一个APK时,触发单击事件,PackageInstaller接收系统服务PackageManagerService传来的intent信息,传来的Intent信息中有APK的一些参数。实现的关键是区分一般APK和特定APK。

通过传给PackageManagerService的intent中添加特别的参数,PackageInstaller接收后进行判断,进行特别的隐藏安装流程。这个实现只能通过程序调用的方式安装。

安装过程的信息窗口在PackageInstallActivity.java中实现的。安装过程的信息窗口有4个:需要实现一个PakkageInstallActivityHide.JAVA的文件,去掉下面的dialog和窗口

安装权限确认窗口:installPermissionConfirm

安装进度条:installProgress

安装结果窗口:installResult

安装错误提示对话框

10.2.2 具体实现(方法一)

1.   在Androidmainfest.xml声明一个特定的intent:android.intent.action.VIEW.HIDE,由PackageInstallActivityHide.java来接受

注意:

2.   实现PakkageInstallActivityHide.java,UninstallerActivityHide.java。 只需把PakkageInstallActivity.java修改去掉dialog和对话框。

3.   安装程序调用者发一个上面定义的intent即可。如下例子,静默安装/sdcard/hello.apk

4.   卸载的方法类似。

5.   注意,这个方法需要PackageInstall这个apk必须与系统一起编译。这个apk在/system/app/目录下面;android.intent.action.VIEW.HIDE 这个静默安装的接口需要开放给第三方。

10.2.3 具体实现(方法二)

1.   从模拟器System\framework目录下提取framework.jar

2.   将framework.jar后缀名改为zip,解压后提取其中的classes.dex文件

3.   用dex2jar工具将classes.dex转成classes.dex.dex2jar.jar(注意新版本的dex2jar工具无法转换Android2.2的framework,建议使用dex2jar-0.0.7.8-SNAPSHOT,该工具可以从google官方站上下载到)

4.   将classes.dex.dex2jar.jar改名为classes.dex.dex2jar.zip解压取出android/content/pm/目录下的PackageManager.class,IPackageInstallObserver.class,IPackageDeleteObserver.class及相关的几个class文件备用

5.   找到android-sdk目录下的android.jar,改名为android.zip(注意改名前先备份一下),解压后将步骤4中取得的class文件覆盖到android对应的目录下,

6.   这个时候你的android.jar已经是一个更新过的SDK了,重新打开eclipse工程,你已经可以调用方法:

void android.content.pm.PackageManager.installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName)

11 Android中数据的存储方式

11.1  方式

1.   Shared Preferences:主要用于保存程序的系统配置信息。用来存储“key-values paires”。一般用于保存程序启动时设定的信息,以便在程序下一次启动时继续保留前一次设定的信息。

2.   xml

3.   Files:用文件的形式保存信息。可以通过对文件的读写来获取或保存相关信息。

4.   SQLite:用数据库的形式保存信息。SQLite是一个开源的数据库 系统。

5.   NetWork:将数据保存于网络。

11.2  区别

1.  Shared Preferences:

Android提供用来存储一些简单的配置信息的一种机制,例如,一些默认欢迎语、登录的用户名和密码等。其以键值对的方式存储,

SharedPreferences是以XML的格式以文件的方式自动保存的,在DDMS中的File Explorer中展开到/data/data/<packagename>/shared_prefs下,以上面这个为例,可以看到一个叫做SETTING_Infos.xml的文件

2.  Files

在Android中,其提供了openFileInput 和 openFileOuput 方法读取设备上的文件,下面看个例子代码,具体如下所示:

String FILE_NAME = "tempfile.tmp"; //确定要操作文件的文件名

FileOutputStream fos = openFileOutput(FILE_NAME, Context.MODE_PRIVATE); //初始化

FileInputStream fis = openFileInput(FILE_NAME); //创建写入流

上述代码中两个方法只支持读取该应用目录下的文件,读取非其自身目录下的文件将会抛出异常。需要提醒的是,如果调用

FileOutputStream 时指定的文件不存在,Android 会自动创建它。另外,在默认情况下,写入的时候会覆盖原文件内容,如果想把

新写入的内容附加到原文件内容后,则可以指定其模式为Context.MODE_APPEND

3.  SQLite

SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库

4.  NetWork:

将数据上传到网络

补充:

1.   Shared Preferences底层使用xml,xml也可以保存数据,但是Shared Preferences只能保存键值对方式,xml能保存复杂数据

2.   Content provider底部还是使用了Sqlite数据库,也是算一种方式。

12 什么是异步,什么是同步?分别在什么情况下使用?

Ø  比如:在子线程中执行数据加载,主线程中实现界面显示就是异步,这样界面可以先显示出来,在后台加载完数据再填充界面,而同步则是在一个线程中,如果数据没有加载完,那么界面就不会展示,两个任务必须按顺序执行. 

Ø  异步适合加载网络数据,或者大量数据

Ø  同步适合多线程访问或修改同一个数据,要保证数据修改前后的一致性的时候使用.

13 开发中遇到过哪些异常?是如何处理的?

1.   java.lang.NullPointerException  异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的对象,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)

2.   java.lang.OutOfMemoryError: bitmap size exceeds VM budget 自动把眼睛跳动到OOM异常看去

3.   java.lang.ClassNotFoundException  异常的解释是"指定的类不存在"。

4.   java.lang.ArrayIndexOutOfBoundsException

算法异常,一般是被0除,logcat中一般有提示;

5.   SQLException:操作数据库异常类,日志中一般会写出来执行什么语句有问题,直接从代码中查找即可;

6.   java.lang.IllegalArgumentException 这个异常的解释是"方法的参数错误",比如g.setColor(int red,int green,int blue)这个方法中的三个值,如果有超过255的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。

7.   java.lang.IllegalAccessException 这个异常的解释是"没有访问权限",当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常

8.   java.io.FileNotFoundException文件找不到异常,找到与自己写得类有关的行号,双击进入找到相关代码,文件读写异常,一般是使用完流没有关流会产生的异常,检查流是否关闭…..

14 开发时是如何做屏幕适配的?

14.1  美工切图:两套图

一般切480*800和1280*720的图做屏幕适配。

14.2  屏幕适配

14.2.1 图片适配

不同的手机分辨率会找不同的drawable对应的目录, 在做屏幕适配时, 会把一些对应分辨率的图片放在drawable-某个手机的分支下.

14.2.2 布局适配

不同手机屏幕的大小不一样, 我们想让程序运行时, 布局也展示的不一样.

在res目录下创建layout-1280x720文件夹, 里边创建的布局文件就会加载在1280x720的手机屏幕上.

14.2.3 尺寸适配

在res下创建values目录, 跟着对应手机的分辨率.

android下单位: px像素, dip/dp与像素无关的单位密度.

密度比值: 密度比值 = 开跟(宽的平方 + 高的平方) / 屏幕的尺寸.

= 开跟(57600 + 102400) / 屏幕的尺寸.

= 开跟(160000) / 屏幕的尺寸.

= 400 / 2.7

= 148.14814814814814814814814814815

= 0.75

= 开跟(1638400 + 518400) / 屏幕的尺寸.

= 开跟(2156800) / 屏幕的尺寸.

= 1468.6047800548655438793375614966 / 4.7

= 312.46910213933309444241224712693

= 2.

换算: px = dip * 密度比值;

在240*320手机上,

160dip * 0.75 = 120px;

在1280*720手机上,

160dip * 2 = 320px; 320dip * 2 = 640px

代码获取密度比值: density

Resources resources = getResources();

DisplayMetrics metrics = resources.getDisplayMetrics();

System.out.println("密度比值: " + metrics.density);

14.2.4 权重适配

14.2.5 代码适配

代码中的默认没有dp这个尺寸,而像素不会根据尺寸匹配屏幕,可以通过工具类转换dp到px.

14.2.6 其他

Ø  在自定义控件中可以根据图片原有比例算出适合屏幕的尺寸来显示.

Ø  提供高清图片,放在一个屏幕比较适中偏小的文件夹中,如mdpi.(这样可以解决图片过多,应用臃肿的现象)

15 开发时,是如何访问网络的?

15.1  一般在子线程中访问网络.

使用AsyncTask在doInBackground中访问网络.

15.2  URLConnection,HttpClient,AsyncHttpClient,Xutils使用get或post方法发送请求,接收响应.获得流对象取得数据.

15.3  使用HttpURLConnection(URLConnection子类)

1)调用URL对象的openConnection()方法来创建URLConnection对象

2)设置URLConnection的参数和普通请求属性

3)发送GET或POST请求

4)通过输入流读取远程资源

5)关闭释放资源

15.4  HttpClient: Apache提供了一个HttpClient,用于发送HTTP请求,接收HTTP响应。

使用HttpClient发送请求、接收响应,只需要以下几步:

1)创建HttpClient对象

2)如果需要发送get请求,创建HttpGet对象;如果发送post请求,创建HttpPost对象

3)发送参数,setParams(HttpParams params), 对于HttpPost,也可以调用setEntity(HttpEntity entity)来进行设置

4)httpClient.execute()发送请求,执行此方法返回HttpResponse对象

5)处理响应对象,调用HttpResponse的getEntity()获取HttpEntity对象,该对象封装了服务器响应内容

15.5  使用Socket通过Tcp或Udp访问网络.

1.基于TCP协议的Socket

服务器端首先声明一个ServerSocket对象并且指定端口号,然后调用Serversocket的accept()方法接收客户端的数据。accept()方法在没有数据进行接收的处于堵塞状态。(Socketsocket=serversocket.accept()),一旦接收到数据,通过inputstream读取接收的数据。

  客户端创建一个Socket对象,指定服务器端的ip地址和端口号(Socketsocket=newSocket("172.168.10.108",8080);),通过inputstream读取数据,获取服务器发出的数据(OutputStreamoutputstream=socket.getOutputStream()),最后将要发送的数据写入到outputstream即可进行TCP协议的socket数据传输。

2. 基于UDP协议的数据传输

服务器端首先创建一个DatagramSocket对象,并且指点监听的端口。接下来创建一个空的DatagramSocket对象用于接收数据(bytedata[]=newbyte[1024;]DatagramSocketpacket=newDatagramSocket(data,data.length)),使用DatagramSocket的receive方法接收客户端发送的数据,receive()与serversocket的accepet()类似,在没有数据进行接收的处于堵塞状态。

客户端也创建个DatagramSocket对象,并且指点监听的端口。接下来创建一个InetAddress对象,这个对象类似与一个网络的发送地址(InetAddress serveraddress=InetAddress.getByName("172.168.1.120")).定义要发送的一个字符串,创建一个DatagramPacket对象,并制定要讲这个数据报包发送到网络的那个地址以及端口号,最后使用DatagramSocket的对象的send()发送数据。*(Stringstr="hello";bytedata[]=str.getByte();DatagramPacketpacket=new DatagramPacket(data,data.length,serveraddress,4567);socket.send(packet);)

16 开发中大图片是如何处理的?图片是怎么进行缓存的?

16.1  介绍OOM:

加载大图片时会报下列异常:java.lang.OutOfMemoryError :堆内存空间:给类实例、数组分配内存空间的。 RGB ARGB 32 应用程序在启动时系统为它创建一个进程,系统为每个进程创建dalvik虚拟机(模拟器设置的VM Heap),当图片的大小大于虚拟机的堆内存空间时,就内存溢出(内存泄露OOM);

16.2  解决办法

缩放图片加载到内存

步骤:

1.   获得设备屏幕的分辨率;

2.   得到原图的分辨率;

3.   通过比较得到一个合适的比例值;

4.   按照比例值缩放图片

5.   把图片显示在imageview

16.3  图片三级缓存:

1.   当加载图片时,因为内存加载速度最快,所以先从内存中加载图片(Lrucache).

2.   如果内存中没有图片,再从本地cache文件夹中加载图片,这样会比较慢,但比网络加载快.如果本地加载成功,将图片放到LruCache中,通过内存缓存起来.

3.   如果本地加载不到图片,再通过网络加载图片,这样是最慢的方式,如果加载成功同样将图片缓存到LruCache中.

16.4  内存存储

Java中常用引用

Ø  强引用: 垃圾回收机制就程序崩溃都不会回收.

Ø  软引用: 保证软件能够运行的情况, 可以回收软引用的对象.Map<String, SoftReference> map;

Ø  弱引用: 内存达到一定程序, 就会回收掉.

Ø  虚引用: 只要垃圾回收一跑, 就会回收.

(1)因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。

(2)另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃,

  所以我们这里用得是LruCache来缓存图片,当存储Image的大小大于LruCache设定的值,系统自动释放内存.

16.5  本地存储:

通过getCacheDir()得到缓存目录,将图片读取到内存中.

实现:把文件描述符传递给本地代码,由本地代码去创建图片

优点:由于是本地代码创建的,那么byte数组的内存占用不会算到应用内存中,并且一旦内存不足,将会把bitmap的数组回收掉,而bitmap不会被回收,当显示的时候,发现bitmap的数组为空时,将会再次根据文件描述符去加载图片,此时可能由于加载耗时造成界面卡顿,但总比OOM要好得多。

注意:由于本地代码在创建图片时,没有对图片进行校验,所以如果文件不完整,或者根本就不是一个图片时,系统也不会报错,仍然会返回一个bitmap,但是这个bitmap是一个纯黑色的bitmap。

所以我们在下载图片的时候,一定要先以一个临时文件下载,等下载完毕了,再对图片进行重命名。

16.6  网络加载:

      直接访问url加载图片.

17 开发中如何避免OOM异常?如果出现来OOM异常应该如何处理?

1.   缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存,eg: Bitmap bitmap = BitmapFactory.decodeFile(path);mImageCache.put(path, new SoftReference<Bitmap>(bitmap));

2.   调整图像大小,onLoadingComplete()手机屏幕尺寸有限,分配给图像的显示区域本身就更小,有时图像大小可以做适当调整;

3.   采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;

4.   及时回收图像,如果引用了大量Bitmap对象,而应用又不需要同时显示所有图片,可以将暂时用不到的Bitmap对象及时回收掉;bitmap.recycle();mImageCache.remove(path);

5.   自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配;

18 开发中做过自定义控件么?或者问做过哪些自定义控件?如何实现的?

18.1  自定义控件的实现步骤

1.   写一个类继承View或你想扩展功能的控件(比如TextView)

2.   在/res/value下创建一个attr.xml文件

3.   重写父类的构造方法(我一般把三个都写上)在构造方法中获取到自定义是属性的值。

4.   重写onDraw()方法

5.   新建一个Activity(此处不再给出),在其使用的布局文件中添加自定义控件,并且可以引入自定义的命名空间,使用attr中定义的属性

(具体资料实现参加附件资料)

18.2  具体实现

1.   用过.

2.   自定义SlidingMenu,仿ViewPager,自定义栏目定制功能,自定义扇形进度条,自定义开关.仿优酷旋转菜单.

18.2.1 仿VIewPager技术要点

1.   新建MyViewpager继承ViewGroup

2.   注册OnTouch事件根据用户手指移动距离判断是否需要跳到下一页

3.   注册手势事件,实现用户手指滑动页面跟着用户手指滑动的效果

4.   处理事件分发机制,判断ViewPager的内部子控件是否需要处理滑动事件,按照预想的效果,将事件合理分发(如上下滑动事件教给子View处理,左右滑动事件交给ViewPager处理)

5.   在需要的Activity中new出来,按照需要添加子View,进行操作和显示

18.2.2 自定义优化菜单技术要点

1.   用相对布局先绘制好界面

2.   按照需要的效果为每层界面添加动画效果(补间动画使用时需要注意在控件消失后设置相应控件不可用或者不可点击,属性动画不需要设置)

3.   重写OnKeyDown事件,对用户按下返回键也实现相应的动画

4.   为菜单上每个按钮添加事件(setOnclick时最好让本类实现点击事件接口,因为菜单的点击事件可能有点多,这样代码更有条理性)

自定义TOgbutton

18.2.3 自定义ToggleButton技术要点

1.   新建MyToggleButton类集成View

2.   初始化各种图片资源

3.   为自身设置OnclickEvent

4.   为自身设置OntouchEvent

5.   设置Touch边界,防止用户将控件滑出边界

6.   在OntouchEvent里根据用户手指移动绝对值距离判断是点击事件or移动事件,定义变量区别,让两个Event分别响应,然后刷新界面

19 都用过哪些设计模式?都是在什么情况下使用?写个单利设计模式、观察者模式。

常用的:适配器模式,装饰模式,观察者模式,工厂模式,单例模式,

19.1  适配器模式

ListView,ViewPager,等的Adapter就是典型的适配器

19.2  单例模式:

1.初始化一个该类的私有成员变量.

2.私有构造方法.

3.提供一个方法返回该类的成员变量.

19.3  观察者模式:

1.写一个回调接口.

2.设置一个该接口的成员变量

3.提供一个set..Listener(该接口)方法,给另一个类来实现此接口.将实现的对象赋值给成员变量

4.在数据改变时调用接口的回调方法.

19.4  工厂模式

1.   Fragment工厂

2.   线程工厂

20 静态代码块、构造代码块、构造函数执行顺序?(子类父类都有,使用多态创建对象)静态方法的覆盖问题?

1.静态代码块->构造代码块->构造函数.

2.静态方法不会被覆盖,如果父类的引用指向了子类的对象,如:Parent sub = new Sub();  这时调用sub的静态方法会执行父类的,如果子类引用只想子类对象,如:Sub sub = new Sub(); 这是调用sub的静态方法会执行子类的.

21 开启线程有哪些方法?是么情况下使用Thread?什么情况下使用Runnable?

1.   写一个类继承Thread,复写run方法,使用start开启线程.

2.   实现runnable接口,将实现的对象传递给Thread的构造方法,再使用start开启线程.

在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:

(1)避免点继承的局限,一个类可以实现多个接口。

(2)适合于资源的共享

22 TCP与UDP?Socket?

22.1  TCP

TCP(Transmission Control Protocol传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。

一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。

我们来看看这三次对话的简单过程:

1.   主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;

2.   主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;

3.   主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。

三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

22.2  UDP

UDP(User Data Protocol用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去

UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境

比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包(如图2所示)。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

22.3  TCP协议和UDP协议的差别

23 关于 Fragment

Android 3.0出现,基于pad的

有两种方式:activity可以包含多个fragment 

另一种是动态加载fragment

24 关于app竖屏幕显示

app竖着的话必须设置属性:android:screenOrientation="portrait"

25 Scrollview里面能不能嵌套listview,有什么问题出现?

能,listview显示不全,只显示一行,listview无法滑动,焦点呗scrollcview抢占了。

1.计算每个item的高度,调用measure计算,然后用,view,setparams.height。每个item的布局必须是linearlyout,因为其他的布局里面没有

重写onmesure的方法。

3.   重写listview的ontounchevent方法。当按下的时候拦截scrollview的滚动。

26 handler不加static为什么会有内存泄露.

同一个线程下的handler共享一个looper对象,消息中保留了对handler的引用,只要有消息在队列中,那么handler便无法被回收,如果handler不是static,那么使用Handler的Service和Activity就也无法被回收,即便它们的ondestroy方法被调用。这就可能导致内存泄露。当然这通常不会发生,除非你发送了一个延时很长的消息。

但把hanlder添加为static后,会发现在handler中调用外部类的方法和成员变量需要它们都定义为final,这显然是不大可能的。这里建议在你的Service或Activity中的增加一个内部static Handler类,

这个内部类持有Service或Activity的弱引用,这样就可以解决final的问题。

27 handler中遇到不加static会出现警告,而我们还要引用成员变量,该怎样解决

外部类的方法和成员变量需要它们都定义为final,

28 application的framework,library分别有什么

framework是程序的结构.类库是构建程序的部件.

29 为什么不能在broadcastreceiver中开启子线程

BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了。

BroadcastReceiver一旦结束,此时BroadcastReceiver的所在进程很容易在系统需要内存时被优先杀死,

因为它属于空进程(没有任何活动组件的进程)。如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死。

所以采用子线程来解决是不可靠的。

30 jvm与dalvik的区别

1.   架构不同:JVM使用栈架构;Dalvik使用的是寄存器,数据是加载到CUP的寄存器上的。

2.   JVM加载的.class文件,Dalvik加载的是.dex文件,对内存的分配情况做了优化。

31 jvm是怎样工作的

32 当前的activity是singletop的,而且在栈的最顶端,如果我们从service中执行startAcitvity来开启这个activity,会执行哪些方法

onResume()-onRestart()

如果我要让手机每三天做一件事,你会怎么设计,有哪些需要注意的

画一下你的app的模型图

如果调用Service的onStart方法,再调用onBind方法,最后调用onStop方法,会怎样

11. Serializable和Parceable有什么区别,分别在什么情况下使用

12. 为什么HttpConnection会涉及到基站问题而HttpClient不会

13. ImageLoader内部是怎样实现的

14. 怎样在子线程中实现handler,并用handler接收消息

15. intent传递数据时为什么要继承parceable或serializeble接口,而且没有实现任何方法

16. 有一个很难复现的bug,而且很急,你会用什么方法找到这个bug

17. 有一个图片墙,我会向下滑动--停,向下滑动--停……,要怎样避免OOM

33 跨进程通信

33.1  原理

Android中的跨进程通信采用的是Binder机制,其底层原理是共享内存。

33.2  为什么不能直接跨进程通信?

为了安全考虑,应用之间的内存是无法互相访问的,各自的数据都存在于自身的内存区域内。

33.3  如何跨进程通信?

要想跨进程通信,就要找到一个大家都能访问的地方,例如硬盘上的文件,多个进程都可以读写该文件,通过对该文件进行读写约定好的数据,来达到通信的目的。

l  Binder在linux层面属于一个驱动,但是这个驱动不是去驱动一个硬件,而且驱动一小段内存。

l  不同的应用通过对一块内存区域进行数据的读、写操作来达到通信的目的。

l  不同的应用在同一内存区域读写数据,为了告知其他应用如何理解写入的数据,就需要一个说明文件,这就是AIDL。当两个应用持有相同的AIDL文件,就能互相理解对方的的意图,就能做出相应的回应,达到通信的目的。

 

相关推荐