第二个

来源:互联网 发布:游光网络账号注册 编辑:程序博客网 时间:2024/04/29 05:12

1、启动应用后,改变系统语言,应用的语言会改变么?

这个一般是不会的,一般需要重启应用才能改变应用语言。但是对应应用来说如果做了国际化处理则支持如果没有处理那系统语言再更改也是无用的。

2、请介绍下adb、ddms、aapt的作用

adb是Android Debug Bridge ,Android调试桥的意思,ddms是Dalvik Debug Monitor Service,dalvik调试监视服务。aapt即Android Asset Packaging Tool,在SDK的build-tools目录下。该工具可以查看,创建, 更新ZIP格式的文档附件(zip, jar, apk)。也可将资源文件编译成二进制文件,尽管我们没有直接使用过该工具,但是开发工具会使用这个工具打包apk文件构成一个Android 应用程序。

Android 的主要调试工具是adb(Android debuging bridge),ddms是一个在adb基础上的一个图形化工具。

adb,它是一个命令行工具。而ddms功能与adb相同,只是它有一个图形化界面。对不喜欢命今操作方式的人来说是一个不错的选择。

3、ddms 和traceview的区别

ddms原意是:davik debug monitor service。简单的说ddms是一个程序执行查看器,在里面可以看见线程和堆栈等信息,traceView是程序性能分析器。traceview是ddms中的一部分内容。

4、补充知识:TraceView的使用

一、TraceView简介

     Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot(瓶颈)。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。二者的用法如下:

开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。

借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如下图所示。

 

点击上图中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。

下面,我们通过一个示例程序介绍Traceview的使用。

实例程序如下图所示:界面有4个按钮,对应四个方法。

 

点击不同的方法会进行不同的耗时操作。

我们分别点击按钮一次,要求找出最耗时的方法。点击前通过DDMS 启动 Start Method Profiling按钮。

 

然后依次点击4个按钮,都执行后再次点击上图中红框中按钮,停止收集数据。

接下来我们开始对数据进行分析。

当我们停止收集数据的时候会出现如下分析图表。该图表分为2大部分,上面分不同的行,每一行代表一个线程的执行耗时情况。main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。图表的下半部分是具体的每个方法执行的时间情况。显示方法执行情况的前提是先选中某个线程。

 

 

我们主要是分析main线程。

上面方法指标参数所代表的意思如下:

列名

描述

Name

该线程运行过程中所调用的函数名

Incl Cpu Time

某函数占用的CPU时间,包含内部调用其它函数的CPU时间

Excl Cpu Time

某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间

Incl Real Time

某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间

Excl Real Time

某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间

Call+Recur Calls/Total

某函数被调用次数以及递归调用占总调用次数的百分比

Cpu Time/Call

某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间

Real Time/Call

同CPU Time/Call类似,只不过统计单位换成了真实时间

我们为了找到最耗时的操作,那么可以通过点击Incl Cpu Time,让其按照时间的倒序排列。我点击后效果如下图:

 

通过分析发现:method1最耗时,耗时2338毫秒。

 

那么有了上面的信息我们可以进入我们的method1方法查看分析我们的代码了。

5、Android中数据存储方式有哪些?

a) 文件存储

b) xml,SharedPreference

c) SQLiteDatabase

d) ContentProvider

e) 网络

15、DVM和JVM的区别?

a) dvm执行的是.dex文件,而jvm执行的是.class。Android工程编译后的所有.class字节码会被dex工具抽取到一个.dex文件中。

b) dvm是基于寄存器的虚拟机  而jvm执行是基于虚拟栈的虚拟机。寄存器存取速度比栈快的多,dvm可以根据硬件实现最大的优化,比较适合移动设备。

c) .class文件存在很多的冗余信息,dex工具会去除冗余信息,并把所有的.class文件整合到.dex文件中。减少了I/O操作,提高了类的查找速度。

16、谈一谈Android的安全机制

1、Android是基于Linux内核的,因此Linux对文件权限的控制同样适用于Android

Android中每个应用都有自己的/data/data/包名 文件夹,该文件夹只能该应用访问,而其他应用则无权访问。

2、Android的权限机制保护了用户的合法权益

如果我们的代码想拨打电话、发送短信、访问通信录、定位、访问sdcard等所有可能侵犯用于权益的行为都是必须要在AndroidManifest.xml中进行声明的,这样就给了用户一个知情权。

3、Android的代码混淆保护了开发者的劳动成果

17、Android的四大组件都需要在清单文件中注册吗?

Activity、Service、ContentProvider如果要使用则必须在AndroidManifest.xml中进行注册,而BroadcastReceiver则有两种注册方式,静态注册和动态注册。其中静态注册就是指在AndroidManifest.xml中进行注册,而动态注册时通过代码注册。

18、Android中进程的级别有哪些?

a) Foreground process

b) Visible process

c) Service process

d) Background process

e) Empty process

一、Activity

1、什么是Activity? 

四大组件之一,通常一个用户交互界面对应一个activity。activity 是Context的子类,同时实现了window.callback和keyevent.callback, 可以处理与窗体用户交互的事件。

常见的Activity类型有FragmentActivitiy,ListActivity,TabAcitivty等。

如果界面有共同的特点或者功能的时候,还会自己定义一个BaseActivity。

2、请描述一下Activity 生命周期

Activity从创建到销毁有多种状态,从一种状态到另一种状态时会激发相应的回调方法,这些回调方法包括:onCreate onStart onResume onPause onStop onDestroy

其实这些方法都是两两对应的,onCreate创建与onDestroy销毁;

onStart可见与onStop不可见;onResume可编辑(即焦点)与onPause;

6个方法是相对应的,那么就只剩下一个onRestart方法了,这个方法在什么时候调用呢?

答案就是:在Activity被onStop后,但是没有被onDestroy,在再次启动此Activity时就调用onRestart(而不再调用onCreate)方法;

如果被onDestroy了,则是调用onCreate方法。

3、Activity的状态都有哪些?

a) foreground activity

b) visible activity

c) background activity

d) empty process

4、如何保存Activity的状态?

Activity的状态通常情况下系统会自动保存的,只有当我们需要保存额外的数据时才需要使用到这样的功能。

一般来说, 调用onPause()和onStop()方法后的activity实例仍然存在于内存中, activity的所有信息和状态数据不会消失, 当activity重新回到前台之后, 所有的改变都会得到保留。

但是当系统内存不足时, 调用onPause()和onStop()方法后的activity可能会被系统摧毁, 此时内存中就不会存有该activity的实例对象了。如果之后这个activity重新回到前台, 之前所作的改变就会消失。为了避免此种情况的发生, 我们可以覆写onSaveInstanceState()方法。onSaveInstanceState()方法接受一个Bundle类型的参数, 开发者可以将状态数据存储到这个Bundle对象中, 这样即使activity被系统摧毁, 当用户重新启动这个activity而调用它的onCreate()方法时, 上述的Bundle对象会作为实参传递给onCreate()方法, 开发者可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态。

需要注意的是, onSaveInstanceState()方法并不是一定会被调用的, 因为有些场景是不需要保存状态数据的. 比如用户按下BACK键退出activity时, 用户显然想要关闭这个activity, 此时是没有必要保存数据以供下次恢复的, 也就是onSaveInstanceState()方法不会被调用. 如果调用onSaveInstanceState()方法, 调用将发生在onPause()或onStop()方法之前。

5、两个Activity之间跳转时必然会执行的是哪几个方法?(重要)

一般情况下比如说有两个activity,分别叫A,B,当在A里面激活B组件的时候, A会调用 onPause()方法,然后B调用onCreate() ,onStart(), onResume()。

 这个时候B覆盖了窗体, A会调用onStop()方法.  如果B是个透明的,或者是对话框的样式, 就不会调用A的onStop()方法。

如下图,打开一个MainActivity,然后点击MainActivity中的按钮跳转到SecondActivity的日志。

 

6、横竖屏切换时Activity的生命周期

此时的生命周期跟清单文件里的配置有关系。

1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期

默认首先销毁当前activity,然后重新加载。

如下图,当横竖屏切换时先执行onPause/onStop方法

 

2、设置Activity的android:configChanges="orientation|keyboardHidden|screenSize"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。

通常在游戏开发, 屏幕的朝向都是写死的。

7、如何将一个Activity设置成窗口的样式?

只需要给我们的Activity配置如下属性即可。

 android:theme="@android:style/Theme.Dialog"

8、如何退出Activity?如何安全退出已调用多个Activity的Application?

1、通常情况用户退出一个Activity只需按返回键,我们写代码想退出activity直接调用finish()方法就行。

2、记录打开的Activity:

每打开一个Activity,就记录下来。在需要退出时,关闭每一个Activity即可。

3、发送特定广播:

在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。

//给某个activity 注册接受接受广播的意图

registerReceiver(receiver, filter)

//如果过接受到的是 关闭activity的广播  就调用finish()方法 把当前的activity finish()掉

4、递归退出

在打开新的Activity时使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭。

5、其实 也可以通过 intent的flag 来实现intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)激活一个新的activity。此时如果该任务栈中已经有该Activity,那么系统会把这个Activity上面的所有Activity干掉。其实相当于给Activity配置的启动模式为SingleTop。

9、请描述一下Activity的启动模式都有哪些以及各自的特点

