Android面试整里大全.doc
1、 Android的四大组件是哪些,它们的作用?答:Activity:Activity是Android程序与用户交互的窗口,是Android构造块中最基本的一种,它需要为保持各界面的状态,做很多持久化的事情,妥善管理生命周期以及一些跳转逻辑service:后台服务于Activity,封装有一个完整的功能逻辑实现,接受上层指令,完成相关的事物,定义好需要接受的Intent提供同步和异步的接口Content Provider:是Android提供的第三方应用数据的访问方案,可以派生Content Provider类,对外提供数据,可以像数据库一样进行选择排序,屏蔽内部数据的存储细节,向外提供统一的接口模型,大大简化上层应用,对数据的整合提供了更方便的途径BroadCast Receiver:接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification,统一了Android的事件广播模型2、 请介绍下Android中常用的五种布局。常用五种布局方式,分别是:FrameLayout(框架布局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局)。一、FrameLayout:所有东西依次都放在左上角,会重叠,这个布局比较简单,也只能放一点比较简单的东西。二、LinearLayout:线性布局,每一个LinearLayout里面又可分为垂直布局(android:orientation="vertical")和水平布局(android:orientation="horizontal" )。当垂直布局时,每一行就只有一个元素,多个元素依次垂直往下;水平布局时,只有一行,每一个元素依次向右排列。三、AbsoluteLayout:绝对布局用X,Y坐标来指定元素的位置,这种布局方式也比较简单,但是在屏幕旋转时,往往会出问题,而且多个元素的时候,计算比较麻烦。四、RelativeLayout:相对布局可以理解为某一个元素为参照物,来定位的布局方式。主要属性有:相对于某一个元素android:layout_below、 android:layout_toLeftOf相对于父元素的地方android:layout_alignParentLeft、android:layout_alignParentRigh;五、TableLayout:表格布局,每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个元素。每一个布局都有自己适合的方式,这五个布局元素可以相互嵌套应用,做出美观的界面。3、 android中的动画有哪几类,它们的特点和区别是什么 答:两种,一种是Tween动画、还有一种是Frame动画。Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;另一种Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。4、 android 中有哪几种解析xml的类?官方推荐哪种?以及它们的原理和区别。答:XML解析主要有三种方式,SAX、DOM、PULL。常规在PC上开发我们使用Dom相对轻松些,但一些性能敏感的数据库或手机上还是主要采用SAX方式,SAX读取是单向的,优点:不占内存空间、解析属性方便,但缺点就是对于套嵌多个分支来说处理不是很方便。而DOM方式会把整个XML文件加载到内存中去,该方法在查找方面可以和XPath很好的结合如果数据量不是很大推荐使用,而PULL常常用在J2ME对于节点处理比较好,类似SAX方式,同样很节省内存,在J2ME中我们经常使用的KXML库来解析。5、 ListView的优化方案答:1、如果自定义适配器,那么在getView方法中要考虑方法传进来的参数contentView是否为null,如果为null就创建contentView并返回,如果不为null则直接使用。在这个方法中尽可能少创建view。2、给contentView设置tag(setTag(),传入一个viewHolder对象,用于缓存要显示的数据,可以达到图像数据异步加载的效果。3、如果listview需要显示的item很多,就要考虑分页加载。比如一共要显示100条或者更多的时候,我们可以考虑先加载20条,等用户拉到列表底部的时候再去加载接下来的20条。6、 请介绍下Android的数据存储方式。答:使用SharedPreferences存储数据;文件存储数据;SQLite数据库存储数据;使用ContentProvider存储数据;网络存储数据;Preference,File, DataBase这三种方式分别对应的目录是/data/data/Package Name/Shared_Pref, /data/data/Package Name/files, /data/data/Package Name/database 。一:使用SharedPreferences存储数据首先说明SharedPreferences存储方式,它是 Android提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了Map数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:void ReadSharedPreferences()String strName,strPassword;SharedPreferences user = getSharedPreferences(“user_info”,0);strName = user.getString(“NAME”,”);strPassword = user getString(“PASSWORD”,”);void WriteSharedPreferences(String strName,String strPassword)SharedPreferences user = getSharedPreferences(“user_info”,0);uer.edit();user.putString(“NAME”, strName);user.putString(“PASSWORD” ,strPassword);userxmit();数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用edit()使其处于编辑状态,然后才能修改数据,最后使用commit()提交修改的数据。实际上SharedPreferences是采用了XML格式将数据存储到设备中,在DDMS中的File Explorer中的/data/data/<package name>/shares_prefs下。使用SharedPreferences是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。7、 二:文件存储数据文件存储方式是一种较常用的方法,在Android中读取/写入文件的方法,与 Java中实现I/O的程序是完全一样的,提供了openFileInput()和openFileOutput()方法来读取设备上的文件。具体实例如下:String fn = “moandroid.log”;FileInputStream fis = openFileInput(fn);FileOutputStream fos = openFileOutput(fn,Context.MODE_PRIVATE);三:网络存储数据网络存储方式,需要与Android 网络数据包打交道,关于Android 网络数据包的详细说明,请阅读Android SDK引用了Java SDK的哪些package?。四:ContentProvider1、ContentProvider简介当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。2、Uri类简介Uri代表了要操作的数据,Uri主要包含了两部分信息:1.需要操作的ContentProvider ,2.对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:1.scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content:/2.主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。3.路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:要操作contact表中id为10的记录,可以构建这样的路径:/contact/10要操作contact表中id为10的记录的name字段, contact/10/name要操作contact表中的所有记录,可以构建这样的路径:/contact?要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:Uri uri = Uri.parse("content:/com.changcheng.provider.contactprovider/contact")3、UriMatcher、ContentUrist和ContentResolver简介因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从 Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。UriMatcher:用于匹配Uri,它的用法如下:1.首先把你需要匹配Uri路径全部给注册上,如下:/常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);/如果match()方法匹配content:/com.changcheng.sqlite.provider.contactprovider /contact路径,返回匹配码为1uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);/添加需要匹配uri,如果匹配就会返回匹配码/如果match()方法匹配 content:/com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);/#号为通配符2.注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用 addURI()方法传入的第三个参数,假设匹配 content:/com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:withAppendedId(uri, id)用于为路径加上ID部分parseId(uri)方法用于从路径中获取ID部分ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用 ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。8、 activity的启动模式有哪些?是什么含义?答:在android里,有4种activity的启动模式,分别为: “standard” (默认) “singleTop” “singleTask” “singleInstance”它们主要有如下不同:9、 1. 如何决定所属task “standard”和”singleTop”的activity的目标task,和收到的Intent的发送者在同一个task内,除非intent包括参数FLAG_ACTIVITY_NEW_TASK。 如果提供了FLAG_ACTIVITY_NEW_TASK参数,会启动到别的task里。 “singleTask”和”singleInstance”总是把activity作为一个task的根元素,他们不会被启动到一个其他task里。2. 是否允许多个实例 “standard”和”singleTop”可以被实例化多次,并且存在于不同的task中,且一个task可以包括一个activity的多个实例; “singleTask”和”singleInstance”则限制只生成一个实例,并且是task的根元素。 singleTop要求如果创建intent的时候栈顶已经有要创建 的Activity的实例,则将intent发送给该实例,而不发送给新的实例。3. 是否允许其它activity存在于本task内 “singleInstance”独占一个task,其它activity不能存在那个task里;如果它启动了一个新的activity,不管新的activity的launch mode 如何,新的activity都将会到别的task里运行(如同加了FLAG_ACTIVITY_NEW_TASK参数)。 而另外三种模式,则可以和其它activity共存。4. 是否每次都生成新实例 “standard”对于没一个启动Intent都会生成一个activity的新实例; “singleTop”的activity如果在task的栈顶的话,则不生成新的该activity的实例,直接使用栈顶的实例,否则,生成该activity的实例。 比如现在task栈元素为A-B-C-D(D在栈顶),这时候给D发一个启动intent,如果D是 “standard”的,则生成D的一个新实例,栈变为ABCDD。 如果D是singleTop的话,则不会生产D的新实例,栈状态仍为A-B-C-D 如果这时候给B发Intent的话,不管B的launchmode是”standard” 还是 “singleTop” ,都会生成B的新实例,栈状态变为A-B-C-D-B。“singleInstance”是其所在栈的唯一activity,它会每次都被重用。“singleTask”如果在栈顶,则接受intent,否则,该intent会被丢弃,但是该task仍会回到前台。当已经存在的activity实例处理新的intent时候,会调用onNewIntent()方法 如果收到intent生成一个activity实例,那么用户可以通过back键回到上一个状态;如果是已经存在的一个activity来处理这个intent的话,用户不能通过按back键返回到这之前的状态。10、 跟activity和Task 有关的 Intent启动方式有哪些?其含义?核心的Intent Flag有: FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_CLEAR_TOP FLAG_ACTIVITY_RESET_TASK_IF_NEEDED FLAG_ACTIVITY_SINGLE_TOPFLAG_ACTIVITY_NEW_TASK 如果设置,这个Activity会成为历史stack中一个新Task的开始。一个Task(从启动它的Activity到下一个Task中的 Activity)定义了用户可以迁移的Activity原子组。Task可以移动到前台和后台;在某个特定Task中的所有Activity总是保持相同的次序。 这个标志一般用于呈现“启动”类型的行为:它们提供用户一系列可以单独完成的事情,与启动它们的Activity完全无关。 使用这个标志,如果正在启动的Activity的Task已经在运行的话,那么,新的Activity将不会启动;代替的,当前Task会简单的移入前台。参考FLAG_ACTIVITY_MULTIPLE_TASK标志,可以禁用这一行为。 这个标志不能用于调用方对已经启动的Activity请求结果。FLAG_ACTIVITY_CLEAR_TOP 如果设置,并且这个Activity已经在当前的Task中运行,因此,不再是重新启动一个这个Activity的实例,而是在这个Activity上方的所有Activity都将关闭,然后这个Intent会作为一个新的Intent投递到老的Activity(现在位于顶端)中。 例如,假设一个Task中包含这些Activity:A,B,C,D。如果D调用了startActivity(),并且包含一个指向Activity B的Intent,那么,C和D都将结束,然后B接收到这个Intent,因此,目前stack的状况是:A,B。 上例中正在运行的Activity B既可以在onNewIntent()中接收到这个新的Intent,也可以把自己关闭然后重新启动来接收这个Intent。如果它的启动模式声明为 “multiple”(默认值),并且你没有在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,那么它将关闭然后重新创建;对于其它的启动模式,或者在这个Intent中设置FLAG_ACTIVITY_SINGLE_TOP标志,都将把这个Intent投递到当前这个实例的onNewIntent()中。 这个启动模式还可以与FLAG_ACTIVITY_NEW_TASK结合起来使用:用于启动一个Task中的根Activity,它会把那个Task中任何运行的实例带入前台,然后清除它直到根Activity。这非常有用,例如,当从Notification Manager处启动一个Activity。 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 如果设置这个标志,这个activity不管是从一个新的栈启动还是从已有栈推到栈顶,它都将以the front door of the task的方式启动。这就讲导致任何与应用相关的栈都讲重置到正常状态(不管是正在讲activity移入还是移除),如果需要,或者直接重置该栈为初始状态。FLAG_ACTIVITY_SINGLE_TOP 如果设置,当这个Activity位于历史stack的顶端运行时,不再启动一个新的FLAG_ACTIVITY_BROUGHT_TO_FRONT 这个标志一般不是由程序代码设置的,如在launchMode中设置singleTask模式时系统帮你设定。 FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET 如果设置,这将在Task的Activity stack中设置一个还原点,当Task恢复时,需要清理Activity。也就是说,下一次Task带着 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED标记进入前台时(典型的操作是用户在主画面重启它),这个Activity和它之上的都将关闭,以至于用户不能再返回到它们,但是可以回到之前的Activity。 这在你的程序有分割点的时候很有用。例如,一个e-mail应用程序可能有一个操作是查看一个附件,需要启动图片浏览Activity来显示。这个 Activity应该作为e-mail应用程序Task的一部分,因为这是用户在这个Task中触发的操作。然而,当用户离开这个Task,然后从主画面选择e-mail app,我们可能希望回到查看的会话中,但不是查看图片附件,因为这让人困惑。通过在启动图片浏览时设定这个标志,浏览及其它启动的Activity在下次用户返回到mail程序时都将全部清除。 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 如果设置,新的Activity不会在最近启动的Activity的列表中保存。 FLAG_ACTIVITY_FORWARD_RESULT 如果设置,并且这个Intent用于从一个存在的Activity启动一个新的Activity,那么,这个作为答复目标的Activity将会传到这个新的Activity中。这种方式下,新的Activity可以调用setResult(int),并且这个结果值将发送给那个作为答复目标的 Activity。 FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY 这个标志一般不由应用程序代码设置,如果这个Activity是从历史记录里启动的(常按HOME键),那么,系统会帮你设定。 FLAG_ACTIVITY_MULTIPLE_TASK 不要使用这个标志,除非你自己实现了应用程序启动器。与FLAG_ACTIVITY_NEW_TASK结合起来使用,可以禁用把已存的Task送入前台的行为。当设置时,新的Task总是会启动来处理Intent,而不管这是是否已经有一个Task可以处理相同的事情。 由于默认的系统不包含图形Task管理功能,因此,你不应该使用这个标志,除非你提供给用户一种方式可以返回到已经启动的Task。 如果FLAG_ACTIVITY_NEW_TASK标志没有设置,这个标志被忽略。 FLAG_ACTIVITY_NO_ANIMATION 如果在Intent中设置,并传递给Context.startActivity()的话,这个标志将阻止系统进入下一个Activity时应用 Acitivity迁移动画。这并不意味着动画将永不运行如果另一个Activity在启动显示之前,没有指定这个标志,那么,动画将被应用。这个标志可以很好的用于执行一连串的操作,而动画被看作是更高一级的事件的驱动。 FLAG_ACTIVITY_NO_HISTORY 如果设置,新的Activity将不再历史stack中保留。用户一离开它,这个Activity就关闭了。这也可以通过设置noHistory特性。 FLAG_ACTIVITY_NO_USER_ACTION 如果设置,作为新启动的Activity进入前台时,这个标志将在Activity暂停之前阻止从最前方的Activity回调的onUserLeaveHint()。 典型的,一个Activity可以依赖这个回调指明显式的用户动作引起的Activity移出后台。这个回调在Activity的生命周期中标记一个合适的点,并关闭一些Notification。 如果一个Activity通过非用户驱动的事件,如来电或闹钟,启动的,这个标志也应该传递给Context.startActivity,保证暂停的Activity不认为用户已经知晓其Notification。 FLAG_ACTIVITY_PREVIOUS_IS_TOP If set and this intent is being used to launch a new activity from an existing one, the current activity will not be counted as the top activity for deciding whether the new intent should be delivered to the top instead of starting a new one. The previous activity will be used as the top, with the assumption being that the current activity will finish itself immediately. FLAG_ACTIVITY_REORDER_TO_FRONT 如果在Intent中设置,并传递给Context.startActivity(),这个标志将引发已经运行的Activity移动到历史stack的顶端。 例如,假设一个Task由四个Activity组成:A,B,C,D。如果D调用startActivity()来启动Activity B,那么,B会移动到历史stack的顶端,现在的次序变成A,C,D,B。如果FLAG_ACTIVITY_CLEAR_TOP标志也设置的话,那么这个标志将被忽略。 11、 请描述下Activity的生命周期。答:activity的生命周期方法有:onCreate()、onStart()、onReStart()、onResume()、onPause()、onStop()、onDestory();可见生命周期:从onStart()直到系统调用onStop()前台生命周期:从onResume()直到系统调用onPause()12、 activity在屏幕旋转时的生命周期答:不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次;设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法13、 如何启用Service,如何停用Service。服务的开发比较简单,如下:第一步:继承Service类public class SMSService extends Service 第二步:在AndroidManifest.xml文件中的<application>节点里对服务进行配置:<service android:name=".SMSService" />服务不能自己运行,需要通过调用Context.startService()或Context.bindService()方法启动服务。这两个方法都可以启动Service,但是它们的使用场合有所不同。使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出了,服务仍然运行。使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。如果打算采用Context.startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。采用startService()方法启动的服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。如果打算采用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法。这个时候调用者和服务绑定在一起,调用者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务解除绑定,可以调用unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()->onDestroy()方法。服务常用生命周期回调方法如下: onCreate() 该方法在服务被创建时调用,该方法只会被调用一次,无论调用多少次startService()或bindService()方法,服务也只被创建一次。onDestroy()该方法在服务被终止时调用。与采用Context.startService()方法启动服务有关的生命周期方法onStart() 只有采用Context.startService()方法启动服务时才会回调该方法。该方法在服务开始运行时被调用。多次调用startService()方法尽管不会多次创建服务,但onStart() 方法会被多次调用。与采用Context.bindService()方法启动服务有关的生命周期方法onBind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。onUnbind()只有采用Context.bindService()方法启动服务时才会回调该方法。该方法在调用者与服务解除绑定时被调用14、 注册广播有几种方式,这些方式有何优缺点?请谈谈Android引入广播机制的用意。答:首先写一个类要继承BroadcastReceiver第一种:在清单文件中声明,添加<receive android:name=".IncomingSMSReceiver " ><intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED")<intent-filter><receiver>第二种使用代码进行注册如:IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");IncomingSMSReceiver receiver = new IncomgSMSReceiver();registerReceiver(receiver.filter);两种注册类型的区别是:1)第一种不是常驻型广播,也就是说广播跟随程序的生命周期。2)第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行。15、 请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。答:简单的说,Handler获取当前线程中的looper对象,looper用来从存放Message的MessageQueue中取出Message,再有Handler进行Message的分发和处理.Message Queue(消息队列):用来存放通过Handler发布的消息,通常附属于某一个创建它的线程,可以通过Looper.myQueue()得到当前线程的消息队列Handler:可以发布或者处理一个消息或者操作一个Runnable,通过Handler发布消息,消息将只会发送到与它关联的消息队列,然也只能处理该消息队列中的消息Looper:是Handler和消息队列之间通讯桥梁,程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler:Handler接受到消息后调用handleMessage进行处理Message:消息的类型,在Handler类中的handleMessage方法中得到单个的消息进行处理在单线程模型下,为了线程通信问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:1. Message Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。2. Handler Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。3. Message Queue Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。 每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。4. Looper Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。对于子线程使用Looper,API Doc提供了正确的使用方法:这个Message机制的大概流程: 1. 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。 2. 一开始Message Queue里面的Mess