@king
2015-10-21T20:01:49.000000Z
字数 26071
阅读 2827
Android
Android四大基本组件分别是Activity,Service服务,Content Provider内容提供者,BroadcastReceiver广播接收器。
应用程序中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。
Activity之间通过Intent进行通信。在Intent 的描述结构中,有两个最重要的部分:动作和动作对应的数据。
典型的动作类型有:MAIN(activity的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。
Intent:一个有效的做某事的请求
Intentfilter:用于描述一个Activity(或者IntentReceiver)能够操作哪些Intent。IntentFilter 需要在AndroidManifest.xml 中定义。
Activity将会调用startActivity(Intent myIntent)
方法。然后,系统会在所有安装的应用程序中定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的activity。新的activity 接收到myIntent 的通知后,开始运行。当startActivity 方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:
- Activities 能够重复利用从其它组件中以Intent 的形式产生的一个请求;
- Activities 可以在任何时候被一个具有相同IntentFilter 的新的Activity 取代。
AndroidManifest文件中含有如下过滤器的Activity组件为默认启动类当程序启动时系统自动调用它
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个Activity或Service 来响应它们收到的信息,或者用NotificationManager 来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
广播类型:
- 普通广播,通过Context.sendBroadcast(Intent myIntent)发送的
- 有序广播,通过Context.sendOrderedBroadcast(intent, receiverPermission)发送的,该方法第2个参数决定该广播的级别,级别数值是在 -1000 到 1000 之间 , 值越大 , 发送的优先级越高;广播接收者接收广播时的级别(可通过intentfilter中的priority进行设置, 设为2147483647时优先级最高),同级别接收的先后是随机的, 再到级别低的收到广播,高级别的或同级别先接收到广播的可以通过abortBroadcast()方法截断广播使其他的接收者无法收到该广播
- 异步广播,通过Context.sendStickyBroadcast(Intent myIntent)发送的,还有sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras)方法,该方法具有有序广播的特性也有异步广播的特性;发送异步广播要: 权限,接收并处理完Intent后,广播依然存在,直到你调用removeStickyBroadcast(intent)主动把它去掉
注意:发送广播时的intent参数与Contex.startActivity()
启动起来的Intent不同,前者可以被多个订阅它的广播接收器调用,后者只能被一个(Activity或service)调用
监听广播Intent步骤:
<1>写一个继承BroadCastReceiver的类,重写onReceive()方法,广播接收器仅在它执行这个方法时处于活跃状态。当onReceive()返回后,它即为失活状态,注意:为了保证用户交互过程的流畅,一些费时的操作要放到线程里,如类名SMSBroadcastReceiver
<2>注册该广播接收者,注册有两种方法。程序动态注册和AndroidManifest文件中进行静态注册(可理解为系统中注册)如下:
静态注册,注册的广播,下面的priority表示接收广播的级别"2147483647"为最高优先级
<receiver android:name=".SMSBroadcastReceiver" >
<intent-filter android:priority = "2147483647" >
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver >
动态注册,一般在Activity可交互时onResume()内注册BroadcastReceiver
IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
registerReceiver(mBatteryInfoReceiver, intentFilter);
//反注册
unregisterReceiver(receiver);
注意:
系统常见广播Intent,如开机启动、电池电量变化、时间改变等广播
一个Service 是一段长生命周期的,没有用户界面的程序,可以用来开发如监控类程序。
比较好的一个例子就是一个正在从播放列表中播放歌曲的媒体播放器。在一个媒体播放器的应用中,应该会有多个activity,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的activity,因为使用者当然会认为在导航到其它屏幕时音乐应该还在播放的。在这个例子中,媒体播放器这个activity 会使用Context.startService()来启动一个service,从而可以在后台保持音乐的播放。同时,系统也将保持这个service 一直执行,直到这个service 运行结束。另外,我们还可以通过使用Context.bindService()方法,连接到一个service 上(如果这个service 还没有运行将启动它)。当连接到一个service 之后,我们还可以service 提供的接口与它进行通讯。拿媒体播放器这个例子来说,我们还可以进行暂停、重播等操作。
Service使用步骤如下
<1>继承service类
<2>AndroidManifast.xml配置清单文件中节点里对服务进行配置
服务不能自己运行,需要通过Contex.startService()或Contex.bindService()启动服务。
通过startService()
方法启动的服务于调用者没有关系,即使调用者关闭了,服务仍然运行。想停止服务要调用Context.stopService()
。此时系统会调用onDestory(),使用此方法启动时,服务首次启动系统先调用服务的onCreate()-->onStart(),如果服务已经启动再次调用只会触发onStart()方法。
使用bindService()启动的服务与调用者绑定,只要调用者关闭服务就终止,使用此方法启动时,服务首次启动系统先调用服务的onCreate()-->onBind(),如果服务已经启动再次调用不会再触发这2个方法,调用者退出时系统会调用服务的onUnbind()-->onDestory(),想主动解除绑定可使用Contex.unbindService()
,系统依次调用onUnbind()-->onDestory();
android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。这些数据可以存储在文件系统中、在一个SQLite数据库、或以任何其他合理的方式,其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据(相当于在应用外包了一层壳),只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。
它的好处:统一数据访问方式。
android系统自带的内容提供者(顶级的表示数据库名,非顶级的都是表名)这些内容提供者在SDK文档的android.provider Java包中都有介绍。见:http://developer.android.com/reference/android/provider/package-summary.html
├────Browser
├────CallLog
├────Contacts
│ ├────Groups
│ ├────People
│ ├────Phones
│ └────Photos
├────Images
│ └────Thumbnails
├────MediaStore
│ ├────Albums
│ ├────Artists
│ ├────Audio
│ ├────Genres
│ └────Playlists
├────Settings
└────Video
CallLog:地址和接收到的电话信息
Contact.People.Phones:存储电话号码
Setting.System:系统设置和偏好设置
使用Content Provider对外共享数据的步骤
<1>继承ContentProvider类并根据需求重写以下方法:
public boolean onCreate();//处理初始化操作
/**
* 插入数据到内容提供者(允许其他应用向你的应用中插入数据时重写)
*/
public Uri insert(Uri uri, ContentValues initialValues);
/**
* 从内容提供者中删除数据(允许其他应用删除你应用的数据时重写)
*/
public int delete(Uri uri, String selection, String[] selectionArgs);
/**
* 更新内容提供者已存在的数据(允许其他应用更新你应用的数据时重写)
*/
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
/**
* 返回数据给调用者(允许其他应用从你的应用中获取数据时重写)
* projection 列名
*/
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) ;
/**
* 用于返回当前Uri所代表数据的MIME类型
* 如果操作的数据为集合类型(多条数据),那么返回的类型字符串应该为vnd.android.cursor.dir/开头
* 例如要得到所有person记录的Uri为content://com.bravestarr.provider.personprovider/person,
* 那么返回的MIME类型字符串应该为"vnd.android.cursor.dir/person"
* 如果操作的数据为单一数据,那么返回的类型字符串应该为vnd.android.cursor.item/开头
* 例如要得到id为10的person记录的Uri为content://com.bravestarr.provider.personprovider/person/10,
* 那么返回的MIME类型字符串应该为"vnd.android.cursor.item/person"
*/
public String getType(Uri uri)
这些方法中的Uri参数,得到后需要进行解析然后做对应处理,Uri表示要操作的数据,包含两部分信息:
- 1.需要操作的contentprovider
- 2.对contentprovider中的什么数据进行操作,一个Uri格式:结构头://authorities(域名)/路径(要操作的数据,根据业务而定)
content://com.bravestarr.provider.personprovider/person/10
说明:contentprovider的结构头已经由android规定为content://
authorities用于唯一标识这个contentprovider程序,外部调用者可以根据这个找到他。
路径表示我们要操作的数据,路径的构建根据业务而定。路径格式如下:
- 要操作person表行号为10的记录,可以这样构建/person/10
- 要操作person表的所有记录,可以这样构建/person
<2>在AndroidManifest.xml中使用对ContentProvider进行配置注册(内容提供者注册它自己就像网站注册域名),ContentProvider采用authoritie(原意授权,可理解为域名)作为唯一标识,方便其他应用能找到
<android:authorities="com.bravestarr.provider.personprovider"/>
<activity> name属性指定某Activity 的子类。
<service> 用于声明服务
<receiver> 用于声明广播接收器
<provider> 用于声明内容提供者
Content Provider激活:当接收到ContentResolver 发出的请求后,Content Provider被激活。而其它三种组件──activity、服务和广播接收器被一种叫做intent 的异步消息所激活
Activity的激活通过传递一个Intent 对象至Context.startActivity()
或Activity.startActivityForResult()
以载入(或指定新工作给)一个Activity。相应的Activity 可以通过调用getIntent() 方法来查看激活它的intent。如果期望它所启动的那个activity 返回一个结果,调用startActivityForResult()
来取代startActivity()
。比如说,如果它启动了另外一个Activity 以使用户挑选一张照片,它也许想知道哪张照片被选中了。结果将会被封装在一个Intent 对象中,并传递给发出调用的activity 的onActivityResult()
方法。
服务的激活可以通过传递一个Intent 对象至Context.startService()
或Context.bindService()
。前者Android 调用onStart()
方法并将Intent 对象传递给它,后者Android 调用的onBind()
方法将这个Intent 对象传递给它
发送广播可以通过传递一个Intent 对象给Context.sendBroadcast()
、
或
Context.sendOrderedBroadcast()Context.sendStickyBroadcast()
。Android 会调用所有对此广播有兴趣的广播接收器的onReceive()
方法,将intent 传递给它们
内容提供者仅在响应ContentResolver 提出请求的时候激活,一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。
Activity关闭:可以通过调用它的finish()方法来关闭一个Activity
Service关闭:对于通过startService()方法启动的服务要调用Context.stopService()方法关闭服务,使用bindService()方法启动的服务要调用Contex.unbindService ()方法关闭服务
介绍生命周期之前,先提一下任务的概念。任务其实就是Activity 的栈,它由一个或多个Activity组成的共同完成一个完整的用户体验, 换句话说任务就是【应用程序】。栈中保存的其实是实例对象,栈中的Activity 永远不会重排,只会压入或弹出,所以如果发生了诸如需要多个地图浏览器的情况,就会使得一个任务中出现多个同一Activity 子类的实例同时存在。
任务中的所有activity 是作为一个整体进行移动的。整个的任务(即activity 栈)可以移到前台,或退至后台。
Activity栈:先进后出规则
Android 应用程序的生命周期是由Android框架进行管理,而不是由应用程序直接控制。通常,每一个应用程序(入口一般会是一个Activity 的onCreate 方法),都会产生一个进程(Process)。当系统内存即将不足的时候,会依照优先级自动进行进程(process)的回收。不管是使用者或开发者, 都无法确定的应用程序何时会被回收。
Activity整个生命周期的4种状态、7个重要方法和3个嵌套循环
活动(Active/Running)状态
Activity运行在屏幕前台(处于当前任务活动栈的最上面)
暂停(Paused)状态
Activity失去焦点但仍对用户可见(如在它之上有另一个透明的Activity或Toast、AlertDialog等弹出窗口时)。暂停的Activity仍然是存活状态(它保留着所有的状态和成员信息并保持和窗口管理器的连接),但是当系统内存极小时可以被系统杀掉
停止(Stopped)状态
完全被另一个Activity遮挡。它仍然保留着所有的状态和成员信息,只是对用户不可见。当其他地方需要内存时它往往被系统杀掉
非活动(Dead)状态
Activity 尚未被启动、已经被手动终止,或已经被系统回收时处于非活动的状态,要手动终止Activity,可以在程序中调用finish
方法。
内存不足时,Dalvak 虚拟机会根据其内存回收规则来回收内存:
- 先回收与其他Activity 或Service/Intent Receiver 无关的进程(即优先回收独立的Activity)。因此建议,一些(耗时)后台操作,最好是作成Service的形式
- 不可见(处于Stopped状态的)Activity
- Service进程(除非真的没有内存可用时会被销毁)
- 非活动的可见的(Paused状态的)Activity
- 当前正在运行(Active/Running状态的)Activity
当Activity从一种状态进入另一状态时系统会自动调用下面相应的方法来通知用户这种变化。
onCreate(Bundle savedInstanceState)
当Activity第一次被实例化的时候系统会调用此方法,整个生命周期只调用1次这个方法,通常用于初始化设置:1、为Activity设置所要使用的布局文件,2、为按钮绑定监听器等静态的设置操作
onStart()
当Activity可见、未获得用户焦点不能交互时,系统会调用此方法。
onRestart()
当Activity已经停止然后重新被启动时系统会调用此方法。
onResume()
当Activity可见且获得用户焦点能交互时系统会调用此方法。
onPause()
当系统启动另外一个新的Activity时,在新Activity启动之前系统调用此方法,保存现有的Activity中的持久数据、停止动画等,这个实现方法必须非常快。
当系统回收内存关闭了Activity之后,用户会期望返回时Activity仍保持着上次离开时的样子,可用onSaveInstanceState()
方法保存Activity被回收之前的状态,在onPause()
之前被触发。当此Activity再次被实例化时会通过onCreate(Bundle savedInstanceState)
将已经保存的临时状态数据传入。
onSaveInstanceState()
方法不总是被调用,触发条件为(按下HOME键,按下电源按键关闭屏幕,横竖屏切换情况下),你应该仅重写onSaveInstanceState()
来记录activity的临时状态。存储持久数据应使用onPause()。
onStop()
当Activity被新的Activity完全覆盖不可见时被系统调用
onDestroy()
当Activity(用户调用finish()
或系统由于内存不足)被系统销毁杀掉时系统调用,(整个生命周期只调用1次)用来释放onCreate()
方法中创建的资源,如结束线程等
举例说明:
例1:有3个Acitivity,分别用One,Two(透明的),Three(全屏)表示,One是应用启动时的主Activity
启动第一个界面Activity One时,它的次序是
onCreate (ONE) - onStart (ONE) - onResume(ONE)
点"打开透明Activity"按钮时,这时走的次序是
onPause(ONE) - onCreate(TWO) - onStart(TWO) - onResume(TWO)
再点back回到第一个界面,Two会被杀这时走的次序是
onPause(TWO) - onActivityResult(ONE) - onResume(ONE) - onStop(TWO) - onDestroy(TWO)
点"打开全屏Activity"按钮时,这时走的次序是
onPause(ONE) - onCreate(Three) - onStart(Three) - onResume(Three) - onStop(ONE)
再点back回到第一个界面,Three会被杀这时走的次序是
onPause(Three) - onActivityResult(ONE) - onRestart(ONE) - onStart(ONE)- onResume(ONE) - onStop(Three) - onDestroy(Three)
再点back退出应用时,它的次序是
onPause(ONE) - onStop(ONE) - onDestroy(ONE)
例2:横竖屏切换时候Activity的生命周期
1、新建一个Activity,并把各个生命周期打印出来
2、运行Activity,得到如下信息
onCreate-->
onStart-->
onResume-->
3、按crtl+f12切换成横屏时
onSaveInstanceState-->
onPause-->
onStop-->
onDestroy-->
onCreate-->
onStart-->
onRestoreInstanceState-->
onResume-->
4、再按crtl+f12切换成竖屏时,发现打印了两次相同的log
onSaveInstanceState-->
onPause-->
onStop-->
onDestroy-->
onCreate-->
onStart-->
onRestoreInstanceState-->
onResume-->
onSaveInstanceState-->
onPause-->
onStop-->
onDestroy-->
onCreate-->
onStart-->
onRestoreInstanceState-->
onResume-->
5、修改AndroidManifest.xml,把该Activity添加android:configChanges="orientation"
,执行步骤3
onSaveInstanceState-->
onPause-->
onStop-->
onDestroy-->
onCreate-->
onStart-->
onRestoreInstanceState-->
onResume-->
6、再执行步骤4,发现不会再打印相同信息,但多打印了一行onConfigChanged
onSaveInstanceState-->
onPause-->
onStop-->
onDestroy-->
onCreate-->
onStart-->
onRestoreInstanceState-->
onResume-->
onConfigurationChanged-->
7、把步骤5的android:configChanges="orientation"
改成 android:configChanges="orientation|keyboardHidden"
,执行步骤3,就只打印onConfigChanged
onConfigurationChanged-->
8、执行步骤4
onConfigurationChanged-->
onConfigurationChanged-->
总结:
- 不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次
- 设置Activity的android:configChanges="orientation"时,切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次
- 设置Activity的android:configChanges="orientation|keyboardHidden"时,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法
补充一点,当前Activity产生事件弹出Toast和AlertDialog的时候Activity的生命周期不会有改变
Activity运行时按下HOME键(跟被完全覆盖是一样的):onSaveInstanceState --> onPause --> onStop,再次进入激活状态时: onRestart -->onStart--->onResume
补充:onCreate(Bundle savedInstanceState)
与onSaveInstanceState(Bundle savedInstanceState)
配合使用,见如下代码,达到显示activity被系统杀死前的状态
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (null != savedInstanceState) {
String _userid = savedInstanceState.getString("StrUserId");
String _uid = savedInstanceState.getString("StrUid");
String _serverid = savedInstanceState.getString("StrServerId");
String _servername = savedInstanceState.getString("StrServerName");
int _rate = savedInstanceState.getInt("StrRate");
//updateUserId(_userid);
//updateUId(_uid);
//updateServerId(_serverid);
//updateUserServer(_servername);
//updateRate(_rate);
}
}
@Override
protected void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString("StrUserId", getUserId());
savedInstanceState.putString("StrUid", getUId());
savedInstanceState.putString("StrServerId", getServerId());
savedInstanceState.putString("StrServerName", getServerName());
savedInstanceState.putInt("StrRate", getRate());
}
引发Activity摧毁和重建的其他情形:
除了系统处于内存不足的原因会摧毁Activity之外,某些系统设置的改变也会导致Activity的摧毁和重建. 例如改变屏幕方向(见上例),改变设备语言设定, 键盘弹出等。
生命周期只有十秒左右,如果在onReceive()
内做超过十秒内的事情,就会报ANR(Application No Response) 程序无响应的错误信息。
它的生命周期为从回调onReceive()方法开始到该方法返回结果后结束。
Service完整的生命周期:从调用onCreate()开始直到调用onDestroy()结束
Service有两种使用方法:
1. 以调用Context.startService()启动,而以调用Context.stopService()结束
2. 以调用Context.bindService()方法建立,以调用Context.unbindService()关闭
onCreate()
当用户调用startService()
或bindService()
时,Service第一次被实例化的时候系统会调用,整个生命周期只调用1次这个方法,通常用于初始化设置。注意:多次调用startService()或bindService()方法不会多次触发onCreate()方法
onDestroy()
当用户调用stopService()或unbindService()来停止服务时被系统调用,(整个生命周期只调用1次)用来释放onCreate()方法中创建的资源
onStart(Intent intent)
通过startService()方法启动的服务,其初始化结束后系统会调用该方法,用于处理传递给startService()的Intent对象。如音乐服务会打开Intent来探明将要播放哪首音乐,并开始播放。注意:多次调用startService()方法会多次触发onStart()方法
IBinder onBind(Intent intent)
通过bindService()方法启动的服务,其 初始化结束后系统会调用该方法,用来绑定传递给bindService 的Intent 的对象。注意:多次调用bindService()时,如果该服务已启动则不会再触发此方法
boolean onUnbind(Intent intent)
用户调用unbindService()时系统调用此方法,Intent 对象同样传递给该方法。
void onRebind(Intent intent)
如果有新的客户端连接至该服务,只有当旧的调用onUnbind()后,新的才会调用该方法
LayoutInflater是用来找res/layout/下的xml布局文件,并且实例化。
findViewById()是找xml布局文件下的具体widget控件(如Button、TextView等)。
具体作用:
- 1、对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;
- 2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。
LayoutInflater 是一个抽象类,在文档中如下声明:
public abstract class LayoutInflater extends Object
获得 LayoutInflater 实例的三种方式
1. LayoutInflater inflater = getLayoutInflater();
//调用Activity的getLayoutInflater()
2. LayoutInflater inflater = LayoutInflater.from(context);
3. LayoutInflater inflater = (LayoutInflater)context.getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
其实,这三种方式本质是相同的,从源码中可以看出:
这三种方式最终本质是都是调用的Context.getSystemService()
。
另外getSystemService()
是Android很重要的一个API,它是Activity的一个方法,根据传入的NAME来取得对应的Object,然后转换成相应的服务对象。以下介绍系统相应的服务。
传入的Name | 返回的对象 | 说明 |
---|---|---|
WINDOW_SERVICE | WindowManager | 管理打开的窗口程序 |
LAYOUT_INFLATER_SERVICE | LayoutInflater | 取得xml里定义的view |
ACTIVITY_SERVICE | ActivityManager | 管理应用程序的系统状态 |
POWER_SERVICE | PowerManger | 电源的服务 |
ALARM_SERVICE | AlarmManager | 闹钟的服务 |
NOTIFICATION_SERVICE | NotificationManager | 状态栏的服务 |
KEYGUARD_SERVICE | KeyguardManager | 键盘锁的服务 |
LOCATION_SERVICE | LocationManager | 位置的服务,如GPS |
SEARCH_SERVICE | SearchManager | 搜索的服务 |
VEBRATOR_SERVICE | Vebrator | 手机震动的服务 |
CONNECTIVITY_SERVICE | Connectivity | 网络连接的服务 |
WIFI_SERVICE | WifiManager | Wi-Fi服务 |
TELEPHONY_SERVICE | TeleponyManager | 电话服务 |
inflate 方法
通过 sdk 的 api 文档,可以知道该方法有以下几种重载形式,返回值均是 View 对象,如下:
public View inflate (int resource, ViewGroup root)
public View inflate (XmlPullParser parser, ViewGroup root)
public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)
public View inflate (int resource, ViewGroup root, boolean attachToRoot)
示意代码:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.custom, (ViewGroup)findViewById(R.id.test));
EditText editText = (EditText)view.findViewById(R.id.content);
对于上面代码,指定了第二个参数 ViewGroup root,当然你也可以设置为 null 值。
注意:
- inflate 方法与 findViewById 方法不同;
- inflater 是用来找 res/layout 下的 xml 布局文件,并且实例化;
- findViewById() 是找具体 xml 布局文件中的具体 widget 控件(如:Button、TextView 等)。
android:divider="#0000"
:ListView的分隔线设为透明<1>获取LocationManager实例:
LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
位置提供器:
- GPS_PROVIDER
- NETWORK_PROVIDER
- PASSIVE_PROVIDER
<2>获取Location对象:
String provider = LocationManager.NETWORK_PROVIDER;
Location location = locationManager.getLastKnownLocation(provider);
这个location对象中包含了经度、纬度、海拔等信息。
eg:
需要权限<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
package com.example.locationtest;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity {
private TextView positionTextView;
private LocationManager locationManager;
private String provider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
positionTextView = (TextView)findViewById(R.id.position_text_view);
locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
// 获取所有可用的位置提供器
List<String> providerList = locationManager.getProviders(true);
if (providerList.contains(LocationManager.GPS_PROVIDER)){
provider = LocationManager.GPS_PROVIDER;
} else if (providerList.contains(LocationManager.NETWORK_PROVIDER)){
provider = LocationManager.NETWORK_PROVIDER;
} else {
// 无可用位置提供器
Toast.makeText(this, "No location provider to use", Toast.LENGTH_SHORT).show();
return;
}
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
// 显示当前设置的位置信息
showLocation(location);
}
locationManager.requestLocationUpdates(provider, 5000, 1, locationListener);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (locationManager != null){
// 关闭程序时将监听器移除
locationManager.removeUpdates(locationListener);
}
}
LocationListener locationListener = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onLocationChanged(Location location) {
// 更新当前设备的位置信息
showLocation(location);
}
};
private void showLocation(Location location){
String currentPosition = "latitude is " + location.getLatitude() + "\n"
+ "longitude is " + location.getLongitude();
positionTextView.setText(currentPosition);
}
}
Geocoding API:向Google服务器发起一条HTTP请求:
http://maps.googleapis.com/maps/api/geocode/json?latlng=30.280505,120.147231&sensor=false
json可换成xml
latlng后是经纬度
sensor通常用false
联网需要权限:
<uses-permission android:name="android.permission.INTERNET"/>
解析地理位置的简单方法:
private void showLocation(final Location location){
new Thread(new Runnable() {
@Override
public void run() {
try{
// 组装反向地理编码的接口地址
StringBuilder url = new StringBuilder();
url.append("http://maps.googleapis.com/maps/api/geocode/json?latlng=")
.append(location.getLatitude())
.append(".")
.append(location.getLongitude())
.append("&sensor=false");
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet(url.toString());
// 在消息请求头中指定语言,保证服务器会返回中文数据
httpGet.addHeader("Accept-Language", "zh-CN");
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = httpResponse.getEntity();
String response = EntityUtils.toString(entity, "utf-8");
JSONObject jsonObject = new JSONObject(response);
JSONArray resultArray = jsonObject.getJSONArray("results");
if (resultArray.length() > 0) {
JSONObject subObject = resultArray.getJSONObject(0);
// 取出格式化后的位置信息
String address = subObject.getString("formatted_address");
Message message = new Message();
message.what = SHOW_LOCATION;
message.obj = address;
handler.sendMessage(message);
}
}
} catch (Exception e){
e.printStackTrace();
}
}
}).start();
}
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case SHOW_LOCATION:
String currentPosistion = (String)msg.obj;
positionTextView.setText(currentPosistion);
break;
default:
break;
}
}
};
<1> 获取SensorManager的实例
SensorManager sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
<2>调用getDefaultSensor()得到任意传感器类型
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
上例使用了光照传感器
<3>对传感器的输出信号进行监听
创建SensorEventListener实例
SensorEventListener listener = new SensorEventListener(){
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy){
}
@Override
public void onSensorChanged(SensorEvent event){
}
使用SensorManager的registerListener()方法注册。
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
<4>传感器使用完毕时要unregistenrListener()
<1>先创建一个类继承SQLiteOpenHelper
public class KingWeatherOpenHelper extends SQLiteOpenHelper {
/**
* Province建表语句
*/
public static final String CREATE_PROVINCE = "create table Province ("
+ "id integer primary key autoincrement, "
+ "province_name text, "
+ "province_code text)";
public KingWeatherOpenHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_PROVINCE);
db.execSQL(CREATE_CITY);
db.execSQL(CREATE_COUNTY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
<2>创建数据库
KingWeatherOpenHelper dbHelper = new KingWeatherOpenHelper(context, databaseName, null, databaseVersion);
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 或者 getReadableDatabase();
……
public View getView(int position, View convertView, ViewGroup parent)
ArrayAdapter
- getView()`方法中有一个converView参数,用于将之前加载好的布局进行缓存,以便复用。
- notifyDataSetChanged():刷新ListView中的显示
View中的setTag(Object o)表示给View添加一个格外的数据,以后可以用getTag()将这个数据取出来。
- px:像素
- pt:磅数。1 pt = 1/72 英寸,一般作为字体单位
- dp:又称dip,密度无关像素。160 dpi 的屏幕上,1 dp 等于 1 px。即 px = dp*(dpi/160);
- sp:可伸缩像素。原理和dp一样,用于字体。
被特殊处理过的png图片,能够指定哪些区域可以被拉伸而哪些区域不可以。
sdk目录下tools文件夹中的draw9patch.bat文件。
上、左:拉伸区域
下、右:内容区域
ToggleButton 与 Switch 都是由Button派生出来。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- 定义一个ToggleButton按钮 -->
<ToggleButton
android:id="@+id/toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="横向排列"
android:textOn="纵向排列"
android:checked="true"
/>
<Switch
android:id="@+id/switcher"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textOff="横向排列"
android:textOn="纵向排列"
android:checked="true"
/>
<LinearLayout
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:orientation="vertical"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试按钮一"
android:textSize="16sp"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试按钮二"
android:textSize="16sp"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试按钮三"
android:textSize="16sp"
/>
</LinearLayout>
</LinearLayout>
package com.example.test;
import android.app.Activity;
import android.os.Bundle;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.ToggleButton;
public class MainActivity extends Activity {
ToggleButton toggle;
Switch switcher;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
toggle = (ToggleButton)findViewById(R.id.toggle);
switcher = (Switch)findViewById(R.id.switcher);
final LinearLayout test = (LinearLayout)findViewById(R.id.test);
OnCheckedChangeListener listener = new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
// 垂直布局
test.setOrientation(1);
} else {
// 水平布局
test.setOrientation(0);
}
}
};
toggle.setOnCheckedChangeListener(listener);
switcher.setOnCheckedChangeListener(listener);
}
}
都会显示当前时间。
数字时钟DigitalClock继承了TextView,可以显示秒数
模拟时钟AnalogClock继承了View,重写了onDraw方法
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical" >
<!-- 定义模拟时钟 -->
<AnalogClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<!-- 定义数字时钟 -->
<DigitalClock
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textColor="#f0f"
android:drawableRight="@drawable/ic_launcher"
/>
</LinearLayout>
与DigitalClock一样都继承自TextView。
区别是Chronometer显示从某个起始时间开始,一共过去了多长时间。
android:format
属性用于指定计时器的计时格式。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Chronometer
android:id="@+id/test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
/>
<Button
android:id="@+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="开始计时"
android:textSize="24sp"
/>
</LinearLayout>
public class MainActivity extends Activity {
Chronometer ch;
Button start;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ch = (Chronometer) findViewById(R.id.test);
start = (Button) findViewById(R.id.start);
start.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View source) {
// 设置开始时间
ch.setBase(SystemClock.elapsedRealtime());
// 启动计时器
ch.start();
start.setEnabled(false);
start.setText("计时中...");
}
});
ch.setOnChronometerTickListener(new OnChronometerTickListener() {
@Override
public void onChronometerTick(Chronometer chronometer) {
// 如果从开始计时到现在超过了20s
if ((SystemClock.elapsedRealtime() - ch.getBase() ) > 20 * 1000){
ch.stop();
start.setEnabled(true);
start.setText("开始计时");
}
}
});
}
}
ImageView继承自View,用于显示图片和Drawable对象,派生了ImageButton、ZoomButton等组件
Button按钮上显示文字
ImageButton上则显示图片,为它指定android:text属性没用。可以指定android:src属性,可以使用静止图片或Drawable对象。
ImageButton派出了一个ZoomButton,可以代表放大、缩小两个按钮。默认提供了btn_minus、btn_plus两个Drawable资源。
Android还提供了一个ZoomControls组件,相当于同时组合了放大、缩小两个按钮,并允许分别为两个按钮绑定不同的事件监听器。
QuickContactBadge 继承了 ImageView ,额外增加的功能是该图片可以关联到手机中指定联系人,当用户单击该图片时,系统将打开相应联系人的联系方式界面。
调用如下方法进行关联:
- assignContactFromEmail(String emailAddress, boolean lazyLookup)
- assignContactFromPhone(String phontNumber, boolean lazyLookup)
- assignContactUri(Uri contactUri)
《疯狂安卓讲义》PDF P105