启动模式(launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task

Activity一共有以下四种launchMode

 1.standard

 2.singleTop

 3.singleTask

 4.singleInstance

我们可以在AndroidManifest.xml配置<activity>的android:launchMode属性为以上四种之一即可。

下面我们结合实例一一介绍这四种lanchMode:

8.1 standard

standard模式是默认的启动模式,不用为<activity>配置android:launchMode属性即可,当然也可以指定值为standard。

我们将创建一个Activity,命名为FirstActivity,来演示一下标准的启动模式。FirstActivity代码如下:

FirstActivity界面中的TextView用于显示当前Activity实例的序列号,Button用于跳转到下一个FirstActivity界面。

然后我们连续点击几次按钮,将会出现下面的现象:

我们注意到都是FirstActivity的实例,但序列号不同,并且我们需要连续按后退键两次,才能回到第一个FirstActivity。standard模式的原理如下图所示:

如图所示,每次跳转系统都会在task中生成一个新的FirstActivity实例,并且放于栈结构的顶部,当我们按下后退键时,才能看到原来的FirstActivity实例。

这就是standard启动模式,不管有没有已存在的实例,都生成新的实例。

8.2 singleTop

我们在上面的基础上为<activity>指定属性android:launchMode="singleTop",系统就会按照singleTop启动模式处理跳转行为。我们重复上面几个动作,将会出现下面的现象:

我们看到这个结果跟standard有所不同,三个序列号是相同的,也就是说使用的都是同一个FirstActivity实例;如果按一下后退键,程序立即退出,说明当前栈结构中只有一个Activity实例。singleTop模式的原理如下图所示:

正如上图所示,跳转时系统会先在栈结构中寻找是否有一个FirstActivity实例正位于栈顶,如果有则不再生成新的,而是直接使用。也许朋友们会有疑问,我只看到栈内只有一个Activity,如果是多个Activity怎么办,如果不是在栈顶会如何?我们接下来再通过一个示例来证实一下大家的疑问。

我们再新建一个Activity命名为SecondActivity,如下:

然后将之前的FirstActivity跳转代码改为:

这时候,FirstActivity会跳转到SecondActivity,SecondActivity又会跳转到FirstActivity。演示结果如下:

我们看到,两个FirstActivity的序列号是不同的,证明从SecondActivity跳转到FirstActivity时生成了新的FirstActivity实例。原理图如下:

我们看到,当从SecondActivity跳转到FirstActivity时,系统发现存在有FirstActivity实例,但不是位于栈顶,于是重新生成一个实例。

这就是singleTop启动模式,如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例。

8.3 singleTask

在上面的基础上我们修改FirstActivity的属性android:launchMode="singleTask"。演示的结果如下:

我们注意到,在上面的过程中,FirstActivity的序列号是不变的,SecondActivity的序列号却不是唯一的,说明从SecondActivity跳转到FirstActivity时,没有生成新的实例,但是从FirstActivity跳转到SecondActivity时生成了新的实例。singleTask模式的原理图如下图所示:

在图中的下半部分是SecondActivity跳转到FirstActivity后的栈结构变化的结果,我们注意到,SecondActivity消失了,没错,在这个跳转过程中系统发现有存在的FirstActivity实例,于是不再生成新的实例,而是将FirstActivity之上的Activity实例统统出栈,将FirstActivity变为栈顶对象,显示到幕前。也许朋友们有疑问,如果将SecondActivity也设置为singleTask模式,那么SecondActivity实例是不是可以唯一呢?在我们这个示例中是不可能的,因为每次从SecondActivity跳转到FirstActivity时,SecondActivity实例都被迫出栈,下次等FirstActivity跳转到SecondActivity时,找不到存在的SecondActivity实例,于是必须生成新的实例。但是如果我们有ThirdActivity,让SecondActivity和ThirdActivity互相跳转,那么SecondActivity实例就可以保证唯一。

这就是singleTask模式,如果发现有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前。

8.4 singleInstance

这种启动模式比较特殊,因为它会启用一个新的栈结构,将Activity放置于这个新的栈结构中,并保证不再有其他Activity实例进入。

我们修改FirstActivity的launchMode="standard",SecondActivity的launchMode="singleInstance",由于涉及到了多个栈结构,我们需要在每个Activity中显示当前栈结构的id,所以我们为每个Activity添加如下代码:

然后我们再演示一下这个流程:

我们发现这两个Activity实例分别被放置在不同的栈结构中,关于singleInstance的原理图如下

我们看到从FirstActivity跳转到SecondActivity时,重新启用了一个新的栈结构,来放置SecondActivity实例,然后按下后退键,再次回到原始栈结构;图中下半部分显示的在SecondActivity中再次跳转到FirstActivity,这个时候系统会在原始栈结构中生成一个FirstActivity实例,然后回退两次,注意,并没有退出,而是回到了SecondActivity,为什么呢?是因为从SecondActivity跳转到FirstActivity的时候,我们的起点变成了SecondActivity实例所在的栈结构,这样一来,我们需要“回归”到这个栈结构。

如果我们修改FirstActivity的launchMode值为singleTop、singleTask、singleInstance中的任意一个,流程将会如图所示:

singleInstance启动模式可能是最复杂的一种模式,为了帮助大家理解,我举一个例子,假如我们有一个share应用,其中的ShareActivity是入口Activity,也是可供其他应用调用的Activity,我们把这个Activity的启动模式设置为singleInstance,然后在其他应用中调用。我们编辑ShareActivity的配置:

然后我们在其他应用中这样启动该Activity:

当我们打开ShareActivity后再按后退键回到原来界面时,ShareActivity做为一个独立的个体存在,如果这时我们打开share应用,无需创建新的ShareActivity实例即可看到结果,因为系统会自动查找,存在则直接利用。大家可以在ShareActivity中打印一下taskId,看看效果。关于这个过程,原理图如下:

10、一个启动模式为singleTop的activity,如果再试图启动会怎样?  面试官想问的是onNewIntent()(2015-11-24)

Activity有一个onNewIntent(Intent intent)回调方法,该方法我们几乎很少使用,导致已经将其忽略掉。该方法的官方解释如下:

This is called for activities that set launchMode to "singleTop" in their package, or if a client used the Intent.FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity. In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.

An activity will always be paused before receiving a new intent, so you can count on onResume being called after this method.

Note that getIntent still returns the original Intent. You can use setIntent to update it to this new Intent.

上文大概意思如下:

该方法被启动模式设置为“singleTop”的Activity回调,或者当通过设置Intent.FLAG_ACTIVITY_SINGLE_TOP 的Intent启动Activity时被回调。在任何情况下,只要当栈顶的Activity被重新启动时没有重新创建一个新的Activity实例而是依然使用该Activity对象,那么onNewIntent(Intent)方法就会被回调。

当一个Activity接收到新Intent的时候会处于暂停状态,因此你可以统计到onResume()方法会被再次执行,当然这个执行是在onNewIntent之后的。

注意:如果我们在Activity中调用了getIntent()方法,那么返回的Intent对象还是老的Intent(也就是第一次启动该Activity时的传入的Intent对象),但是如果想让getIntent()返回最新的Intent,那么我们可以通过setIntent(Intent)方法设置。

二、Service

1、Service是否在main thread中执行, service里面是否能执行耗时的操作?

默认情况,如果没有显示的指service所运行的进程, Service和activity是运行在当前app所在进程的main thread(UI主线程)里面。

service里面不能执行耗时的操作(网络请求,拷贝数据库,大文件 )

特殊情况 ,可以在清单文件配置 service 执行所在的进程 ,让service在另外的进程中执行

2、Activity怎么和Service绑定,怎么在Activity中启动自己对应的Service?

Activity通过bindService(Intent service, ServiceConnection conn, int flags)跟Service进行绑定,当绑定成功的时候Service会将代理对象通过回调的形式传给conn,这样我们就拿到了Service提供的服务代理对象。

Activity中可以通过startService和bindService方法启动Service。一般情况下如果想获取Service的服务对象那么肯定需要通过bindService()方法,比如音乐播放器,第三方支付等。如果仅仅只是为了开启一个后台任务那么可以使用startService()方法。

3、请描述一下Service的生命周期

Service有绑定模式和非绑定模式,以及这两种模式的混合使用方式。不同的使用方法生命周期方法也不同。

非绑定模式:当第一次调用startService的时候执行的方法依次为onCreate()、onStartCommand(),onStart())当Service关闭的时候调用onDestory方法。

绑定模式:第一次bindService()的时候,执行的方法为onCreate()、onBind()解除绑定的时候会执行onUnbind()、onDestory()。

上面的两种生命周期是在相对单纯的模式下的情形。我们在开发的过程中还必须注意Service实例只会有一个,也就是说如果当前要启动的Service已经存在了那么就不会再次创建该Service当然也不会调用onCreate()方法。

一个Service可以被多个客户进行绑定,只有所有的绑定对象都执行了onBind()方法后该Service才会销毁,不过如果有一个客户执行了onStart()方法,那么这个时候如果所有的bind客户都执行了unBind()该Service也不会销毁。

Service的生命周期图如下所示,帮助大家记忆。

 

4、什么是IntentService?有何优点?

我们通常只会使用Service,可能IntentService对大部分同学来说都是第一次听说。那么看了下面的介绍相信你就不再陌生了。如果你还是不了解那么在面试的时候你就坦诚说没用过或者不了解等。并不是所有的问题都需要回答上来的。

一、IntentService简介

IntentService是Service的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题:

Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中;

Service也不是专门一条新线程,因此不应该在Service中直接处理耗时的任务;

二、IntentService特征

会创建独立的worker线程来处理所有的Intent请求;

会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;

所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;

Service的onBind()提供默认实现,返回null;

Service的onStartCommand提供默认实现,将请求Intent添加到队列中;

三、使用IntentService

本人写了一个IntentService的使用例子供参考。该例子中一个MainActivity一个MyIntentService,这两个类都是四大组件当然需要在清单文件中注册。这里只给出核心代码:

MainActivity.java:

 

MyIntentService.java

运行后效果如下:

 

5、说说Activity、Intent、Service是什么关系

他们都是Android开发中使用频率最高的类。其中Activity和Service都是Android四大组件之一。他俩都是Context类的子类ContextWrapper的子类,因此他俩可以算是兄弟关系吧。不过兄弟俩各有各自的本领,Activity负责用户界面的显示和交互,Service负责后台任务的处理。Activity和Service之间可以通过Intent传递数据,因此可以把Intent看作是通信使者。

