Android入门之四大组件
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 mClients = new ArrayList(); /** 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节点下声明一个
节点, 在该节点下可以添加"android:priority"来指定当前广播接收者的优先权,在 节点下添加子节点, 用来指定当前"接受者"所接收的广播的类型,如果想要接收的广播类型在定义的时候指定了需要的权限, 则需要在配置清单中申请该权限, 才可以接收该广播并进行操作。
eg: 以接收短信为里,在AndroidManifest中注册下。
//这里指定接收的广播类型... //广播类型既可以是系统广播, 也可以是自己创建的广播
动态注册广播接受者:
简单的说就是通过使用ContentWrapper.registerReceive()方法来注册BroadcastReceive。
eg:发送自定义静态注册广播消息(或者系统广播)
Intent intent = new Intent();intent.setAction("your custom receive name"); //设置Actionintent.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/4Uri组成部分: 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中添加读写通讯录的权限。
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出来)。
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 一句话锁定MySQL数据占用元凶
- Android(安卓)危险权限、权限组列表和所有普通权限
- android 电话状态的监听(来电和去电)
- android Service与BroadcastReceiver
- Android(安卓)Configuration系统设置
- Android保存图片到系统相册并更新
- ListView 使用详解
- 部分 CM11 系统 Android(安卓)平板运行植物大战僵尸 2 黑屏的解