Android有四大组件:Activity、Service、Broadcast Receiver、Content Provider。
Activity
概述:
Activity是android中使用比较频繁的一个组件,使用它需要在AndroidManifest中进行声明。Activity
是Android程序与用户交互的窗口,从这个角度看来,可以类比为做网站的页面,既然是页面,无疑是最耗
时也是最复杂的。
- 说起Activity,那么第一步必不可少的就是了解Activity的生命周期了。
从上图可以看到Activity的七种生命周期相关的方法从上到下依次是:
- onCreate():Activity首次创建的时候调用,在这个方法中,通常调用setContentView(int)方法,传入你所需要的UI效果的layout资源布局,findViewById(int)来检索在布局中你需要用到的窗体小部件。
- onRestart():在Actually停止后,再次被启动前调用。
- onStart():界面对用户而言可见的时候调用。
- onResume():用户获得焦点的时候。
- onPause():用户失去焦点。
- onStop():界面不可见。
- onDestory():退出应用程序的时候。
总结:
简单的说,分为三种大状态:
1. Activity刚启动的时候:Android系统会依次调用onCreate(),onStart(),onResume().
2. 暂停状态:当有另一个Activity来到最前端显示的时候, 进要进入暂停状态,要停留在暂停状态, 就需
要新展现到前端的Activity是部分遮盖当前Activity的.也就是说, 在当前的Activity之上有一个Activity,
这个Activity类似于一个小窗口,只遮住了当前Activity的一小部分。当用户从小窗口退出回到当前的Activity
的时候,当前的Activity就会从onPause()状态恢复到onResume()状态,这时Android系统会调用onResume()。
3.停止状态:如果当前新获得焦点的Activity把当前Activity完全覆盖的话, 当前Activity就进入了停
止状态。通常有两种情形:一种是用户退出程序,另一种是,当前Activity被新获得焦点的Activity完全覆盖
住了。如果是第一种情形,系统会调用onDestory()执行销毁当前的Activity,当前Activity对象会从内存中
销毁。如果是第二种情形,那么此时当前Activity会保持在栈中,如果用户把新来的Activity退出,系统会回退
到当前的Activity,就会执行onRestart(),onStart(),onResume()。
Activity启动模式
Android系统是通过任务栈来管理Activity的,当一个Activity启动时,会把它压入到该Task的堆栈中,当用户
按返回键或者结束该Activity时,它会从该Task的堆栈中弹出,Android系统默认的任务栈模式采用"后进先出"
的原则,Android中为我们定义了如下的四种加载模式。
1.standard模式:Activity默认的加载模式,当使用意图(Intent)等方法启动一个Activity的时候, 就创建一
个新的Activity, 并放入 栈的最顶端。
2.singleTop模式: 如果当前栈顶是新创建的Activity时, 就不创建新的Activity, 而是直接调用栈顶的这个
Activity,如果栈顶不是当前需要的Activity时, 就创建一个。
3.singleTask模式:如果当前系统中不存在当前需要的Activity时, 就创建一个新的Activity放入最顶端,
如果存在的话, 就把位于栈中的这个Activity之上的所有Activity全部销毁, 然后调用这个Activity并显示。
4.singleInstance模式:如果当前系统中不存在当前需要的Activity时, 就创建一个新的,如果存在所需要的
Activity时, 就把系统中存在的Activity从栈中提取出来, 然后放入栈的最顶端。
Activity需要注意的一些细节
1· 在Activity中是不适合做一些太耗时的操作的。不要在子线程中修改UI。
2. 注意onDestory()方法中一些资源的释放,比如网络请求,当Activity销毁的时候,都应当取消该次请求,
避免界面已经销毁而网络请求仍然在执行,导致下个界面请求。比如一些对象资源的显示回收,如图片等。
Service
概述: Service不像Activity那样,它不直接与用户进行交互,因此没有用户界面,能够长期在后台运行,且
比Activity具有更高的优先级,在系统资源紧张时不会轻易被Android系统终止。Service不仅可以实
现后台服务的功能,也可以用于进程间的通信(AIDL)。使用Service时,同样需要在AndroidManifest
中进行声明。
Service生命周期:
服务的开启有两种方式,第一种开启方式是startService()开始,stopSrvice()结束;另一种开始方式是,
始于bindService(),终于unbindService()。所以以不同的方式开启Service,生命周期略有不同。
Paste_Image.png
Code:
- 本地服务:
public class LocalService extends Service {
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.local_service_started;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
return LocalService.this;
}
}
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("LocalService", "Received start id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION);
// Tell the user we stopped.
Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.local_service_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, LocalServiceActivities.Controller.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
mNM.notify(NOTIFICATION, notification);
}
}
- write client code that directly accesses the running service, such as:
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Toast.makeText(Binding.this, R.string.local_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(Binding.this, R.string.local_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(Binding.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
}
- 远程服务(AIDL)
public class MessengerService extends Service {
/** For showing and hiding our notification. */
NotificationManager mNM;
/** Keeps track of all current registered clients. */
ArrayList<Messenger> mClients = new ArrayList<Messenger>();
/** Holds last value set by a client. */
int mValue = 0;
/**
* Command to the service to register a client, receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client where callbacks should be sent.
*/
static final int MSG_REGISTER_CLIENT = 1;
/**
* Command to the service to unregister a client, ot stop receiving callbacks
* from the service. The Message's replyTo field must be a Messenger of
* the client as previously given with MSG_REGISTER_CLIENT.
*/
static final int MSG_UNREGISTER_CLIENT = 2;
/**
* Command to service to set a new value. This can be sent to the
* service to supply a new value, and will be sent by the service to
* any registered clients with the new value.
*/
static final int MSG_SET_VALUE = 3;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REGISTER_CLIENT:
mClients.add(msg.replyTo);
break;
case MSG_UNREGISTER_CLIENT:
mClients.remove(msg.replyTo);
break;
case MSG_SET_VALUE:
mValue = msg.arg1;
for (int i=mClients.size()-1; i>=0; i--) {
try {
mClients.get(i).send(Message.obtain(null,
MSG_SET_VALUE, mValue, 0));
} catch (RemoteException e) {
// The client is dead. Remove it from the list;
// we are going through the list from back to front
// so this is safe to do inside the loop.
mClients.remove(i);
}
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
>
@Override
public void onCreate() {
mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting.
showNotification();
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
mNM.cancel(R.string.remote_service_started);
// Tell the user we stopped.
Toast.makeText(this, R.string.remote_service_stopped, Toast.LENGTH_SHORT).show();
}
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.remote_service_started);
>
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, Controller.class), 0);
>
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.stat_sample) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.local_service_label)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
// We use a string id because it is a unique number. We use it later to cancel.
mNM.notify(R.string.remote_service_started, notification);
}
}
- clients can now bind to the service and send messages to it. Note that this allows clients to register with it to receive messages back as well
/** Messenger for communicating with service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mIsBound;
/** Some text view we are using to show state information. */
TextView mCallbackText;
/**
* Handler of incoming messages from service.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MessengerService.MSG_SET_VALUE:
mCallbackText.setText("Received from service: " + msg.arg1);
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. We are communicating with our
// service through an IDL interface, so get a client-side
// representation of that from the raw service object.
mService = new Messenger(service);
mCallbackText.setText("Attached.");
// We want to monitor the service for as long as we are
// connected to it.
try {
Message msg = Message.obtain(null,
MessengerService.MSG_REGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
// Give it some value as an example.
msg = Message.obtain(null,
MessengerService.MSG_SET_VALUE, this.hashCode(), 0);
mService.send(msg);
} catch (RemoteException e) {
// In this case the service has crashed before we could even
// do anything with it; we can count on soon being
// disconnected (and then reconnected if it can be restarted)
// so there is no need to do anything here.
}
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mCallbackText.setText("Disconnected.");
// As part of the sample, tell the user what happened.
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because there is no reason to be able to let other
// applications replace our component.
bindService(new Intent(Binding.this,
MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mCallbackText.setText("Binding.");
}
void doUnbindService() {
if (mIsBound) {
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
if (mService != null) {
try {
Message msg = Message.obtain(null,
MessengerService.MSG_UNREGISTER_CLIENT);
msg.replyTo = mMessenger;
mService.send(msg);
} catch (RemoteException e) {
// There is nothing special we need to do if the service
// has crashed.
}
}
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
Broadcast Receiver
概述:Android系统中有各式各样的广播,比如电话或者短信都会产生一个广播,当应用程序运行的时候就会向
Android注册各种广播,主要的两种广播类型是:一种是非常驻型广播,此类广播会伴随应用程序的生命
周期;另一种是常驻型广播,这类广播不会随着应用程序是否关闭而存在,只要有广播来就会被系统调用
自动运行。Android系统接收到广播后,便会对广播进行判断,找出所需的事件,然后向不同的应用程序
注册事件。不同的广播可以处理不同的广播事件,也可以处理相同的广播事件,广播接受者的时候用往往
伴随着权限的声明,当然也别忘了在AndroidManifest中注册广播。
定义一个广播接受者很容易,有动态注册(非常驻型广播)还有静态注册(常驻型广播),动态注册的优先级高于静态注册的优先级。
静态注册广播接收者通常只需要两步走:
- 编写一个类继承BroadcastReceiver, 并重写其中的onReceive方法, 在该方法中编写自己的业务逻辑代码。
- 在AndroidManifest中进行注册。在Application节点下声明一个<receive>节点, 在该节点下可以添加"android:priority"来指定当前广播接收者的优先权,在<receive>节点下添加<action>子节点, 用来指定当前"接受者"所接收的广播的类型,如果想要接收的广播类型在定义的时候指定了需要的权限, 则需要在配置清单中申请该权限, 才可以接收该广播并进行操作。
eg: 以接收短信为里,在AndroidManifest中注册下。
<uses-permissoin android:name="android.permission.RECEIVE_SMS" /> <!--权限声明-->
<application
android:icon="@drawable/ic_launcher"
android:name="@string/app_name">
<receiver android:name=.MyReceiver" android:priority="2147483647">
<intent-filter>
//这里指定接收的广播类型...
//广播类型既可以是系统广播, 也可以是自己创建的广播
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver >
</application>
动态注册广播接受者:
简单的说就是通过使用ContentWrapper.registerReceive()方法来注册BroadcastReceive。
eg:发送自定义静态注册广播消息(或者系统广播)
Intent intent = new Intent();
intent.setAction("your custom receive name"); //设置Action
intent.putExtra("msg", "接收静态注册广播成功!"); //添加附加信息
sendBroadcast(intent);
eg:注册广播实现监听:
IntentFilter intentFilter = new IntentFilter();//广播过滤器
intentFilter.addAction("AndroidManifest中声明的自定义的或系统的广播名");
intentFilter.setPriority(Integer.MAX_VALUE); //设置广播优先级(动态时 -1000 至 1000,静态注册最大可达到Integer.MAX_VALUE)
registerReceiver(new MyBroadcastReciver(), intentFilter);//注册广播
Content Provider
概述:
1.Android中的ContentProvider机制可支持在多个应用中存储和读取数据,这也是跨应用共享数据的唯一
方式,常见的通讯录功能就是采用的这个机制。这些相关的信息可以在android.provider包下找到一些
Android提供的ContentProvider,通过它们提供的地址并且获取到相应的权限之后,就可以获得这些
ContentProvider,从而可以使用ContentResolver对象,通过对URI来操作数据。
2. 在Android中,如果想公开自己的数据 ,供其他的应用程序使用,那么有两种方法:一是创建自己的
ContentProvider,需要继承系统的ContentProvider类;二是如果你的数据和Android已提供的存在的
ContentProvider的数据结构一致的话,就可以将数据写到已存在的ContentProvider中,当然一般这些
操作都需要一定的权限,记得在AndroidManifest中声明所需要的权限。
URI通用资源标识符介绍
eg:
所有联系人的Uri: content://contacts/people
某个联系人的Uri: content://contacts/people/5
所有图片Uri: content://media/external
某个图片的Uri:content://media/external/images/media/4
Uri组成部分:
A:无法改变的标准前缀:如:"content://","tel://"等,当前缀是"content://"时,说明可通过
ContentProvider控制这些数据。
B:URI的标识,它通过authorities属性声明限制一个类的访问,用于限制是哪个ContentProvider能够有权
限提供这些数据。对于第三方应用程序,为了保证URI的唯一性,它必须是完整的类名。
C:路径,可以近似地理解为需要操作的数据库中表的名字。
D:如果URI中包含表示需要获取的记录的ID,则返回该ID对应的数据,如果没有ID,就表示返回全部。
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于
操作Uri的工具类,分别为UriMatcher和ContentUris。
1.UriMatcher常用的方法:
1.1 public void addURI(String authority,String path, int code): UriMatcher是一个Uri的容器
,这个容器里面包含着即将可能要操作的Uri,它用于业务逻辑的处理,path相当于Uri中上述所说的路径
,也就是想要操作的数据表名,第三个参数code,如果通过下面的match()方法匹配成功就返回这个code值。
1.2 public int match(Uri uri):与传入的Uri匹配,它会首先与找之前通过addURI方法添加进来的Uri匹配
,如果匹配成功就返回之前设置的code值,否则返回一个值-1的常量UriMatcher.NO_MATCH。
2.ContentUris常用的方法:
2.1 public static Uri withAppendedId(Uri contentUri,long id):用于为路径加上ID。
2.2 public static long parseId(Uri contentUri):用于从路径中获取ID。
ContentProvider核心类常用方法:
1.public abstract boolean onCreate(): 在ContentProvider创建后被调用。
2.public abstract Uri insert(Uri uri,ContentValues values):根据Uri插入Values对应的数据。
3.public abstract int delete(Uri uri,String selection,String[] selectionArgs):根据Uri删除
selection指定条件所匹配的全部记录。
4.public abstract int update (Uri uri, ContentValues values, String selection, String[]
selectionArgs):根据Uri修改selection指定条件所匹配的全部记录。
5.public abstract Cursor query (Uri uri, String[] projection, String selection, String[]
selectionArgs, String sortOrder):根据Uri查询出selection指定条件所匹配的全部记录,并且可以
指定查询哪些列(projection)以什么方式(sortOrder)排序。
6.public abstract String getType (Uri uri):返回当前Uri所指向数据的MIME类型,MIME类型由自己进
行定义。如果该Uri对应的数据包括多条记录,那么MIME类型的字符串就以 vnd.android.cursor.dir/开头,
如果Uri对应的数据只包含一条记录,那么MIME类型的字符串就以vnd.android.cursor.item/开头,这样系
统能够识别对应的MIME类型,在执行query()方法返回Cursor对象的时候,系统将不需要再进行验证,若返回
的字符串不能被系统识别,在执行query()方法返回Cursor对象的时候将需要再进行验证。
以Android系统的通讯录为例
1.首先需要再在AndroidManifest中添加读写通讯录的权限。
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
2.查看Android系统源码看下Android系统共享的一些数据,从模拟器中可以查看到相应的共享的信息。
3.找到通讯录,观看通讯录的表结构
Paste_Image.png
- data表:记录了用户的通讯录所有数据,包括手机号,显示名称等,但是里面的mimetype_id表示不同的数据类型。这与表mimetypes表中的_id相对应。raw_contact_id与下面的raw_contacts表中的_id相对应。
-
groups表,此表记录的是通讯录中的分组,其中account_name标识了这个分组属于那个用户。在字段中有一个group_visible标识了当前组是否可见(0表示可见,1表示隐藏)
Paste_Image.png -
mimetypes表:
-
raw_contacts表中,有一个字段是contact_id,接下来会与下面的contacts表中的_id相关联。
Paste_Image.png -
contacts表,这个表中的name_raw_contact_id就与上面的raw_contacts表中的_id相对应。
Paste_Image.png -
通讯录中显示的手机号存放在data表中,而对应的用户显示名称却是在raw_contacts中,所以当我们要去获取手机号对应的用户显示名称的时候,我们还需要去查看raw_contacts表中的值,这个中间对应的桥梁就是data表中raw_contact_id
4.操作Android系统的通讯录数据库的方法(改天写个通讯录Demo出来)。
网友评论