6、Service和Activity在同一个线程吗

对于同一app来说默认情况下是在同一个线程中的,main Thread (UI Thread)。

7、Service里面可以弹吐司么?

可以的。弹吐司有个条件就是得有一个Context上下文,而Service本身就是Context的子类,因此在Service里面弹吐司是完全可以的。比如我们在Service中完成下载任务后可以弹一个吐司通知用户。

8、如何让一个Service成为前置进程?

在启动该Service的时候可以在添加上如下方法:

9、Service的onStartCommand方法有几种返回值?各代表什么意思?

有四种返回值,不同值代表的意思如下:

START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。

START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。

START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

10、Service的onRebind(Intent)方法在什么情况下会执行?

如果在onUnbind()方法返回true的情况下会执行,否则不执行。

官方解释如下:

Called when new clients have connected to the service, after it had previously been notified that all had disconnected in its onUnbind. This will only be called if the implementation of onUnbind was overridden to return true.

11、Activity调用Service中的方法都有哪些方式?

Activity调用Service中的方法主要是通过绑定服务的模式实现的,绑定服务又分为三种方式。如下所示:

一、Extending the Binder class

通过Binder接口的形式实现,当Activity绑定Service成功的时候Activity会在ServiceConnection的类的onServiceConnected()回调方法中获取到Service的onBind()方法return 过来的Binder的子类。

二、Using a Messenger

这是官方给出的另外一种沟通方式。原文如下:

If you need your interface to work across different processes, you can create an interface for the service with a Messenger. In this manner, the service defines a Handler that responds to different types of Message objects. This Handler is the basis for a Messenger that can then share an IBinder with the client, allowing the client to send commands to the service using Message objects. Additionally, the client can define a Messenger of its own so the service can send messages back.

 

This is the simplest way to perform interprocess communication (IPC), because the Messenger queues all requests into a single thread so that you don't have to design your service to be thread-safe.

 

Here's a summary of how to use a Messenger:

 

    The service implements a Handler that receives a callback for each call from a client.

    The Handler is used to create a Messenger object (which is a reference to the Handler).

    The Messenger creates an IBinder that the service returns to clients from onBind().

    Clients use the IBinder to instantiate the Messenger (that references the service's Handler), which the client uses to send Message objects to the service.

    The service receives each Message in its Handlerspecifically, in the handleMessage() method.

 

In this way, there are no "methods" for the client to call on the service. Instead, the client delivers "messages" (Message objects) that the service receives in its Handler.

Here's a simple example service that uses a Messenger interface:

1. public class MessengerService extends Service {

2.     /** Command to the service to display a message */

3.     static final int MSG_SAY_HELLO = 1;

4. 

5.     /**

6.      * Handler of incoming messages from clients.

7.      */

8.     class IncomingHandler extends Handler {

9.         @Override

10.         public void handleMessage(Message msg) {

11.             switch (msg.what) {

12.                 case MSG_SAY_HELLO:

13.                     Toast.makeText(getApplicationContext(),

14. "hello!", Toast.LENGTH_SHORT).show();

15.                     break;

16.                 default:

17.                     super.handleMessage(msg);

18.             }

19.         }

20.     }

21. 

22.     /**

23.      * Target we publish for clients to send messages to IncomingHandler.

24.      */

25.     final Messenger mMessenger = new Messenger(new IncomingHandler());

26. 

27.     /**

28.      * When binding to the service, we return an interface to our messenger

29.      * for sending messages to the service.

30.      */

31.     @Override

32.     public IBinder onBind(Intent intent) {

33.         Toast.makeText(getApplicationContext(),

34. "binding", Toast.LENGTH_SHORT).show();

35.         return mMessenger.getBinder();

36.     }

37. }

Notice that the handleMessage() method in the Handler is where the service receives the incoming Message and decides what to do, based on the what member.

 

All that a client needs to do is create a Messenger based on the IBinder returned by the service and send a message using send(). For example, here's a simple activity that binds to the service and delivers the MSG_SAY_HELLO message to the service:

1. public class ActivityMessenger extends Activity {

2.     /** Messenger for communicating with the service. */

3.     Messenger mService = null;

4. 

5.     /** Flag indicating whether we have called bind on the service. */

6.     boolean mBound;

7. 

8.     /**

9.      * Class for interacting with the main interface of the service.

10.      */

11.     private ServiceConnection mConnection = new ServiceConnection() {

12.         public void onServiceConnected(ComponentName className, IBinder service) {

13.             // This is called when the connection with the service has been

14.             // established, giving us the object we can use to

15.             // interact with the service.  We are communicating with the

16.             // service using a Messenger, so here we get a client-side

17.             // representation of that from the raw IBinder object.

18.             mService = new Messenger(service);

19.             mBound = true;

20.         }

21. 

22.         public void onServiceDisconnected(ComponentName className) {

23.             // This is called when the connection with the service has been

24.             // unexpectedly disconnected -- that is, its process crashed.

25.             mService = null;

26.             mBound = false;

27.         }

28.     };

29. 

30.     public void sayHello(View v) {

31.         if (!mBound) return;

32.         // Create and send a message to the service, using a supported 'what' value

33.         Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);

34.         try {

35.             mService.send(msg);

36.         } catch (RemoteException e) {

37.             e.printStackTrace();

38.         }

39.     }

40. 

41.     @Override

42.     protected void onCreate(Bundle savedInstanceState) {

43.         super.onCreate(savedInstanceState);

44.         setContentView(R.layout.main);

45.     }

46. 

47.     @Override

48.     protected void onStart() {

49.         super.onStart();

50.         // Bind to the service

51.         bindService(new Intent(this, MessengerService.class), mConnection,

52.             Context.BIND_AUTO_CREATE);

53.     }

54. 

55.     @Override

56.     protected void onStop() {

57.         super.onStop();

58.         // Unbind from the service

59.         if (mBound) {

60.             unbindService(mConnection);

61.             mBound = false;

62.         }

63.     }

64. }

Notice that this example does not show how the service can respond to the client. If you want the service to respond, then you need to also create a Messenger in the client. Then when the client receives the onServiceConnected() callback, it sends a Message to the service that includes the client's Messenger in the replyTo parameter of the send() method.

三、Using AIDL

aidl比较适合当客户端和服务端不在同一个应用下的场景。

 

12、Activity如何给Service发送Message?(2015.10.18)

该题和下一题,都是关于Activity和Service直接护发消息的知识,非常的有意思,但是也非常的不好回答。Activity给Service还算稍微简单一点,但是Service给Activity发送消息就有点难度了,不是常人能所了解的。如果你没有看过Android官方关于Bound Service的解释,估计很难应付。

Activity如何给Service发送Message见《Activity调用Service中的方法都有哪些方式?》题目中Activity跟Service绑定的第二种方式:Using a Messenger。

13、Service如何给Activity发送Message?(2015.10.18)

Service和Activity如果想互发Message就必须使用使用Messenger机制。

Service如何给Activity发送Message见《Activity调用Service中的方法都有哪些方式?》题目中Activity跟Service绑定的第二种方式:Using a Messenger。

考虑到官方只给出了Activity给Service发送Message的代码,在这里我给出一个Activity跟Service之间互相发送Message通信的示例代码:

1. MainActivity代码

1. package com.example.serviceAndActivity;

2. 

3. import android.os.Bundle;

4. import android.os.Handler;

5. import android.os.IBinder;

6. import android.os.Message;

7. import android.os.Messenger;

8. import android.os.RemoteException;

9. import android.app.Activity;

10. import android.app.Service;

11. import android.content.ComponentName;

12. import android.content.Intent;

13. import android.content.ServiceConnection;

14. import android.util.Log;

15. import android.view.View;

16. import android.widget.Toast;

17. /**

18.  * ActivityService互发Message

19.  *

20.  * @author wzy 2015-11-25

21.  *

22.  */

23. public class MainActivity extends Activity {

24. private Messenger messenger;

25. //将该Handler发送Service

26. private Messenger mOutMessenger = new Messenger(new OutgoingHandler());

27. 

28. @Override

29. protected void onCreate(Bundle savedInstanceState) {

30. super.onCreate(savedInstanceState);

31. setContentView(R.layout.activity_main);

32. }

33. //绑定服务

34. public void click1(View view) {

35. Intent intent = new Intent(this, MessengerService.class);

36. ServiceConnection conn = new MyServiceConnection();

37. bindService(intent, conn, Service.BIND_AUTO_CREATE);

38. }

39. 

40. //发送消息

41. public void click2(View view) throws RemoteException {

42. if (messenger == null) {

43. Toast.makeText(this, "服务不可用!", Toast.LENGTH_SHORT).show();

44. return;

45. }

46. Message message = new Message();

47. message.obj="长江长江我是黄河";

48. message.what =0;

49. messenger.send(message);

50. 

51. }

52. 

53. class OutgoingHandler extends Handler{

54. @Override

55. public void handleMessage(Message msg) {

56. Log.d("tag", msg.toString());

57. }

58. }

59. 

60. class MyServiceConnection implements ServiceConnection {

61. 

62. @Override

63. public void onServiceConnected(ComponentName name, IBinder service) {

64. Toast.makeText(MainActivity.this, "连接成功!", Toast.LENGTH_SHORT).show();

65. messenger = new Messenger(service);

66. Message message=new Message();

67. message.what = 1;

68. //Activity绑定Service的时候给Service发送一个消息,该消息的obj属性是一个Messenger对象

69. message.obj = mOutMessenger;

70. try {

71. messenger.send(message);

72. } catch (RemoteException e) {

73. e.printStackTrace();

74. }

75. }

76. 

77. @Override

78. public void onServiceDisconnected(ComponentName name) {

79. Toast.makeText(MainActivity.this, "连接已经断开!", Toast.LENGTH_SHORT).show();

80. }

81. 

82. }

83. 

84. }

2. MessengerService代码

