Android面试题180309
面试岗位:Android开发实习生
面试时间:2018年3月9日上午10:00
1.简要解释一下activity、intent、intent filter、service、Broadcast、BroadcaseReceiver。
解答原文链接:http://blog.csdn.net/Zengyangtech/article/details/6025676
Activity Android中,Activity是所有程序的根本,所有程序的流程都运行在Activity之中,Activity具有自己的生命周期(由系统控制生命周期,程序无法改变,但可以用onSaveInstanceState保存其状态)。 对于Activity,关键是其生命周期的把握(如那张经典的生命周期图=.=),其次就是状态的保存和恢复(onSaveInstanceState onRestoreInstanceState),以及Activity之间的跳转和数据传输(intent)。 Activity中常用的函数有SetContentView() findViewById() finish() startActivity(),其生命周期涉及的函数有: void onCreate(Bundle savedInstanceState) void onStart() void onRestart() void onResume() void onPause() void onStop() void onDestroy() 注意的是,Activity的使用需要在Manifest文件中添加相应的<Activity>,并设置其属性和intent-filter。 Intent Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,Intent在这里起着一个媒体中介的作用,专门提供组件互相调用的相关信息,实现调用者与被调用者之间的解耦。在SDK中给出了Intent作用的表现形式为: · 通过Context.startActivity() orActivity.startActivityForResult() 启动一个Activity; · 通过 Context.startService() 启动一个服务,或者通过Context.bindService() 和后台服务交互; · 通过广播方法(比如 Context.sendBroadcast(),Context.sendOrderedBroadcast(), Context.sendStickyBroadcast()) 发给broadcast receivers。 Intent属性的设置,包括以下几点:(以下为XML中定义,当然也可以通过Intent类的方法来获取和设置) (1)Action,也就是要执行的动作 SDk中定义了一些标准的动作,包括 onstant Target component Action ACTION_CALL activity Initiate a phone call. ACTION_EDIT activity Display data for the user to edit. ACTION_MAIN activity Start up as the initial activity of a task, with no data input and no returned output. ACTION_SYNC activity Synchronize data on a server with data on the mobile device. ACTION_BATTERY_LOW broadcast receiver A warning that the battery is low. ACTION_HEADSET_PLUG broadcast receiver A headset has been plugged into the device, or unplugged from it. ACTION_SCREEN_ON broadcast receiver The screen has been turned on. ACTION_TIMEZONE_CHANGED broadcast receiver The setting for the time zone has changed. 当然,也可以自定义动作(自定义的动作在使用时,需要加上包名作为前缀,如"com.example.project.SHOW_COLOR”),并可定义相应的Activity来处理我们的自定义动作。 (2)Data,也就是执行动作要操作的数据 Android中采用指向数据的一个URI来表示,如在联系人应用中,一个指向某联系人的URI可能为:content://contacts/1。对于不同的动作,其URI数据的类型是不同的(可以设置type属性指定特定类型数据),如ACTION_EDIT指定Data为文件URI,打电话为tel:URI,访问网络为http:URI,而由content provider提供的数据则为content: URIs。 (3)type(数据类型),显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。 (4)category(类别),被执行动作的附加信息。例如 LAUNCHER_CATEGORY 表示Intent 的接受者应该在Launcher中作为顶级应用出现;而ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个,这些动作可以在同一块数据上执行。还有其他的为 Constant Meaning CATEGORY_BROWSABLE The target activity can be safely invoked by the browser to display data referenced by a link — for example, an image or an e-mail message. CATEGORY_GADGET The activity can be embedded inside of another activity that hosts gadgets. CATEGORY_HOME The activity displays the home screen, the first screen the user sees when the device is turned on or when the HOME key is pressed. CATEGORY_LAUNCHER The activity can be the initial activity of a task and is listed in the top-level application launcher. CATEGORY_PREFERENCE The target activity is a preference panel. (5)component(组件),指定Intent的的目标组件的类名称。通常 Android会根据Intent 中包含的其它属性的信息,比如action、data/type、category进行查找,最终找到一个与之匹配的目标组件。但是,如果 component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其它所有属性都是可选的。 (6)extras(附加信息),是其它所有附加信息的集合。使用extras可以为组件提供扩展信息,比如,如果要执行“发送电子邮件”这个动作,可以将电子邮件的标题、正文等保存在extras里,传给电子邮件发送组件。 理解Intent的关键之一是理解清楚Intent的两种基本用法:一种是显式的Intent,即在构造Intent对象时就指定接收者;另一种是隐式的Intent,即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于降低发送者和接收者之间的耦合。 对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些隐式Intent,通过解析,将 Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。 Intent解析机制主要是通过查找已注册在AndroidManifest.xml中的所有IntentFilter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程中,Android是通过Intent的action、type、category这三个属性来进行判断的,判断方法如下: · 如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配; · 如果Intent没有提供type,系统将从data中得到数据类型。和action一样,目标组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配。 · 如果Intent中的数据不是content: 类型的URI,而且Intent也没有明确指定它的type,将根据Intent中数据的scheme (比如 http: 或者mailto:) 进行匹配。同上,Intent 的scheme必须出现在目标组件的scheme列表中。 · 如果Intent指定了一个或多个category,这些类别必须全部出现在组建的类别列表中。比如Intent中包含了两个类别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。 Intent-Filter的定义 一些属性设置的例子: <action android:name="com.example.project.SHOW_CURRENT" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="video/mpeg" android:scheme="http" . . . /> <data android:mimeType="image/*" /> <data android:scheme="http" android:type="video/*" /> 完整的实例 <activity android:name="NotesList" android:label="@string/title_notes_list"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.EDIT"/> <action android:name="android.intent.action.PICK"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="vnd.android.cursor.dir/vnd.google.note"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.GET_CONTENT"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="vnd.android.cursor.item/vnd.google.note"/> </intent-filter> </activity> Intent用法实例 1.无参数Activity跳转 Intent it = new Intent(Activity.Main.this, Activity2.class); startActivity(it); 2.向下一个Activity传递数据(使用Bundle和Intent.putExtras) Intent it = new Intent(Activity.Main.this, Activity2.class); Bundle bundle=new Bundle(); bundle.putString("name", "This is from MainActivity!"); it.putExtras(bundle); // it.putExtra(“test”, "shuju”); startActivity(it); // startActivityForResult(it,REQUEST_CODE); 对于数据的获取可以采用: Bundle bundle=getIntent().getExtras();String name=bundle.getString("name"); 3.向上一个Activity返回结果(使用setResult,针对startActivityForResult(it,REQUEST_CODE)启动的Activity) Intent intent=getIntent(); Bundle bundle2=new Bundle(); bundle2.putString("name", "This is from ShowMsg!"); intent.putExtras(bundle2); setResult(RESULT_OK, intent); 4.回调上一个Activity的结果处理函数(onActivityResult) @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { // TODO Auto-generated method stub super.onActivityResult(requestCode, resultCode, data); if (requestCode==REQUEST_CODE){ if(resultCode==RESULT_CANCELED) setTitle("cancle"); else if (resultCode==RESULT_OK) { String temp=null; Bundle bundle=data.getExtras(); if(bundle!=null) temp=bundle.getString("name"); setTitle(temp); } } } 下面是转载来的其他的一些Intent用法实例(转自javaeye) 显示网页 1. Uri uri = Uri.parse("http://google.com"); 2. Intent it = new Intent(Intent.ACTION_VIEW, uri); 3. startActivity(it); 显示地图 1. Uri uri = Uri.parse("geo:38.899533,-77.036476"); 2. Intent it = new Intent(Intent.ACTION_VIEW, uri); 3. startActivity(it); 4. //其他 geo URI 範例 5. //geo:latitude,longitude 6. //geo:latitude,longitude?z=zoom 7. //geo:0,0?q=my+street+address 8. //geo:0,0?q=business+near+city 9. //google.streetview:cbll=lat,lng&cbp=1,yaw,,pitch,zoom&mz=mapZoom 路径规划 1. Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=startLat%20startLng&daddr=endLat%20endLng&hl=en"); 2. Intent it = new Intent(Intent.ACTION_VIEW, uri); 3. startActivity(it); 4. //where startLat, startLng, endLat, endLng are a long with 6 decimals like: 50.123456 打电话 1. //叫出拨号程序 2. Uri uri = Uri.parse("tel:0800000123"); 3. Intent it = new Intent(Intent.ACTION_DIAL, uri); 4. startActivity(it); 1. //直接打电话出去 2. Uri uri = Uri.parse("tel:0800000123"); 3. Intent it = new Intent(Intent.ACTION_CALL, uri); 4. startActivity(it); 5. //用這個,要在 AndroidManifest.xml 中,加上 6. //<uses-permission id="android.permission.CALL_PHONE" /> 传送SMS/MMS 1. //调用短信程序 2. Intent it = new Intent(Intent.ACTION_VIEW, uri); 3. it.putExtra("sms_body", "The SMS text"); 4. it.setType("vnd.android-dir/mms-sms"); 5. startActivity(it); 1. //传送消息 2. Uri uri = Uri.parse("smsto://0800000123"); 3. Intent it = new Intent(Intent.ACTION_SENDTO, uri); 4. it.putExtra("sms_body", "The SMS text"); 5. startActivity(it); 1. //传送 MMS 2. Uri uri = Uri.parse("content://media/external/images/media/23"); 3. Intent it = new Intent(Intent.ACTION_SEND); 4. it.putExtra("sms_body", "some text"); 5. it.putExtra(Intent.EXTRA_STREAM, uri); 6. it.setType("image/png"); 7. startActivity(it); 传送 Email 1. Uri uri = Uri.parse("mailto:xxx@abc.com"); 2. Intent it = new Intent(Intent.ACTION_SENDTO, uri); 3. startActivity(it); 1. Intent it = new Intent(Intent.ACTION_SEND); 2. it.putExtra(Intent.EXTRA_EMAIL, "me@abc.com"); 3. it.putExtra(Intent.EXTRA_TEXT, "The email body text"); 4. it.setType("text/plain"); 5. startActivity(Intent.createChooser(it, "Choose Email Client")); 1. Intent it=new Intent(Intent.ACTION_SEND); 2. String[] tos={"me@abc.com"}; 3. String[] ccs={"you@abc.com"}; 4. it.putExtra(Intent.EXTRA_EMAIL, tos); 5. it.putExtra(Intent.EXTRA_CC, ccs); 6. it.putExtra(Intent.EXTRA_TEXT, "The email body text"); 7. it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); 8. it.setType("message/rfc822"); 9. startActivity(Intent.createChooser(it, "Choose Email Client")); 1. //传送附件 2. Intent it = new Intent(Intent.ACTION_SEND); 3. it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text"); 4. it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/mysong.mp3"); 5. sendIntent.setType("audio/mp3"); 6. startActivity(Intent.createChooser(it, "Choose Email Client")); 播放多媒体 Uri uri = Uri.parse("file:///sdcard/song.mp3"); Intent it = new Intent(Intent.ACTION_VIEW, uri); it.setType("audio/mp3"); startActivity(it); Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1"); Intent it = new Intent(Intent.ACTION_VIEW, uri); startActivity(it); Market 相关 1. //寻找某个应用 2. Uri uri = Uri.parse("market://search?q=pname:pkg_name"); 3. Intent it = new Intent(Intent.ACTION_VIEW, uri); 4. startActivity(it); 5. //where pkg_name is the full package path for an application 1. //显示某个应用的相关信息 2. Uri uri = Uri.parse("market://details?id=app_id"); 3. Intent it = new Intent(Intent.ACTION_VIEW, uri); 4. startActivity(it); 5. //where app_id is the application ID, find the ID 6. //by clicking on your application on Market home 7. //page, and notice the ID from the address bar Uninstall 应用程序 1. Uri uri = Uri.fromParts("package", strPackageName, null); 2. Intent it = new Intent(Intent.ACTION_DELETE, uri); 3. startActivity(it); service service是没有界面的长生命周期的代码。一个很好的例子是媒体播放器从列表中播放歌曲。在一个媒体播放器程序中,大概要有一个或多个活动(activity)来供用户选择歌曲并播放它。然而,音乐的回放就不能使用活动(activity)了,因为用户希望他导航到其他界面时音乐继续播放。这种情况下,媒体播放器活动(activity)要用Context.startService()启动一个服务来在后台运行保持音乐的播放。系统将保持这个音乐回放服务的运行直到它结束。注意一下,你要用Context.bindService()方法连接服务(如果它没有运行,要先启动它)。当连接到服务后,你可以通过服务暴露的一个接口和它通信。对于音乐服务,它允许你暂停、倒带,等等。 Broadcast和BroadcaseReceiver Android BroadcastReceiver 简介 在 Android 中使用 Activity, Service, Broadcast, BroadcastReceiver 活动(Activity) - 用于表现功能 服务(Service) - 相当于后台运行的 Activity 广播(Broadcast) - 用于发送广播 广播接收器(BroadcastReceiver) - 用于接收广播 Intent - 用于连接以上各个组件,并在其间传递消息 BroadcastReceiver 在Android中,Broadcast是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver是对发送出来的 Broadcast进行过滤接受并响应的一类组件。下面将详细的阐述如何发送Broadcast和使用BroadcastReceiver过 滤接收的过程: 首先在需要发送信息的地方,把要发送的信息和用于过滤的信息(如Action、Category)装入一个Intent对象,然后通过调用 Context.sendBroadcast()、sendOrderBroadcast()或sendStickyBroadcast()方法,把 Intent对象以广播方式发送出去。 当Intent发送以后,所有已经注册的BroadcastReceiver会检查注册时的IntentFilter是否与发送的Intent相匹配,若 匹配则就会调用BroadcastReceiver的onReceive()方法。所以当我们定义一个BroadcastReceiver的时候,都需要 实现onReceive()方法。 注册BroadcastReceiver有两种方式: 一种方式是,静态的在AndroidManifest.xml中用<receiver>标签生命注册,并在标签内用<intent- filter>标签设置过滤器。 另一种方式是,动态的在代码中先定义并设置好一个 IntentFilter对象,然后在需要注册的地方调 Context.registerReceiver()方法,如果取消时就调用Context.unregisterReceiver()方法。 不管是用xml注册的还是用代码注册的,在程序退出的时候没有特殊需要都得注销,否则下次启动程序可能会有多个 BroadcastReceiver 另外,若在使用sendBroadcast()的方法是指定了接收权限,则只有在AndroidManifest.xml中用<uses- permission>标签声明了拥有此权限的BroascastReceiver才会有可能接收到发送来的Broadcast。 同样,若在注册BroadcastReceiver时指定了可接收的Broadcast的权限,则只有在包内的AndroidManifest.xml中 用<uses-permission>标签声明了,拥有此权限的Context对象所发送的Broadcast才能被这个 BroadcastReceiver所接收。 动态注册: IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(String);--为 BroadcastReceiver指定action,使之用于接收同action的广播 registerReceiver(BroadcastReceiver,intentFilter); 一般:在onStart中注册,onStop中取消unregisterReceiver 发送广播消息:extends Service 指定广播目标Action:Intent Intent = new Intent(action-String) --指定了此action的receiver会接收此广播 需传递参数(可选) putExtra(); 发送:sendBroadcast(Intent); 引用: http://ycl248.blog.163.com/blog/static/3634280620106239617577/ http://litonggang.javaeye.com/blog/519574 http://lhc966.javaeye.com/blog/803647
2.请介绍下Android中常用的五种布局。
解答原文链接:http://blog.csdn.net/xiaopihair123/article/details/53150235
常用五种布局方式,分别是:FrameLayout(框架布局),LinearLayout (线性布局),AbsoluteLayout(绝对布局),RelativeLayout(相对布局),TableLayout(表格布局)。 LinearLayout: 线性布局,每一个LinearLayout里面又可分为垂直布局(android:orientation=”vertical”)和水平布局(android:orientation=”horizontal” )。当垂直布局时,每一行就只有一个元素,多个元素依次垂直往下;水平布局时,只有一行,每一个元素依次向右排列。 RelativeLayout: 相对布局可以理解为某一个元素为参照物,来定位的布局方式。主要属性有:相对于某一个元素android:layout_below、 android:layout_toLeftOf相对于父元素的地方android:layout_alignParentLeft、android:layout_alignParentRigh; FrameLayout: 所有东西依次都放在左上角,会重叠,这个布局比较简单,也只能放一点比较简单的东西。 TableLayout: 表格布局,每一个TableLayout里面有表格行TableRow,TableRow里面可以具体定义每一个元素。每一个布局都有自己适合的方式,这五个布局元素可以相互嵌套应用,做出美观的界面。 AbsoluteLayout: 绝对布局用X,Y坐标来指定元素的位置,这种布局方式也比较简单,但是在屏幕旋转时,往往会出问题,而且多个元素的时候,计算比较麻烦。
3.介绍一下Android系统的架构。
解答原文链接:http://blog.csdn.net/sp6645597/article/details/50472740
Android由底层往上分为4个主要功能层,分别是linux内核层(Linux Kernel),系统运行时库层(Libraries和Android Runtime),应用程序架构层(Application Framework)和应用程序层(Applications)。
4.Android的长度单位详解。
原文链接:http://blog.csdn.net/zuolongsnail/article/details/6397768
in(英寸)长度单位(基本用不到) mm(毫米)长度单位(基本用不到) pt(point磅)1/72英寸,标准的长度单位(基本用不到) px(pixel像素)指的是屏幕上的点 dp(与密度无关的像素)一种基于屏幕密度的抽象单位。在每英寸160点的显示器上,1dp=1px dip(device independent pixels设备独立像素) 不同设备有不同的显示效果,这个和设备硬件有关。 sp(scaled pixels放大像素)与dp类似,主要用于字体显示(best for textsize) dip和px的区别及其转换 区别: 在DisplayMetrics.densityDpi=160的设备上,DisplayMetrics.density是等于1.0的(通过查看DisplayMetrics的SDK源代码得知),此时1dip=1px。但是换到其他不同density的设备上,dip和px的大小就不一样了,px在任何密度的设备上几何长度都是一样的,而dip会根据设备密度变化的。所以在开发中布局时尽量使用dip而少使用px。dpi表示每英寸像素数,读者可以参考这篇文章:(hdpi,mdpi,ldpi)和WVGA,HVGA,QVGA的区别以及联系。 转换: dip和px之间的转换很简单,下面是dip和px的工具类。 [java] view plain copy /** * 密度常量设置,提供dip转换为px的方法 * * @author zuolongsnail * */ public final class DensityConst { /** 默认密度 */ public static float density = 1.0f; /** 默认每英寸像素数 */ public static int densityDpi = 160; /** * 初始化与密度相关的所有变量值 * * @param activity */ public static void initDensity(Activity activity) { DisplayMetrics dm = new DisplayMetrics(); activity.getWindowManager().getDefaultDisplay().getMetrics(dm); density = dm.density; densityDpi = dm.densityDpi; } /** * dip转化为像素 * * @param dip * @return */ public static int getPx(int dip) { return (int) (dip * density); // return (int)(dip*(densityDpi/160)); } /** * 像素转化为dip * * @param px * @return */ public static int getDip(int px) { return (int) (px / density); // return (int)((px*160)/densityDpi); } }
5.Android常用控件的信息(越详细越好)。
原文链接:https://www.cnblogs.com/linjiqin/category/284058.html
6.横竖屏切换时activity的生命周期。
原文链接:http://blog.csdn.net/hzw19920329/article/details/51345971
前面我们分析过Activity的生命周期(见:android-----Activity生命周期),但是在现实应用中我们可能会对手机进行横竖屏切换,那么这样的切换对Activity的生命周期有什么影响呢? 先来看看实例: [java] view plain copy public class Activity1 extends Activity{ public Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_lifecycle); System.out.println("Activity1----->onCreate"); } @Override protected void onDestroy() { super.onDestroy(); System.out.println("Activity1----->onDestroy"); } @Override protected void onPause() { super.onPause(); System.out.println("Activity1----->onPause"); } @Override protected void onRestart() { super.onRestart(); System.out.println("Activity1----->onRestart"); } @Override protected void onResume() { super.onResume(); System.out.println("Activity1----->onResume"); } @Override protected void onStart() { super.onStart(); System.out.println("Activity1----->onStart"); } @Override protected void onStop() { super.onStop(); System.out.println("Activity1----->onStop"); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); System.out.println("Activity1----->onRestoreInstanceState"); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); System.out.println("Activity1----->onSaveInstanceState"); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if(newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { System.out.println("现在是横屏转竖屏"); }else if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { System.out.println("现在是竖屏转横屏"); } } } 横竖屏切换涉及到的是Activity的android:configChanges属性,而与其android:launchMode属性没有关系; android:configChanges可以设置的属性值有: (1)orientation:消除横竖屏的影响 (2)keyboardHidden:消除键盘的影响 (3)screenSize:消除屏幕大小的影响 当我们设置Activity的android:configChanges属性为orientation或者orientation|keyboardHidden或者不设置这个属性的时候,Logcat输出如下: 刚刚启动Activity的时候: Activity1----->onCreate Activity1----->onStart Activity1----->onResume 模拟器中按下Ctrl+F11或者Ctrl+F12由竖屏切换到横屏: Activity1----->onPause Activity1----->onSaveInstanceState Activity1----->onStop Activity1----->onDestroy Activity1----->onCreate Activity1----->onStart Activity1----->onRestoreInstanceState Activity1----->onResume 模拟器中按下Ctrl+F11或者Ctrl+F12由横屏切换到竖屏: Activity1----->onPause Activity1----->onSaveInstanceState Activity1----->onStop Activity1----->onDestroy Activity1----->onCreate Activity1----->onStart Activity1----->onRestoreInstanceState Activity1----->onResume 当我们设置Activity的android:configChanges属性为orientation|screenSize或者orientation|screenSize|keyboardHidden,Logcat输出如下: 刚刚启动Activity的时候: Activity1----->onCreate Activity1----->onStart Activity1----->onResume 模拟器中按下Ctrl+F11或者Ctrl+F12由竖屏切换到横屏: 现在是竖屏转横屏 模拟器中按下Ctrl+F11或者Ctrl+F12由横屏切换到竖屏: 现在是横屏转竖屏 可以发现在设置了orientation|screenSize属性之后,在进行横竖屏切换的时候调用的方法是onConfigurationChanged,而不会重新加载Activity的各个生命周期; 还有一点需要注意的是在进行横竖屏切换的时候在调用onStop之前会调用onSaveInstanceState来进行Activity的状态保存,随后在重新显示该Activity的onResume方法之前会调用onRestoreInstanceState来恢复之前由onSaveInstanceState保存的Activity信息; 当然在显示中我们可以屏蔽掉横竖屏的切换操作,这样就不会出现切换的过程中Activity生命周期重新加载的情况了,具体做法是,在Activity中加入如下语句: android:screenOrientation="portrait" 始终以竖屏显示 android:screenOrientation="landscape" 始终以横屏显示 如果不想设置整个软件屏蔽横竖屏切换,只想设置屏蔽某个Activity的横竖屏切换功能的话,只需要下面操作: Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);以竖屏显示 Activity.this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);以横屏显示
7.Android中的动画有哪几类,它们的特点和区别是什么?
原文链接:https://www.zhihu.com/question/19703349
补间动画和帧动画。
所谓补间动画,是指通过指定View的初末状态和变化时间、方式,对View的内容完成一系列的图形变换来实现动画效果。主要包括四种效果:Alpha、Scale、Translate和Rotate。
帧动画就是Frame动画,即指定每一帧的内容和停留时间,然后播放动画。
8.handler机制的原理?
原文链接:http://blog.csdn.net/u012827296/article/details/51236614
Handler的运行机制 Handler的作用: 当我们需要在子线程处理耗时的操作(例如访问网络,数据库的操作),而当耗时的操作完成后,需要更新UI,这就需要使用Handler来处理,因为子线程不能做更新UI的操作。Handler能帮我们很容易的把任务(在子线程处理)切换回它所在的线程。简单理解,Handler就是解决线程和线程之间的通信的。 Handler的使用 使用的handler的两种形式: 1.在主线程使用handler; 2.在子线程使用handler。 在主线程使用handler的示例: public class TestHandlerActivity extends AppCompatActivity { private static final String TAG = "TestHandlerActivity"; private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //获得刚才发送的Message对象,然后在这里进行UI操作 Log.e(TAG,"------------> msg.what = " + msg.what); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_test); initData(); } private void initData() { //开启一个线程模拟处理耗时的操作 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(2000); //通过Handler发送一个消息切换回主线程(mHandler所在的线程) mHandler.sendEmptyMessage(0); } }).start(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 这里写图片描述 在主线程使用handler很简单,只需在主线程创建一个handler对象,在子线程通过在主线程创建的handler对象发送Message,在handleMessage()方法中接受这个Message对象进行处理。通过handler很容易的从子线程切换回主线程了。 那么来看看在子线程中使用是否也是如此。 public class TestHandlerActivity extends AppCompatActivity { private static final String TAG = "TestHandlerActivity"; //主线程中的handler private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //获得刚才发送的Message对象,然后在这里进行UI操作 Log.e(TAG,"------------> msg.what = " + msg.what); } }; //子线程中的handler private Handler mHandlerThread = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_test); initData(); } private void initData() { //开启一个线程模拟处理耗时的操作 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(2000); //通过Handler发送一个消息切换回主线程(mHandler所在的线程) mHandler.sendEmptyMessage(0); //在子线程中创建Handler mHandlerThread = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e("sub thread","---------> msg.what = " + msg.what); } }; mHandlerThread.sendEmptyMessage(1); } }).start(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 这里写图片描述 程序崩溃了。报的错误是没有在子线程调用Looper.prepare()的方法。而为什么在主线程中使用不会报错?通过源码的分析可以解析这个问题。 在子线程中正确的使用Handler应该是这样的。 public class TestHandlerActivity extends AppCompatActivity { private static final String TAG = "TestHandlerActivity"; //主线程的Handler private Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); //获得刚才发送的Message对象,然后在这里进行UI操作 Log.e(TAG,"------------> msg.what = " + msg.what); } }; //子线程中的Handler private Handler mHandlerThread = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_test); initData(); } private void initData() { //开启一个线程模拟处理耗时的操作 new Thread(new Runnable() { @Override public void run() { SystemClock.sleep(2000); //通过Handler发送一个消息切换回主线程(mHandler所在的线程) mHandler.sendEmptyMessage(0); //调用Looper.prepare()方法 Looper.prepare(); mHandlerThread = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Log.e("sub thread","---------> msg.what = " + msg.what); } }; mHandlerThread.sendEmptyMessage(1); //调用Looper.loop()方法 Looper.loop(); } }).start(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 这里写图片描述 可以看到,通过调用Looper.prepare()运行正常,handleMessage方法中就可以接收到发送的Message。 至于为什么要调用这个方法呢?去看看源码。 Handler的源码分析 Handler的消息处理主要有五个部分组成,Message,Handler,Message Queue,Looper和ThreadLocal。首先简要的了解这些对象的概念 Message:Message是在线程之间传递的消息,它可以在内部携带少量的数据,用于线程之间交换数据。Message有四个常用的字段,what字段,arg1字段,arg2字段,obj字段。what,arg1,arg2可以携带整型数据,obj可以携带object对象。 Handler:它主要用于发送和处理消息的发送消息一般使用sendMessage()方法,还有其他的一系列sendXXX的方法,但最终都是调用了sendMessageAtTime方法,除了sendMessageAtFrontOfQueue()这个方法 而发出的消息经过一系列的辗转处理后,最终会传递到Handler的handleMessage方法中。 Message Queue:MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分的消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。 Looper:每个线程通过Handler发送的消息都保存在,MessageQueue中,Looper通过调用loop()的方法,就会进入到一个无限循环当中,然后每当发现Message Queue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中只会有一个Looper对象。 ThreadLocal:MessageQueue对象,和Looper对象在每个线程中都只会有一个对象,怎么能保证它只有一个对象,就通过ThreadLocal来保存。Thread Local是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储到数据,对于其他线程来说则无法获取到数据。 了解了这些基本概念后,我们深入源码来了解Handler的工作机制。 MessageQueue的工作原理 MessageQueue消息队列是通过一个单链表的数据结构来维护消息列表的。下面主要看enqueueMessage方法和next()方法。如下: boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 可以看出,在这个方法里主要是根据时间的顺序向单链表中插入一条消息。 next()方法。如下 Message next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. final long ptr = mPtr; if (ptr == 0) { return null; } int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { // Try to retrieve the next message. Return if found. final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextPollTimeoutMillis = 0; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 在next方法是一个无限循环的方法,如果有消息返回这条消息并从链表中移除,而没有消息则一直阻塞在这里。 Looper的工作原理 每个程序都有一个入口,而Android程序是基于java的,java的程序入口是静态的main函数,因此Android程序的入口也应该为静态的main函数,在android程序中这个静态的main在ActivityThread类中。我们来看一下这个main方法,如下: public static void main(String[] args) { SamplingProfilerIntegration.start(); // CloseGuard defaults to true and can be quite spammy. We // disable it here, but selectively enable it later (via // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); Environment.initForCurrentUser(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); Security.addProvider(new AndroidKeyStoreProvider()); // Make sure TrustedCertificateStore looks in the right place for CA certificates final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); TrustedCertificateStore.setDefaultUserDirectory(configDir); Process.setArgV0("<pre-initialized>"); //###### Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 在main方法中系统调用了 Looper.prepareMainLooper();来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。来看看Looper.prepareMainLooper()是怎么创建出这两个对象的。如下: public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } } 1 2 3 4 5 6 7 8 9 可以看到,在这个方法中调用了 prepare(false);方法和 myLooper();方法,我在进入这个两个方法中,如下: private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } 1 2 3 4 5 6 在这里可以看出,sThreadLocal对象保存了一个Looper对象,首先判断是否已经存在Looper对象了,以防止被调用两次。sThreadLocal对象是ThreadLocal类型,因此保证了每个线程中只有一个Looper对象。Looper对象是什么创建的,我们进入看看,如下: private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 1 2 3 4 可以看出,这里在Looper构造函数中创建出了一个MessageQueue对象和保存了当前线程。从上面可以看出一个线程中只有一个Looper对象,而Message Queue对象是在Looper构造函数创建出来的,因此每一个线程也只会有一个MessageQueue对象。 对prepare方法还有一个重载的方法:如下 public static void prepare() { prepare(true); } 1 2 3 prepare()仅仅是对prepare(boolean quitAllowed) 的封装而已,在这里就很好解释了在主线程为什么不用调用Looper.prepare()方法了。因为在主线程启动的时候系统已经帮我们自动调用了Looper.prepare()方法。 在Looper.prepareMainLooper()方法中还调用了一个方法myLooper(),我们进去看看,如下: /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); } 1 2 3 4 5 6 7 在调用prepare()方法中在当前线程保存一个Looper对象sThreadLocal.set(new Looper(quitAllowed));my Looper()方法就是取出当前线程的Looper对象,保存在sMainLooper引用中。 在main()方法中还调用了Looper.loop()方法,如下: public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 在这个方法里,进入一个无限循环,不断的从MessageQueue的next方法获取消息,而next方法是一个阻塞操作,当没有消息的时候一直在阻塞,当有消息通过 msg.target.dispatchMessage(msg);这里的msg.target其实就是发送给这条消息的Handler对象。 Handler的运行机制 看看Handler的构造方法。如下: public Handler(Callback callback) { this(callback, false); } public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 1 2 3 4 5 6 7 8 9 10 11 12 我们去看看没有Looper 对象的构造方法: public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 可以看到,到looper对象为null,抛出 “Can’t create handler inside thread that has not called Looper.prepare()”异常由这里可以知道,当我们在子线程使用Handler的时候要手动调用Looper.prepare()创建一个Looper对象,之所以主线程不用,是系统启动的时候帮我们自动调用了Looper.prepare()方法。 handler的工作主要包含发送和接收过程。消息的发送主要通过post和send的一系列方法,而post的一系列方法是最终是通过send的一系列方法来实现的。而send的一系列方法最终是通过sendMessageAtTime方法来实现的,除了sendMessageAtFrontOfQueue()这个方法。去看看这些一系列send的方法,如下: public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageAtTime(msg, uptimeMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); } public final boolean sendMessageAtFrontOfQueue(Message msg) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, 0); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 可以看出,handler发送一条消息其实就是在消息队列插入一条消息。在Looper的loop方法中,从Message Queue中取出消息调msg.target.dispatchMessage(msg);这里其实就是调用了Handler的dispatchMessage(msg)方法,进去看看,如下: /** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 判断msg.callback是否为空,不为空调用 handleCallback(msg);来处理消息。其实callback是一个Runnable对象,就是Handler发送post消息传过来的对象。 public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public final boolean postAtTime(Runnable r, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r), uptimeMillis); } public final boolean postAtTime(Runnable r, Object token, long uptimeMillis) { return sendMessageAtTime(getPostMessage(r, token), uptimeMillis); } public final boolean postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } public final boolean postAtFrontOfQueue(Runnable r) { return sendMessageAtFrontOfQueue(getPostMessage(r)); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 进去handleCallback方法看看怎么处理消息的,如下: private static void handleCallback(Message message) { message.callback.run(); } 1 2 3 4 可以看出,其实就是回调Runnable对象的run方法。Activity的runOnUiThread,View的postDelayed方法也是同样的原理,我们先看看runOnUiThread方法,如下: public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } } 1 2 3 4 5 6 7 View的postDelayed方法。如下: public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } // Assume that post will succeed later ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); return true; } 1 2 3 4 5 6 7 8 9 实质上都是在UI线程中执行了Runnable的run方法。 如果msg.callback是否为null,判断mCallback是否为null?mCallback是一个接口,如下: /** * Callback interface you can use when instantiating a Handler to avoid * having to implement your own subclass of Handler. * * @param msg A {@link android.os.Message Message} object * @return True if no further handling is desired */ public interface Callback { public boolean handleMessage(Message msg); } 1 2 3 4 5 6 7 8 9 10 CallBack其实提供了另一种使用Handler的方式,可以派生子类重写handleMessage()方法,也可以通过设置CallBack来实现。 我们梳理一下我们在主线程使用Handler的过程。 首先在主线程创建一个Handler对象 ,并重写handleMessage()方法。然后当在子线程中需要进行更新UI的操作,我们就创建一个Message对象,并通过handler发送这条消息出去。之后这条消息被加入到MessageQueue队列中等待被处理,通过Looper对象会一直尝试从Message Queue中取出待处理的消息,最后分发会Handler的handler Message()方法中。 这里写图片描述 END…
9.什么是嵌入式实时操作系统,Android操作系统属于实时操作系统吗?
原文链接:http://blog.csdn.net/aj758461601/article/details/24361203
嵌入式实时操作系统其主要特点如下:
●实时性。由于对嵌人式实时操作系统的共同要求是系统能快速响应事件,具有较强的实时性,所以嵌入式实时操作系统的内核都是可抢占的。
●可裁剪性。嵌入式操作系统运行的硬件平台多种多样,其宿主对象更是五花八门,所以要求嵌入式操作系统中提供的各个功能模块可以让用户根据需要选择使用,即要求它具有良好的可裁剪性。
●高可靠性。嵌入式系统广泛应用于军事武器、航空航天、交通运输、重要的生产设各领域,所以要求嵌人式操作系统必须有极高的可靠性,对关键、要害的应用还要提供必要的容错和防错措施,以进一步提高系统的可靠性。
●易移植性。为了适应多种多样的硬件平台,嵌人式操作系统应可在不做大量修改的情况下能稳定地运行于不同的平台。
Android 操作系统用的内核是linux,而linux内核不属于实时操作系统范畴。 所以Android不是实时操作系统。
10.Android中线程与线程,进程与进程之间如何通信?
原文链接:http://blog.csdn.net/xunmeng_93/article/details/72821012
进程间通讯: 1.Bundle/Intent传递数据: 可传递基本类型,String,实现了Serializable或Parcellable接口的数据结构。Serializable是Java的序列化方法,Parcellable是Android的序列化方法,前者代码量少(仅一句),但I/O开销较大,一般用于输出到磁盘或网卡;后者实现代码多,效率高,一般用户内存间序列化和反序列化传输。 2.文件共享: 对同一个文件先后写读,从而实现传输,Linux机制下,可以对文件并发写,所以要注意同步。顺便一提,Windows下不支持并发读或写。 3.Messenger: Messenger是基于AIDL实现的,服务端(被动方)提供一个Service来处理客户端(主动方)连接,维护一个Handler来创建Messenger,在onBind时返回Messenger的binder。 双方用Messenger来发送数据,用Handler来处理数据。Messenger处理数据依靠Handler,所以是串行的,也就是说,Handler接到多个message时,就要排队依次处理。 4.AIDL: AIDL通过定义服务端暴露的接口,以提供给客户端来调用,AIDL使服务器可以并行处理,而Messenger封装了AIDL之后只能串行运行,所以Messenger一般用作消息传递。 通过编写aidl文件来设计想要暴露的接口,编译后会自动生成响应的java文件,服务器将接口的具体实现写在Stub中,用iBinder对象传递给客户端,客户端bindService的时候,用asInterface的形式将iBinder还原成接口,再调用其中的方法。 5.ContentProvider: 系统四大组件之一,底层也是Binder实现,主要用来为其他APP提供数据,可以说天生就是为进程通信而生的。自己实现一个ContentProvider需要实现6个方法,其中onCreate是主线程中回调的,其他方法是运行在Binder之中的。自定义的ContentProvider注册时要提供authorities属性,应用需要访问的时候将属性包装成Uri.parse(“content://authorities”)。还可以设置permission,readPermission,writePermission来设置权限。 ContentProvider有query,delete,insert等方法,看起来貌似是一个数据库管理类,但其实可以用文件,内存数据等等一切来充当数据源,query返回的是一个Cursor,可以自定义继承AbstractCursor的类来实现。 6.Socket: 学过计算机网络的对Socket不陌生,所以不需要详细讲述。只需要注意,Android不允许在主线程中请求网络,而且请求网络必须要注意声明相应的permission。然后,在服务器中定义ServerSocket来监听端口,客户端使用Socket来请求端口,连通后就可以进行通信。 7.广播(Broadcast) 广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。 在应用程序中发送广播比较简单。只需要调用sendBroadcast方法即可。该方法需要一个Intent对象。通过Intent对象可以发送需要广播的数据。 线程间通讯方式: 1. runOnUiThread(Runnable) 2. View.postDelay(Runnable , long)/new Handler().postDelayed(Runnable) 3. Message/Handler 4. AsyncTask
11.SAX、DOM、PULL解析xml的原理,以及各自优缺点?
原文链接:http://blog.csdn.net/seu_calvin/article/details/52027484
1. DOM(Document Object Model)文档对象模型 (1)DOM是W3C指定的一套规范标准,DOM的核心是按树形结构处理数据,DOM解析器读入XML文件并在内存中建立一个结构一模一样的树,可以指定要访问的元素,进行随机访问,随意修改XML文件。尤其是向前处理时非常容易。 (2)DOM是基于内存的,不管文件有多大,都会将所有的内容预先装载到内存中。从而消耗很大的内存空间。 2. SAX(Simple API for XML)用于XML的简单API (1)SAX是基于事件驱动的,以类似于流的形式读取XML文件,也就是说读入文档的过程和解析的过程是同时进行的。因此甚至不必解析整个文档而在某个条件达到满足时停止解析,因此占用较少的内存。 SAX解析器采用了基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现指定的TAG的时候,可以激活一个回调方法,告诉该方法指定的标签已经找到。当某个事件被触发时,才获取相应的XML的部分数据。 (2)SAX只能对XML进行读取,而且不能在文件中插入数据。 (3)SAX对于已经解析过的部分,不能再重新倒回去处理。因此同DOM解析相比,SAX缺乏灵活性。 3. JDOM DOM&SAX是解析xml的底层接口。而JDOM和DOM4J则是基于底层API的更高级封装类。(DOM是通用的,而JDOM和DOM4J则是面向java语言的) (1)JDOM是基于树的,处理XML的Java API,把树加载到内存中,因此不适合处理大文档。但是和DOM主要有两方面不同。 第一JDOM是使用具体类而不使用接口。这在某些方面简化了API,但是也限制了灵活性。第二,API大量使用了Java集合类,便于Java开发者使用。 (2)JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档。它包含一些转换器将JDOM表示成SAX2事件流、DOM模型或XML文本文档。 4. DOM4J DOM4J是一个非常非常优秀的Java XML API,具有性能优异、功能强大和容易使用的特点。特别值得一提的是大名鼎鼎的Hibernate也用DOM4J来读取XML配置文件。 (1)DOM4J大量使用接口,有更复杂的API,所以DOM4J比JDOM有更大的灵活性。大量使用了Java集合类,方便Java开发人员。 (2)缺点就是API过于复杂。 5. PULL PULL技术已经被集成到Android系统中,所以在使用PULL的时候不需要额外引入到jar。 PULL提供了类似SAX的事件处理机制,但是两者最主要的区别是区别是SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,而Pull解析器允许你的代码从解析器中主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。 Android系统中和Pull方式相关的包为org.xmlpull.v1,在这个包中提供了Pull解析器的工厂类XmlPullParserFactory和Pull解析器XmlPullParser,前者实例调用newPullParser方法创建后者实例,接着后者实例就可以调用getEventType()和next()等方法依次主动提取事件,并根据提取的事件类型进行相应的逻辑处理。 6. 总结 (1)如果XML文档较大且不考虑移植性问题建议采用DOM4J; (2)如果XML文档较小则建议采用JDOM;如果需要及时处理而不需要保存数据则考虑SAX。 (3)Android中用PULL比较多,毕竟是系统集成的。
12.解析一下Android中MVC的具体实现。
原文链接:http://blog.csdn.net/emptoney/article/details/52101844
最近一直在想怎么去优化一下现有的项目的代码和框架,项目中代码的臃肿和繁多在修改需求的时候带来了非常大的不方便,在大的项目工程中,一个好的框架,好的设计模式,能减少很大的工作量,所以最近一直在深入的学习MVC和MVP设计模式,这一篇对对于MVC的介绍非常的简单明了,想学习MVC设计模式的可以看看,欢迎有心得的相互交流,相互学习。 原文地址:http://www.2cto.com/kf/201506/405766.html MVC概念 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。说了这么多,听着感觉很抽象,废话不多说,我们来看看MVC在Android开发中是怎么应用的吧! MVC for Android 在Android开发中,比较流行的开发框架模式采用的是MVC框架模式,采用MVC模式的好处是便于UI界面部分的显示和业务逻辑,数据处理分开。那么Android项目中哪些代码来充当M,V,C角色呢? M层:适合做一些业务逻辑处理,比如数据库存取操作,网络操作,复杂的算法,耗时的任务等都在model层处理。 V层:应用层中处理数据显示的部分,XML布局可以视为V层,显示Model层的数据结果。 C层:在Android中,Activity处理用户交互问题,因此可以认为Activity是控制器,Activity读取V视图层的数据(eg.读取当前EditText控件的数据),控制用户输入(eg.EditText控件数据的输入),并向Model发送数据请求(eg.发起网络请求等)。 接下来我们通过一个获取天气预报数据的小项目来解读 MVC for Android。先上一个界面图: Controller控制器 [java] view plain copy import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.***.androidmvcdemo.R; import com.***.androidmvcdemo.entity.Weather; import com.***.androidmvcdemo.entity.WeatherInfo; import com.***.androidmvcdemo.model.OnWeatherListener; import com.***.androidmvcdemo.model.WeatherModel; import com.***.androidmvcdemo.model.WeatherModelImpl; public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener { private WeatherModel weatherModel; private Dialog loadingDialog; private EditText cityNOInput; private TextView city; private TextView cityNO; private TextView temp; private TextView wd; private TextView ws; private TextView sd; private TextView wse; private TextView time; private TextView njd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); weatherModel = new WeatherModelImpl(); initView(); } /** * 初始化View */ private void initView() { cityNOInput = findView(R.id.et_city_no); city = findView(R.id.tv_city); cityNO = findView(R.id.tv_city_no); temp = findView(R.id.tv_temp); wd = findView(R.id.tv_WD); ws = findView(R.id.tv_WS); sd = findView(R.id.tv_SD); wse = findView(R.id.tv_WSE); time = findView(R.id.tv_time); njd = findView(R.id.tv_njd); findView(R.id.btn_go).setOnClickListener(this); loadingDialog = new ProgressDialog(this); loadingDialog.setTitle(加载天气中...); } /** * 显示结果 * * @param weather */ public void displayResult(Weather weather) { WeatherInfo weatherInfo = weather.getWeatherinfo(); city.setText(weatherInfo.getCity()); cityNO.setText(weatherInfo.getCityid()); temp.setText(weatherInfo.getTemp()); wd.setText(weatherInfo.getWD()); ws.setText(weatherInfo.getWS()); sd.setText(weatherInfo.getSD()); wse.setText(weatherInfo.getWSE()); time.setText(weatherInfo.getTime()); njd.setText(weatherInfo.getNjd()); } /** * 隐藏进度对话框 */ public void hideLoadingDialog() { loadingDialog.dismiss(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_go: loadingDialog.show(); weatherModel.getWeather(cityNOInput.getText().toString().trim(), this); break; } } @Override public void onSuccess(Weather weather) { hideLoadingDialog(); displayResult(weather); } @Override public void onError() { hideLoadingDialog(); Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show(); } private <t extends="" view=""> T findView(int id) { return (T) findViewById(id); } } 从上面代码可以看到,Activity持有了WeatherModel模型的对象,当用户有点击Button交互的时候,Activity作为Controller控制层读取View视图层EditTextView的数据,然后向Model模型发起数据请求,也就是调用WeatherModel对象的方法 getWeathre()方法。当Model模型处理数据结束后,通过接口OnWeatherListener通知View视图层数据处理完毕,View视图层该更新界面UI了。然后View视图层调用displayResult()方法更新UI。至此,整个MVC框架流程就在Activity中体现出来了。 Model模型 来看看WeatherModelImpl代码实现 [java] view plain copy package com.***.androidmvcdemo.model; /** * Description:请求网络数据接口 * User: *** * Date: 2015/6/3 * Time: 15:40 */ public interface WeatherModel { void getWeather(String cityNumber, OnWeatherListener listener); } ................ package com.***.androidmvcdemo.model; import com.android.volley.Response; import com.android.volley.VolleyError; import com.***.androidmvcdemo.entity.Weather; import com.***.androidmvcdemo.volley.VolleyRequest; /** * Description:从网络获取天气信息接口实现 * User: *** * Date: 2015/6/3 * Time: 15:40 */ public class WeatherModelImpl implements WeatherModel { @Override public void getWeather(String cityNumber, final OnWeatherListener listener) { /*数据层操作*/ VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, Weather.class, new Response.Listener<weather>() { @Override public void onResponse(Weather weather) { if (weather != null) { listener.onSuccess(weather); } else { listener.onError(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { listener.onError(); } }); } } 以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类。controller控制器activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI 。至此,Activity就将View视图显示和Model模型数据处理隔离开了。activity担当contronller完成了model和view之间的协调作用。 至于这里为什么不直接设计成类里面的一个getWeather()方法直接请求网络数据?你考虑下这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,你怎么解决问题?难道是修改 getWeather()方法的实现? no no no,这样修改不仅破坏了以前的代码,而且还不利于维护, 考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决着一个问题,我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel,重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式。Activity调用代码无需要任何修改。 MVC使用总结 利用MVC设计模式,使得这个天气预报小项目有了很好的可扩展和维护性,当需要改变UI显示的时候,无需修改Contronller(控制器)Activity的代码和Model(模型)WeatherModel模型中的业务逻辑代码,很好的将业务逻辑和界面显示分离。 在Android项目中,业务逻辑,数据处理等担任了Model(模型)角色,XML界面显示等担任了View(视图)角色,Activity担任了Contronller(控制器)角色。contronller(控制器)是一个中间桥梁的作用,通过接口通信来协同 View(视图)和Model(模型)工作,起到了两者之间的通信作用。 什么时候适合使用MVC设计模式?当然一个小的项目且无需频繁修改需求就不用MVC框架来设计了,那样反而觉得代码过度设计,代码臃肿。一般在大的项目中,且业务逻辑处理复杂,页面显示比较多,需要模块化设计的项目使用MVC就有足够的优势了。 4.在MVC模式中我们发现,其实控制器Activity主要是起到解耦作用,将View视图和Model模型分离,虽然Activity起到交互作用,但是找Activity中有很多关于视图UI的显示代码,因此View视图和Activity控制器并不是完全分离的,也就是说一部分View视图和Contronller控制器Activity是绑定在一个类中的。 MVC的优点: (1)耦合性低。所谓耦合性就是模块代码之间的关联程度。利用MVC框架使得View(视图)层和Model(模型)层可以很好的分离,这样就达到了解耦的目的,所以耦合性低,减少模块代码之间的相互影响。 (2)可扩展性好。由于耦合性低,添加需求,扩展代码就可以减少修改之前的代码,降低bug的出现率。 (3)模块职责划分明确。主要划分层M,V,C三个模块,利于代码的维护。
浙公网安备 33010602011771号