1. package com.example.serviceAndActivity;

 

import android.app.Service;

import android.content.Intent;

import android.os.Handler;

import android.os.IBinder;

import android.os.Message;

import android.os.Messenger;

import android.os.RemoteException;

import android.util.Log;

/**

 * Service接收到Activity的消息后会在返回一条消息

 

 * @author wzy 2015-11-25

 *

 */

public class MessengerService extends Service {

private Messenger  messenger = new Messenger(new IncomingHandler());

private Messenger mActivityMessenger ;

 

@Override

public IBinder onBind(Intent intent) {

IBinder binder = messenger.getBinder();

return binder;

}

//1.定义一个Handler对象,该Handler处理Activity发送过来的消息

class IncomingHandler extends Handler{

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case 0:

Log.d("tag", msg.toString());

if (mActivityMessenger!=null) {

Message message= new Message();

message.what = 2;

message.obj="地瓜地瓜我是土豆";

try {

mActivityMessenger.send(message);

} catch (RemoteException e) {

e.printStackTrace();

}

}

break;

case 1:

mActivityMessenger =  (Messenger) msg.obj;

Log.d("tag", "已经获取到Activity发送了的Messenger对象");

break;

default:

break;

}

}

}

 

}

 

运行界面以及日志如下图所示:

 

 

 

三、BroadCastReceiver

1、请描述一下BroadcastReceiver

BroadCastReceiver是Android四大组件之一,主要用于接收系统或者app发送的广播事件。

广播分两种:有序广播和无序广播。

内部通信实现机制:通过Android系统的Binder机制实现通信。

无序广播:完全异步,逻辑上可以被任何广播接收者接收到。优点是效率较高。缺点是一个接收者不能将处理结果传递给下一个接收者,并无法终止广播intent的传播。

有序广播:按照被接收者的优先级顺序,在被接收者中依次传播。比如有三个广播接收者A,B,C,优先级是A > B > C。那这个消息先传给A,再传给B,最后传给C。每个接收者有权终止广播,比如B终止广播,C就无法接收到。此外A接收到广播后可以对结果对象进行操作,当广播传给B时,B可以从结果对象中取得A存入的数据。

在通过Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)时我们可以指定resultReceiver广播接收者,这个接收者我们可以认为是最终接收者,通常情况下如果比他优先级更高的接收者如果没有终止广播,那么他的onReceive会被执行两次,第一次是正常的按照优先级顺序执行,第二次是作为最终接收者接收。如果比他优先级高的接收者终止了广播,那么他依然能接收到广播。

在我们的项目中经常使用广播接收者接收系统通知,比如开机启动、sd挂载、低电量、外播电话、锁屏等。

如果我们做的是播放器,那么监听到用户锁屏后我们应该将我们的播放之暂停等。

2、manifest和代码中如何注册和使用BroadcastReceiver

在清单文件中注册广播接收者称为静态注册,在代码中注册称为动态注册。静态注册的广播接收者只要app在系统中运行则一直可以接收到广播消息,动态注册的广播接收者当注册的Activity或者Service销毁了那么就接收不到广播了。

静态注册:在清单文件中进行如下配置

动态注册:在代码中进行如下注册

3、BroadCastReceiver的生命周期

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

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

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

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

4、如何让自己的广播只让指定的app接收(2015.09.02)

1、自己的应用(假设名称为应用A)在发送广播的时候给自己发送的广播添加自定义权限,假设权限名为:com.itheima.android.permission然后需要在应用A的AndroidManifest.xml中声明如下权限:

  <permission android:name="com.itheima.android.permission"android:protectionLevel="normal"></permission>

    <uses-permission android:name="com.itheima.android.permission"/>

 

2、其他应用(假设名称诶应用B)如果想接收该广播,那么就必须知道应用A广播使用的权限。然后在应用B的清单文件中如下配置:

    <uses-permission android:name="com.itheima.android.permission"/>

或者在应用AndroidManifest.xml中的<receiver>标签中进行如下配置:

<receiver android:name="com.itheima.android.broadcastReceiver.MyReceiver" android:permission="com.itheima.android.permission">

            <intent-filter >

                <action android:name="com.itheima.mybroadcast"></action>

            </intent-filter>

        </receiver>

 

每个权限通过 protectionLevel 来标识保护级别:

normal:低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加<uses-permission>标签),安装时不需要用户确认;

dangerous:高风险权限,安装时需要用户的确认才可使用;

signature:只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它;

signatureOrSystem:签名相同,或者申请权限的应用为系统应用(在system image中)。

上述四类权限级别同样可用于自定义权限中。如果开发者需要对自己的应用程序(或部分应用)进行访问控制,则可以通过在AndroidManifest.xml中添加<permission>标签,将其属性中的protectionLevel设置为上述四类级别中的某一种来实现。

5、什么是最终广播接收者?

最终广播是我们自己应用发送有序广播时通过ContextWrapper.sendOrderedBroadcast()方法指定的当前应用下的广播,该广播可能会被执行两次,第一次是作为普通广播按照优先级接收广播,第二次是作为final receiver必须接收一次。

6、广播的优先级对无序广播生效吗?

生效的。

7、动态注册的广播优先级谁高?

谁先注册谁优先级高。

8、如何判断当前BroadcastReceiver接收到的是有序广播还是无序广播?(2015-10-16)

BroadcastReceiver类中onReceive()方法中,可以调用boolean b = isOrderedBroadcast();该方法是BroadcastReceiver类中提供的方法,用于告诉我们当前的接收到的广播是否为有序广播。

 

 

 

四、ContentProvider&数据库

1、请介绍下ContentProvider是如何实现数据共享的?

Android中如果想将自己应用的数据(一般多为数据库中的数据)提供给第三发应用,那么我们只能通过ContentProvider来实现了。

ContentProvider是应用程序之间共享数据的接口。使用的时候首先自定义一个类继承ContentProvider,然后覆写query、insert、update、delete等方法。因为其是四大组件之一因此必须在AndroidManifest文件中进行注册。

第三方可以通过ContentResolver来访问该Provider。

2、为什么要用ContentProvider?它和sql的实现上有什么差别?

ContentProvider屏蔽了数据存储的细节,内部实现对用户完全透明,用户只需要关心操作数据的uri就可以了,ContentProvider可以实现不同app之间共享。

Sql也有增删改查的方法,但是sql只能查询本应用下的数据库。而ContentProvider 还可以去增删改查本地文件. xml文件的读取等。

3、说说ContentProvider、ContentResolver、ContentObserver之间的关系

ContentProvider 内容提供者,用于对外提供数据

ContentResolver.notifyChange(uri)发出消息

ContentResolver 内容解析者,用于获取内容提供者提供的数据

ContentObserver 内容监听器,可以监听数据的改变状态

ContentResolver.registerContentObserver()监听消息。

4、如何访问asserts资源目录下的数据库?

//1. 获取到assert目录下的db文件

AssetManager assetManager = getAssets();

InputStream is = assetManager.open("myuser.db");

//将文件拷贝到/data/data/com.itheima.android.asserts.sqlite/databases/myuser.db

//如果databases目录不存在则创建

File file = new File("/data/data/com.itheima.android.asserts.sqlite/databases");

if (!file.exists()) {

file.mkdirs();

}

FileOutputStream fos = new FileOutputStream(new File(file,"myuser.db"));

byte[] buff = new byte[1024*8];

int len=-1;

while((len=is.read(buff))!=-1){

fos.write(buff, 0, len);

}

fos.close();

is.close();

//访问数据库

SQLiteDatabase database = openOrCreateDatabase("myuser.db",MODE_PRIVATE,null);

String sql = "select c_name from t_user";

Cursor cursor = database.rawQuery(sql , null);

while(cursor.moveToNext()){

String string = cursor.getString(0);

Log.d("tag", string);

}

cursor.close();

database.close();

}

5、如何在高并发下进行数据库查询?(2015-11-25)

(这个问题的回答很广泛,可以自由发挥)

比如:不要关联多表查询,减少链接时间,创建索引、将查询到的数据采用缓存策略等等。

五、Android中的布局

1、Android中常用的布局都有哪些

FrameLayout

RelativeLayout

LinearLayout

AbsoluteLayout

TableLayout

GrideLayout(Android 4.0推出)

2、谈谈UI中, Padding和Margin有什么区别?

android:padding和android:layout_margin的区别,其实概念很简单,padding是站在父view的角度描述问题,它规定它里面的内容必须与这个父view边界的距离。margin则是站在自己的角度描述问题,规定自己和其他(上下左右)的view之间的距离,如果同一级只有一个view,那么它的效果基本上就和padding一样了。

3、使用权重如何让一个控件的宽度为父控件的1/3?

可以在水平方向的LinearLayout中设置weightSum为3,然后让其子控件的weight为1,那么该子控件就是父控件的1/3。

4、Android中布局的优化措施都有哪些?

1、尽可能减少布局的嵌套层级

可以使用sdk提供的hierarchyviewer工具分析视图树,帮助我们发现没有用到的布局。

2、不用设置不必要的背景,避免过度绘制

比如父控件设置了背景色,子控件完全将父控件给覆盖的情况下,那么父控件就没有必要设置背景。

3、使用<include>标签复用相同的布局代码

4、使用<merge>标签减少视图层次结构

该标签主要有两种用法:

1) 因为所有的Activity视图的根节点都是FrameLayout,因此如果我们的自定义的布局也是FragmenLayout的时候那么可以使用merge替换。

2) 当应用Include或者ViewStub标签从外部导入xml结构时,可以将被导入的xml用merge作为根节点表示,这样当被嵌入父级结构中后可以很好的将它所包含的子集融合到父级结构中,而不会出现冗余的节点。

<merge>只能作为xml布局的根元素。

5、通过<ViewStub>实现View的延迟加载

布局如下:

核心代码:

5、android:layout_gravity和android:gravity的区别?

第一个是让该布局在其父控件中的布局方式,第二个是该布局布置其字对象的布局方式。

 

 

 

六、ListView

1、ListView如何提高其效率?

① 复用ConvertView

② 自定义静态类ViewHolder

③ 使用分页加载

④ 使用WeakRefrence引用ImageView对象

2、ViewHolder为什么要声明为静态类?

非静态内部类拥有外部类对象的强引用,因此为了避免对外部类(外部类很可能是Activity)对象的引用,那么最好将内部类声明为static的。

3、Activity中使用Handler的时候如何去除警告信息?

可以使用弱引用.

4、谈谈ListView中的MVC思想?

M:model

V:view

C:Controller

5、ListView使用了哪些设计模式?

1、适配器

2、观察者

3、享元设计模式

6、ListView数据集改变后,如何更新ListView?

使用该ListView的adapter的notifyDataSetChanged()方法。该方法会使ListView重新绘制。

7、ListView如何实现分页加载

  ① 设置ListView的滚动监听器:setOnScrollListener(new OnScrollListener{….})

在监听器中有两个方法: 滚动状态发生变化的方法(onScrollStateChanged)和listView被滚动时调用的方法(onScroll)

  ② 在滚动状态发生改变的方法中,有三种状态:

手指按下移动的状态: SCROLL_STATE_TOUCH_SCROLL: // 触摸滑动

惯性滚动(滑翔(flging)状态): SCROLL_STATE_FLING: // 滑翔

静止状态: SCROLL_STATE_IDLE: // 静止

 对不同的状态进行处理:

分批加载数据,只关心静止状态:关心最后一个可见的条目,如果最后一个可见条目就是数据适配器(集合)里的最后一个,此时可加载更多的数据。在每次加载的时候,计算出滚动的数量,当滚动的数量大于等于总数量的时候,可以提示用户无更多数据了。

8、ListView可以显示多种类型的条目吗?

这个当然可以的,ListView显示的每个条目都是通过baseAdapter的getView(int position, View convertView, ViewGroup parent)来展示的,理论上我们完全可以让每个条目都是不同类型的view,除此之外adapter还提供了getViewTypeCount()和getItemViewType(int position)两个方法。在getView方法中我们可以根据不同的viewtype加载不同的布局文件。

9、ListView如何定位到指定位置

可以通过ListView提供的lv.setSelection(48);方法。

10、如何在ScrollView中如何嵌入ListView

通常情况下我们不会在ScrollView中嵌套ListView,但是如果面试官非让我嵌套的话也是可以的。

ScrollView添加一个ListView会导致listview控件显示不全,通常只会显示一条,这是因为两个控件的滚动事件冲突导致。所以需要通过listview中的item数量去计算listview的显示高度,从而使其完整展示,如下提供一个方法供大家参考。

如下图,在ScrollView中嵌套了ListView,ListView展现的是物流状态。

 

布局文件片段:

1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

2.     android:layout_width="match_parent"

3.     android:layout_height="match_parent"

4.     android:orientation="vertical" >

5.     <ScrollView

6.         android:id="@+id/sv"

7.         android:layout_width="match_parent"

8.         android:layout_height="0dp"

9.         android:layout_weight="1" >

10. 

11.         <LinearLayout

12.             android:layout_width="match_parent"

13.             android:layout_height="match_parent"

14.             android:orientation="vertical" >

15. 

16.             <ListView

17.                 android:id="@+id/lv"

18.                 android:layout_width="match_parent"

19.                 android:layout_height="wrap_content" >

20.             </ListView>

21.         </LinearLayout>

22.     </ScrollView>

23. 

24. </LinearLayout>

注意:如果直接将ListView放到ScrollView中,那么上面的代码依然是没有效果的.必须将ListVIew放到LinearLayout等其他容器中才行.

11、ListView中如何优化图片

图片的优化策略比较多。

1、处理图片的方式:

如果ListView中自定义的Item中有涉及到大量图片的,一定要对图片进行细心的处理,因为图片占的内存是ListView项中最头疼的,处理图片的方法大致有以下几种:

①、不要直接拿路径就去循环BitmapFactory.decodeFile;使用Options保存图片大小、不要加载图片到内存去。

②、对图片一定要经过边界压缩尤其是比较大的图片,如果你的图片是后台服务器处理好的那就不需要了

③、在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使用WeakReference mContextRef)、SoftReference、WeakHashMap等的来存储图片信息。

④、在getView中做图片转换时,产生的中间变量一定及时释放

2、异步加载图片基本思想:

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

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

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

原理:

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

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

优化三:ImageLoader里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,如果每次总是new一个线程去执行这是非常不可取的,好一点的用的AsyncTask类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存。

12、ListView中图片错位的问题是如何产生的

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

 

13、scrollView嵌套listview方式除了测量还有什么方法?(2015-11-29)

1、手动设置ListView高度

经过测试发现,在xml中直接指定ListView的高度,是可以解决这个问题的,但是ListView中的数据是可变的,实际高度还需要实际测量。

于是手动代码设置ListView高度的方法就诞生了。

/**

* 动态设置ListView的高度

* @param listView

*/

public static void setListViewHeightBasedOnChildren(ListView listView) {

if(listView == null) return;

ListAdapter listAdapter = listView.getAdapter();

if (listAdapter == null) {

// pre-condition

return;

}

int totalHeight = 0;

for (int i = 0; i < listAdapter.getCount(); i++) {

View listItem = listAdapter.getView(i, null, listView);

listItem.measure(0, 0);

totalHeight += listItem.getMeasuredHeight();

}

ViewGroup.LayoutParams params = listView.getLayoutParams();

params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));

listView.setLayoutParams(params);

}

2、使用单个ListView取代ScrollView中所有内容

如果满足头布局和脚布局的UI设计,直接使用listview替代scrollview

3、使用LinearLayout取代ListView

既然ListView不能适应ScrollView,那就换一个可以适应ScrollView的控件,干嘛非要吊死在ListView这一棵树上呢?

LinearLayout是最好的选择。但如果我仍想继续使用已经定义好的Adater呢?我们只需要自定义一个类继承自LinearLayout,为其加上对BaseAdapter的适配。

4、自定义可适应ScrollView的ListView

这个方法和上面的方法是异曲同工,方法3是自定义了LinearLayout以取代ListView的功能,但如果我脾气就是倔,就是要用ListView怎么办?

那就只好自定义一个类继承自ListView,通过重写其onMeasure方法,达到对ScrollView适配的效果。

5、参考博客

 http://bbs.anzhuo.cn/thread-982250-1-1.html

 

七、JNI&NDK

1、Android中如何调用C语言

当我们的Java需要调用C语言的时候可以通过JNI的方式,Java Native Interface。Android提供了对JNI的支持,因此我们在Android中可以使用JNI调用C语言。在Android开发目录的libs目录下添加xxx.so文件,不过xxx.so文件需要放在对应的CPU架构名目录下,比如armeabi,x86等。

Java代码需要通过System.loadLibrary(libName);加载so文件。同时C语言中的方法在java中必须以native关键字来声明。普通Java方法调用这个native方法接口,虚拟机内部自动调用so文件中对应的方法。

2、请介绍一下NDK

1.NDK 是一系列工具的集合

NDK 提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so 和java 应用一起打包成apk。NDK 集成了交叉编译器,并提供了相应的mk 文件隔离CPU、平台、ABI 等差异,开发人员只需要简单修改mk 文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

2.NDK 提供了一份稳定、功能有限的API 头文件声明

Google 明确声明该API 是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK 中看出,这些API 支持的功能非常有限,包含有:C 标准库(libc)、标准数学库(libm)、压缩库(libz)、Log 库(liblog)。

3、JNI调用常用的两个参数

 JNIEnv *env, jobject obj

第一个是指向虚拟机对象的指针,是一个二级指针。里面封装了很多方法和中间变量供我们使用。

第二个代表着调用该方法的Java对象的C语言表示。

八、Android中的网络访问

1、Android中如何访问网络

Android提供了org.apache.http.HttpClientConnection和java.net.HttpURLConnection两个连接网络对象。使用哪个都行,具体要看企业领导的要求了。

除此之外一般我比较喜欢使用xUtils中的HttpUtils功能,该模块底层使用的就是org.apache.http.client.HttpClient,使用起来非常方便。

2、如何解析服务器传来的JSON文件

Android中内置了JSON的解析API,在org.json包中包含了如下几个类:JSONArray、JSONObject、JSONStringer、JSONTokener和一个异常类JSONException。

 

1、JSON解析步骤

1)、读取网络文件数据并转为一个json字符串

InputStream in = conn.getInputStream();

String jsonStr = DataUtil.Stream2String(in);//将流转换成字符串的工具类

2)、将字符串传入相应的JSON构造函数中

①、通过构造函数将json字符串转换成json对象

JSONObject  jsonObject = new JSONObject(jsonStr);

②、通过构造函数将json字符串转换成json数组:

JSONArray array = new JSONArray(jsonStr);

3)、解析出JSON中的数据信息:

①、从json对象中获取你所需要的键所对应的值

JSONObject  json=jsonObject.getJSONObject("weatherinfo");

String city = json.getString("city");

String temp = json.getString("temp")

②、遍历JSON数组,获取数组中每一个json对象,同时可以获取json对象中键对应的值

for (int i = 0; i < array.length(); i++) {

JSONObject obj = array.getJSONObject(i);

String title=obj.getString("title");

String description=obj.getString("description");

}

2、生成JSON对象和数组

1)生成JSON:

方法1、创建一个map,通过构造方法将map转换成json对象

Map<String, Object> map = new HashMap<String, Object>();

map.put("name", "zhangsan");

map.put("age", 24);

JSONObject json = new JSONObject(map);

方法2、创建一个json对象,通过put方法添加数据

JSONObject json=new JSONObject();

json.put("name", "zhangsan");

json.put("age", 24);

2)生成JSON数组:

创建一个list,通过构造方法将list转换成json对象

Map<String, Object> map1 = new HashMap<String, Object>();

map1.put("name", "zhangsan");

map1.put("age", 24);

Map<String, Object> map2 = new HashMap<String, Object>();

map2.put("name", "lisi");

map2.put("age", 25);

List<Map<String, Object>> list=new ArrayList<Map<String,Object>>();

list.add(map1);

list.add(map2);

JSONArray array=new JSONArray(list);

System.out.println(array.toString());

3、如何解析服务器传来的XML格式数据

Android为我们提供了原生的XML解析和生成支持。

1、XML解析

获取解析器: Xml.newPullParser()

设置输入流: setInput()

获取当前事件类型: getEventType()

解析下一个事件, 获取类型: next()

获取标签名: getName()

获取属性值: getAttributeValue()

获取下一个文本: nextText()

获取当前文本: getText()

5种事件类型: START_DOCUMENT, END_DOCUMENT, START_TAG, END_TAG, TEXT

示例代码:

public List<Person> getPersons(InuptStream in){

XmlPullParser parser=Xml.newPullParser();//获取解析器

parser.setInput(in,"utf-8");

for(int type=){   //循环解析

}

}

2、XML生成

获取生成工具: Xml.newSerializer()

设置输出流: setOutput()

开始文档: startDocument()

结束文档: endDocument()

开始标签: startTag()

结束标签: endTag()

属性: attribute()

文本: text()

示例代码:

XmlSerializer serial=Xml.newSerializer();//获取xml序列化工具

serial.setOuput(put,"utf-8");

serial.startDocument("utf-8",true);

serial.startTag(null,"persons");

for(Person p:persons){

serial.startTag(null,"persons");

serial.attribute(null,"id",p.getId().toString());

serial.startTag(null,"name");

serial.attribute(null,"name",p.getName().toString());

serial.endTag(null,"name");

serial.startTag(null,"age");

serial.attribute(null,"age",p.getAge().toString());

serial.endTag(null,"age");

serial.endTag(null,"persons");

}

4、如何从网络上加载一个图片显示到界面

可以通过BitmapFactory.decodeStream(inputStream);方法将图片转换为bitmap,然后通过

imageView.setImageBitmap(bitmap);将该图片设置到ImageView中。这是原生的方法,还可以使用第三方开源的工具来实现,比如使用SmartImageView作为ImageView控件,然后直接设置一个url地址即可。也可以使用xUtils中的BitmapUtils工具。

5、如何播放网络视频

除了使用Android提供的MediaPlayer和VideoView外通常还可以使用第三方开源万能播放器,VitamioPlayer。该播放器兼容性好,支持几乎所有主流视频格式。

6、常见的访问网络API都有哪些?

Android系统自带的HttpUrlConnection/HttpClient。

java.net.HttpURLConnection;

 

org.apache.http.client.HttpClient;

OKhttp:

http://blog.csdn.net/lmj623565791/article/details/47911083

xUtils:

https://github.com/wyouflf/xUtils

 

九、Intent

1、Intent传递数据时,可以传递哪些类型数据?

Intent可以传递的数据类型非常的丰富,java的基本数据类型和String以及他们的数组形式都可以,除此之外还可以传递实现了Serializable和Parcelable接口的对象。

2、Serializable和Parcelable的区别

1.在使用内存的时候,Parcelable 类比Serializable性能高,所以推荐使用Parcelable类。

2.Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。

3.Parcelable不能使用在要将数据存储在磁盘上的情况。尽管Serializable效率低点,但在这种情况下,还是建议你用Serializable 。

实现:

1 Serializable 的实现,只需要继承Serializable 即可。这只是给对象打了一个标记,系统会自动将其序列化。

2 Parcelabel 的实现,需要在类中添加一个静态成员变量 CREATOR,这个变量需要继承 Parcelable.Creator 接口。

3、请描述一下Intent 和 IntentFilter

Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个 Intent 对象,消息“目的地”是必须的,而内容则是可选项。

通过Intent 可以实现各种系统组件的调用与激活.  

IntentFilter: 可以理解为邮局或者是一个信笺的分拣系统…

这个分拣系统通过3个参数来识别

Action: 动作    view

Data: 数据uri   uri

Category : 而外的附加信息

Action 匹配

Action 是一个用户定义的字符串,用于描述一个 Android 应用程序组件,一个 IntentFilter 可以包含多个 Action。在 AndroidManifest.xml 的 Activity 定义时可以在其 <intent-filter >节点指定一个 Action 列表用于标示 Activity 所能接受的“动作”,例如:

 <intent-filter >

 <action android:name="android.intent.action.MAIN" />

 <action android:name="cn.itheima.action" />

……

 </intent-filter>

如果我们在启动一个 Activity 时使用这样的 Intent 对象:

 Intent intent =new Intent();

 intent.setAction("cn.itheima.action");

那么所有的 Action 列表中包含了“cn.itheima”的 Activity 都将会匹配成功。

Android 预定义了一系列的 Action 分别表示特定的系统动作。这些 Action 通过常量的方式定义在 android.content. Intent中,以“ACTION_”开头。我们可以在 Android 提供的文档中找到它们的详细说明。

URI 数据匹配

一个 Intent 可以通过 URI 携带外部数据给目标组件。在 <intent-filter >节点中,通过 <data/>节点匹配外部数据。

mimeType 属性指定携带外部数据的数据类型,scheme 指定协议,host、port、path 指定数据的位置、端口、和路径。如下:

 <data android:mimeType="mimeType" android:scheme="scheme"

 android:host="host" android:port="port" android:path="path"/>

电话的uri   tel: 12345

   http://www.baidu.com

自己定义的uri  itcast://cn.itcast/person/10

如果在 Intent Filter 中指定了这些属性,那么只有所有的属性都匹配成功时 URI 数据匹配才会成功。

Category 类别匹配

<intent-filter >节点中可以为组件定义一个 Category 类别列表,当 Intent 中包含这个列表的所有项目时 Category 类别匹配才会成功。

4、under what condition could the code sample below crash your application?How would you modify the code to avoid this potential problem?Explain your answer?(重要)

Intent sendIntent = new Intent();

sendIntent.setAction(Intent.ACTION_SEND);

sendIntent.putExtra(Intent.EXTRA_TEXT,textMessage);

sendIntent.setType(HTTP.PLAIN_TEXT_TYEP);//"text/plain" MIME type

context.startActivity(sendIntent);

answer:

if startActivity(Intent ) method is not called from in Activity ,for example,from service or receiver,it will crash your application.

I can add a flag “Intent_ACTIVITY_NEW_TASK”on sendIntent to avoid this error.

5、what are Activity and Fragment?where and why should you use one over the other?

 

6、can you use an intent to provide data to a ContentProvider ? if not ,what would be the proper mechanism for doing this?

SQLiteDatabase is often used to provide data to a ContenProvider.

十、Fragment

1、Fragment跟Activity之间是如何传值的?

1) 当Fragment跟Activity绑定之后,在Fragment中可以直接通过getActivity()方法获取到其绑定的Activity对象,这样就可以调用Activity的方法了。在Activity中可以通过如下方法获取到Fragment实例


获取到Fragment之后就可以调用Fragment的方法。也就实现了通信功能。

2)另外也可以调用fragment.setArguments(Bundle)方法,将数据绑定到Fragment域中。

2、描述一下Fragment的生命周期

3、Fragment的replace和add方法的区别(2015.8.30)

Fragment本身并没有replace和add方法,这里的理解应该为使用FragmentManager的replace和add两种方法切换Fragment时有什么不同。

我们经常使用的一个架构就是通过RadioGroup切换Fragment,每个Fragment就是一个功能模块。

实现这个功能可以通过replace和add两种方法。

Fragment的容器一个FrameLayout,add的时候是把所有的Fragment一层一层的叠加到了FrameLayout上了,而replace的话首先将该容器中的其他Fragment去除掉然后将当前Fragment添加到容器中。

一个Fragment容器中只能添加一个Fragment种类,如果多次添加则会报异常,导致程序终止,而replace则无所谓,随便切换。

因为通过add的方法添加的Fragment,每个Fragment只能添加一次,因此如果要想达到切换效果需要通过Fragment的的hide和show方法结合者使用。将要显示的show出来,将其他hide起来。这个过程Fragment的生命周期没有变化。

通过replace切换Fragment,每次都会执行上一个Fragment的onDestroyView,新Fragment的onCreateView、onStart、onResume方法。

基于以上不同的特点我们在使用的使用一定要结合着生命周期操作我们的视图和数据。

4、Fragment如何实现类似Activity栈的压栈和出栈效果的?(2015.8.30)

Fragment的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次add的Fragment和replace的Fragment,然后当我们点击back按钮的时候会自动帮我们实现退栈操作。

除此之外因为我们要使用FragmentManger用的是FragmentActivity,因此FragmentActivity的onBackPress方法必定重新覆写了。打开看一下,发现确实如此。

1. Android高级(★★★

一、Android性能优化

1、如何对Android应用进行性能分析

一款App流畅与否安装在自己的真机里,玩几天就能有个大概的感性认识。不过通过专业的分析工具可以使我们更好的分析我们的应用。

如果不考虑使用其他第三方性能分析工具的话,我们可以直接使用ddms中的工具,其实ddms工具已经非常的强大了。ddms中有traceview、heap、allocation tracker等工具都可以帮助我们分析应用的方法执行时间效率和内存使用情况。

 traceview工具在本文章中已经有详细的介绍,因此这里就不再赘述。

 heap

heap工具可以帮助我们检查代码中是否存在会造成内存泄漏的地方。

heap监测应用进程使用内存情况的步骤如下:

1. 启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;

2. 点击选中想要监测的进程,比如system_process进程;

3. 点击选中Devices视图界面中最上方一排图标中的“Update Heap”图标;

6. 点击Heap视图中的“Cause GC”按钮;

7. 此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。

 说明:

a) 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作;

b) 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;

c) 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。

  如何才能知道我们的程序是否有内存泄漏的可能性呢。这里需要注意一个值:Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:

a) 不断的操作当前应用,同时注意观察data object的Total Size值;

b) 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;

c) 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,

  直到到达一个上限后导致进程被kill掉。

d) 此处以system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过3.55后进程就会被kill。

  总之,使用DDMS的Heap视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。

 allocation tracker

运行DDMS,只需简单的选择应用进程并单击Allocation tracker标签,就会打开一个新的窗口,单击“Start Tracing”按钮;

然后,让应用运行你想分析的代码。运行完毕后,单击“Get Allocations”按钮,一个已分配对象的列表就会出现第一个表格中。

单击第一个表格中的任何一项,在表格二中就会出现导致该内存分配的栈跟踪信息。通过allocation tracker,不仅知道分配了哪类对象,还可以知道在哪个线程、哪个类、哪个文件的哪一行。

2、什么情况下会导致内存泄露

Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。

内存溢出的几点原因:

1、资源释放问题

程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放造成内存泄露。

2、对象内存过大问题

保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。

3、static关键字的使用问题

static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

public class ClassName {  

     private static Context mContext;  

     //省略

}

以上的代码是很危险的,如果将Activity赋值到mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。

我们举Android文档中的一个例子。

 

 

 

 

sBackground是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:

    Drawable->TextView->Context

    所以,最终该Context也没有得到释放,发生了内存泄露。

 针对static的解决方案

① 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。

    ② Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。

    ③ 使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;

4、线程导致内存溢出

线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

 

 

这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一 般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。

由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。

针对这种线程导致的内存泄露问题的解决方案:

    第一、将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。

    第二、在线程内部采用弱引用保存Context引用。

3、如何避免OOM异常

想要避免OOM异常首先我们要知道什么情况下会导致OOM异常。

1、图片过大导致OOM

Android 中用bitmap时很容易内存溢出,比如报如下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget。

解决方法:

方法1: 等比例缩小图片

以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。

方法2:对图片采用软引用,及时地进行recyle()操作

2、界面切换导致OOM

有时候我们会发现这样的问题,横竖屏切换N次后 OOM了。

这种问题没有固定的解决方法,但是我们可以从以下几个方面下手分析。

1、看看页面布局当中有没有大的图片,比如背景图之类的。

去除xml中相关设置,改在程序中设置背景图(放在onCreate()方法中):

    在Activity destory时注意,drawable.setCallback(null); 防止Activity得不到及时的释放。

 2、跟上面方法相似,直接把xml配置文件加载成view 再放到一个容器里,然后直接调用 this.setContentView(View view);方法,避免xml的重复加载。

3、 在页面切换时尽可能少地重复使用一些代码

比如:重复调用数据库,反复使用某些对象等等......

常见的内存使用不当的情况

3、查询数据库没有关闭游标

程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

4、构造Adapter时,没有使用缓存的 convertView

在使用ListView的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。

5、Bitmap对象不再使用时调用recycle()释放内存

有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。

6、其他

  Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、 onDestroy()方法中需要适当的释放资源的情况。

4、Android中如何捕获未捕获的异常(重要)

1、自定义一个Application,比如叫MyApplication继承Application实现UncaughtExceptionHandler。

2、覆写UncaughtExceptionHandleronCreate和uncaughtException方法。

注意:上面的代码只是简单的将异常打印出来。

onCreate方法中我们给Thread类设置默认异常处理handler,如果这句代码不执行则一切都是白搭。

uncaughtException方法中我们必须新开辟个线程进行我们异常的收集工作,然后将系统给杀死。

3、AndroidManifest中配置该Application

4、blog分享

关于异常数据的收集在网上有一篇不错的blog可以推荐给大家。

http://blog.csdn.net/jdsjlzx/article/details/7606423

 

5、Android性能优化博客

给大家推荐一篇Android性能优化的blog,其目录如下:

1.背景

2.应用UI性能问题分析

2-1 应用UI卡顿原理

2-2 应用UI卡顿常见原因

2-3 应用UI卡顿分析解决方法

2-3-1 使用HierarchyViewer分析UI性能

2-3-2 使用GPU过度绘制分析UI性能

2-3-3 使用GPU呈现模式图及FPS考核UI性能

2-3-4 使用Lint进行资源及冗余UI布局等优化

2-3-5 使用Memory监测及GC打印与Allocation Tracker进行UI卡顿分析

2-3-6 使用Traceview和dmtracedump进行分析优化

2-3-7 使用Systrace进行分析优化

2-3-8 使用tracestxt文件进行ANR分析优化

2-4 应用UI性能分析解决总结

3.应用开发Memory内存性能分析优化

3-1 Android内存管理原理

3-2 Android内存泄露性能分析

3-2-1 Android应用内存泄露概念

3-2-2 Android应用内存泄露察觉手段

3-2-3 Android应用内存泄露leakcanary工具定位分析

3-2-4 Android应用内存泄露MAT工具定位分析

3-2-5 Android应用开发规避内存泄露建议

3-3 Android内存溢出OOM性能分析

3-3-1 Android应用内存溢出OOM概念

3-3-2 Android应用内存溢出OOM性能分析

3-3-3 Android应用规避内存溢出OOM建议

3-4 Android内存性能优化总结

4.Android应用API使用及代码逻辑性能分析

4-1 Android应用StringStringBuilderStringBuffer优化建议

4-2 Android应用OnTrimMemory实现性能建议

4-3 Android应用HashMap与ArrayMap及SparseArray优化建议

4-4 Android应用ContentProviderOperation优化建议

4-5 Android应用其他逻辑优化建议

5.Android应用移动设备电池耗电性能分析

5-1 Android应用耗电量概念

5-2 Android应用耗电量优化建议

6.Android应用开发性能优化总结

blog原文如下:

http://blog.csdn.net/yanbober/article/details/48394201

 

 

 

 

二、Android屏幕适配

1、屏幕适配方式都有哪些

1.1 适配方式之dp

名词解释:

 分辨率:eg:480*800,1280*720。表示物理屏幕区域内像素点的总和。(切记:跟屏幕适配没有任何关系)

   因为我们既可以把1280*720的分辨率做到4.0的手机上面。我也可以把1280*720的分辨率做到5.0英寸的手机上面,如果分辨率相同,手机屏幕越小清晰。

 px(pix):像素,就是屏幕中最小的一个显示单元

 dpi(像素密度):即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越丰富。

计算公式:像素密度=√{(长度像素数^2+宽度像素数^2)}/ 屏幕尺寸

注:屏幕尺寸单位为英寸 例:分辨率为1280*720 屏幕宽度为6英寸 计算所得像素密度约等于245,屏幕尺寸指屏幕对角线的长度。

Android手机中dpi分类:

ldpi

Resources for low-density (ldpi) screens (~120dpi).

mdpi

Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.)

hdpi

Resources for high-density (hdpi) screens (~240dpi).

xhdpi

Resources for extra high-density (xhdpi) screens (~320dpi).

在我们的Android工程目录中有如下drawable-*dpi目录,这些目录是用来适配不同分辨率手机的。

 

Android应用在查找图片资源时会根据其分辨率自动从不同的文件目录下查找(这本身就是Android系统的适配策略),如果在低分辨的文件目录中比如drawable-mdpi中没有图片资源,其他目录中都有,当我们将该应用部署到mdpi分辨率的手机上时,那么该应用会查找分辨率较高目录下的资源文件,如果较高分辨率目录下也没有资源则只好找较低目录中的资源了。

常见手机屏幕像素及对应分别率级别:

 ldpi  320*240     

 mdpi  480*320     

 hdpi  800*480

 xhdpi  1280*720

 xxhdpi  1920*1080

dp和px之间的简单换算关系:

 

 ldpi的手机 1dp=0.75px

 mdpi的手机 1dp=1.0px

 hdpi的手机 1dp=1.5px

 xhdpi的手机 1dp=2.0px

 xxhdpi的手机 1dp=3.0px

 

根据上面的描述我们得出如下结论,对于mdpi的手机,我们的布局通过dp单位可以达到适配效果。

1.2 适配方式之dimens

drawable目录类似的,在Android工程的res目录下有values目录,这个是默认的目录,同时为了适配不同尺寸手机我们可以创建一个values-1280x720的文件夹,同时将dimens.xml文件拷贝到该目录下。

 

dimens.xml中定义一个尺寸,如下图所示。

 

values-1280x720目录中的dimens.xml中定义同样的尺寸名称,但是使用不同的尺寸,如下图所示。

 

当我们在布局文件中使用长或者宽度单位时,比如下图所示,应该使用@dimen/width来灵活的定义宽度。

 

:在values-1280x720中,中间的是大写字母X的小写形式x,而不是加减乘除的乘号。如果我们在values-1280x720中放置了dimens常量,一定记得也将该常量的对应值在values目录下的dimens.xml中放一份,因为该文件是默认配置,当用户的手机不是1280*720的情况下系统应用使用的是默认values目录中的dimens.xml。

1.3 适配方式之layout

values一样,在Android工程目录中layout目录也支持类似values目录一样的适配,在layout中我们可以针对不同手机的分辨率制定不同的布局,如下图所示。

 

1.4 适配方式之java代码适配

为了演示用java代码控制适配的效果,因此假设有这样的需求,让一个TextView控件的宽和高分别为屏幕的宽和高的一半。

我们新创建一个Android工程,修改main_activity.xml,布局文件清单如下:

MainActivity.java类中完成用java代码控制TextView的布局效果,其代码清单如下:

其中Constant类是一个常量类,很简单,只有两个常量用来记录屏幕的宽和高,其代码清单如下:

1.5适配方式之weight权重适配

在控件中使用属性android:layout_weight="1"以起到适配效果,但是该属性的使用有如下规则:

1、只能用在线性控件中,比如LinearLayout。

2、竖直方向上使用权重的控件高度必须为0dp(Google官方的推荐用法)

3、水平方向上使用权重的控件宽度必须为0dp(Google官方的推荐用法)

2、屏幕适配的处理技巧都有哪些

手机自适应主要分为两种情况:横屏和竖屏的切换,以及分辨率大小的不同。

2.1横屏和竖屏的切换

1、Android应用程序支持横竖屏幕的切换,Android中每次屏幕的切换动会重启Activity,所以应该在Activity销毁(执行onPause()方法和onDestroy()方法)前保存当前活动的状态;在Activity再次创建的时候载入配置,那样,进行中的游戏就不会自动重启了!有的程序适合从竖屏切换到横屏,或者反过来,这个时候怎么办呢?可以在配置Activity的地方进行如下的配置android:screenOrientation="portrait"(landscape是横向,portrait是纵向)。这样就可以保证是竖屏总是竖屏了。

2、而有的程序是适合横竖屏切换的。如何处理呢?首先要在配置Activity的时候进行如下的配置:

android:configChanges="keyboardHidden|orientation",另外需要重写Activity的onConfigurationChanged方法。实现方式如下:

2.2分辨率大小不同

对于分辨率问题,官方给的解决办法是创建不同的layout文件夹,这就需要对每种分辨率的手机都要写一个布局文件,虽然看似解决了分辨率的问题,但是如果其中一处或多处有修改了,就要每个布局文件都要做出修改,这样就造成很大的麻烦。那么可以通过以下几种方式解决:

一)使用layout_weight

目前最为推荐的Android多屏幕自适应解决方案。

    该属性的作用是决定控件在其父布局中的显示权重,一般用于线性布局中。其值越小,则对应的layout_width或layout_height的优先级就越高(一般到100作用就不太明显了);一般横向布局中,决定的是layout_width的优先级;纵向布局中,决定的是layout_height的优先级。

    传统的layout_weight使用方法是将当前控件的layout_width和layout_height都设置成fill_parent,这样就可以把控件的显示比例完全交给layout_weight;这样使用的话,就出现了layout_weight越小,显示比例越大的情况(即权重越大,显示所占的效果越小)。不过对于2个控件还好,如果控件过多,且显示比例也不相同的时候,控制起来就比较麻烦了,毕竟反比不是那么好确定的。于是就有了现在最为流行的0px设值法。看似让人难以理解的layout_height=0px的写法,结合layout_weight,却可以使控件成正比例显示,轻松解决了当前Android开发最为头疼的碎片化问题之一。

二)清单文件配置:【不建议使用这种方式,需要对不同的界面写不同的布局】

需要在AndroidManifest.xml文件的<manifest>元素如下添加子元素

<supports-screensandroid:largeScreens="true"

android:normalScreens="true"

android:anyDensity="true"

android:smallScreens="true"

android:xlargeScreens="true">

</supports-screens>

以上是为我们的屏幕设置多分辨率支持(更准确的说是适配大、中、小三种密度)。

Android:anyDensity="true",这一句对整个的屏幕都起着十分重要的作用,值为true,我们的应用程序当安装在不同密度的手机上时,程序会分别加载hdpi,mdpi,ldpi文件夹中的资源。相反,如果值设置为false,即使我们在hdpi,mdpi,ldpi,xdpi文件夹下拥有同一种资源,那么应用也不会自动地去相应文件夹下寻找资源。而是会在大密度和小密度手机上加载中密度mdpi文件中的资源。

有时候会根据需要在代码中动态地设置某个值,可以在代码中为这几种密度分别设置偏移量,但是这种方法最好不要使用,最好的方式是在xml文件中不同密度的手机进行分别设置。这里地图的偏移量可以在values-xpdi,values-hpdi,values-mdpi,values-ldpi四种文件夹中的dimens.xml文件进行设置。

三)、其他:

说明:

在不同分辨率的手机模拟器下,控件显示的位置会稍有不同

通过在layout中定义的布局设置的参数,使用dp(dip),会根据不同的屏幕分辨率进行适配

但是在代码中的各个参数值,都是使用的像素(px)为单位的

技巧:

1、尽量使用线性布局,相对布局,如果屏幕放不下了,可以使用ScrollView(可以上下拖动)

ScrowView使用的注意:

在不同的屏幕上显示内容不同的情况,其实这个问题我们往往是用滚动视图来解决的,也就是ScrowView;需要注意的是ScrowView中使用layout_weight是无效的,既然使用ScrowView了,就把它里面的控件的大小都设成固定的吧。

2、指定宽高的时候,采用dip的单位,dp单位动态匹配

3、由于android代码中写的单位都是像素,所有需要通过工具类进行转化

4、尽量使用9-patch图,可以自动的依据图片上面显示的内容被拉伸和收缩。其中在编辑的时候,灰色区域是被拉伸的,上下两个点控制水平方向的拉伸,左右两点控制垂直方向的拉伸

3、dp和px之间的关系

dp:是dip的简写,指密度无关的像素。

指一个抽象意义上的像素,程序用它来定义界面元素。一个与密度无关的,在逻辑尺寸上,与一个位于像素密度为160dpi的屏幕上的像素是一致的。要把密度无关像素转换为屏幕像素,可以用这样一个简单的公式:pixels=dips*(density/160)。举个例子,在DPI为240的屏幕上,1个DIP等于1.5个物理像素。

布局时最好使用dp来定义我们程序的界面,因为这样可以保证我们的UI在各种分辨率的屏幕上都可以正常显示。

三、AIDL

1、什么是AIDL以及如何使用

①aidl是Android interface definition Language 的英文缩写,意思Android 接口定义语言。

②使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。

③将服务的aidl放到对应的src目录,工程的gen目录会生成相应的接口类

我们通过bindService(Intent,ServiceConnect,int)方法绑定远程服务,在bindService中有一个ServiceConnec接口,我们需要覆写该类的onServiceConnected(ComponentName,IBinder)方法,这个方法的第二个参数IBinder对象其实就是已经在aidl中定义的接口,因此我们可以将IBinder对象强制转换为aidl中的接口类。

我们通过IBinder获取到的对象(也就是aidl文件生成的接口)其实是系统产生的代理对象,该代理对象既可以跟我们的进程通信,又可以跟远程进程通信,作为一个中间的角色实现了进程间通信。

四、自定义控件

1、如何自定义一个控件

自定义控件可以分为两种自定义组合控件和自定义view。

 自定义组合控件

自定义组合控件就是把多个控件做为一个整体看待、处理。这样的好处不仅可以减轻xml的代码量,也提高了代码的复用性。

在手机卫士项目中我们第一次接触了自定义组合控件。

1. 声明一个View 对象,继承相对布局,或者线性布局或者其他的ViewGroup。

2. 在自定义的View 对象里面重写它的构造方法,在构造方法里面就把布局都初始化完毕。

3. 根据业务需求添加一些api 方法,扩展自定义的组合控件;

4. 希望在布局文件里面可以自定义一些属性。

5. 声明自定义属性的命名空间。

xmlns:itheima="http://schemas.android.com/apk/res/com.itheima.mobilesafe"

6. 在res 目录下的values 目录下创建attrs.xml 的文件声明我们写的属性。

7. 在布局文件中写自定义的属性。

8. 使用这些定义的属性。自定义View 对象的构造方法里面有一个带两个参数的构造方法布局文件里面定义的属性都放在AttributeSet attrs,获取那些定义的属性。

 自定义view

自定义View首先要实现一个继承自View的类。添加类的构造方法,通常是三个构造方法,不过从Android5.0开始构造方法已经添加到4个了。override父类的方法,如onDraw,(onMeasure)等。如果自定义的View有自己的属性,需要在values下建立attrs.xml文件,在其中定义属性,同时代码也要做修改。

blog分享:http://blog.csdn.net/lmj623565791/article/details/24252901

2、请描述一下View的绘制流程

整个View树的绘图流程是在ViewRoot.java类(该类位于Android源码下面:D:\AndroidSource_GB\AndroidSource_GB\frameworks\base\core\java\android\view)的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw),其框架过程如下:

 

1、mesarue()过程

  主要作用:为整个View树计算实际的大小,即设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:

  mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

  具体的调用链如下: ViewRoot根对象的属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:    

         1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(对应属性:mMeasuredHeight)和宽(对应属性:mMeasureWidth)。

         2 、如果该View对象是个ViewGroup类型,需要重写该onMeasure()方法,对其子视图进行遍历的measure()过程。对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。

2、layout布局过程

主要作用:为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。

具体的调用链如下:

          1、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现)接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)。

      2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

3、draw()绘图过程

  ViewRoot对象的performTraversals()方法调用draw()方法发起绘制该View树,值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些“需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。

调用流程

          1 、绘制该View的背景

          2 、为显示渐变框做一些准备操作(大多数情况下,不需要改渐变框)          

          3、调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)

          4、调用dispatchDraw ()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw ()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

参考blog分享:http://blog.csdn.net/qinjuning/article/details/7110211

五、Android中的事件处理

1、Handler机制

Android中主线程也叫UI线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。

我们通常将Handler声明在Activity中,然后覆写Handler中的handleMessage方法,当子线程调用handler.sendMessage()方法后handleMessage方法就会在主线程中执行。

这里面除了Handler、Message外还有隐藏的Looper和MessageQueue对象。

在主线程中Android默认已经调用了Looper.preper()方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中。当调用Handler的sendMessage(对象)方法的时候就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue中取出Message,然后调用Message的target对象的handleMessage()方法。这样就完成了整个消息机制。

2、事件分发机制

2.1 事件分发中的onTouch和onTouchEvent有什么区别,又该如何使用?

这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。

另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。

2.2 请描述一下Android的事件分发机制

Android的事件分发机制主要是Touch事件分发,有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。

View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。

先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图:

 

当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

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事件。

0 0