Android的应用组件
应用组件是 Android 应用的基本构建基块。
共有四种不同的应用组件类型。每种类型都服务于不同的目的,并且具有定义组件的创建和销毁方式的不同生命周期。
- 活动Activity
- 服务Service
- 广播接收器(Broadcast Receiver)
- 内容提供程序(Content Provider)
Activity、服务和广播接收器 — 通过名为 Intent 的异步消息进行启动。Intent 会在运行时将各个组件相互绑定(您可以将 Intent 视为从其他组件请求操作的信使),无论组件属于您的应用还是其他应用。
组件间的通信Intent
Intent是连接应用程序的三个核心组件——Activity、Service和BroadcastReceiver的桥梁。
Intent负责对应用中操作的动作、动作涉及数据及附加数据进行描述。
一个Intent对象其实就是信息的捆绑。
显式Intent与隐式Intent的区别
Intent声明启动的组件有两种方式:
-
显式Intent:通过组件名指定启动的目标组件,比如
startActivity(new Intent(A.this,B.class));
每次启动的组件只有一个; -
隐式Intent:不指定组件名,而指定Intent的Action,Data,或Category,当我们启动组件时, 会去匹配AndroidManifest.xml相关组件的
Intent-filter
,逐一匹配出满足属性的组件,当不止一个满足时, 会弹出一个让我们选择启动哪个的对话框。
Intent对象
Intent对象由组件(Componen)名称、操作(Action)、操作类别(Category)、数据(Data)、数据类型(Type)、附加信息(Extras)及标志(Flags)七部分组成。
-
组件(Component)名称
要处理该Intent的组件名称。通过
setComponent()
来设置组件名和getComponent()
读取组件名。ComponentName cn = new ComponentName(OneActivity.this, TwoActivity.class); Intent it = new Intent(); it.setComponent(cn);
-
操作(Action)
一个字符串,用于命名要采取的行动。设置该Intent会触发的操作类型,可以通过
setAction()
方法进行设置,在Android系统之中已经为用户准备好了一些表示Action操作的常量,例如:ACTION_CALL、ACTION_MAIN等。
-
操作类别(Category)
对执行操作的类别进行描述,可以通过
addCategory()
方法设置多个类别,removeCategory()
删除上次添加的类别,getCategories()
获取当前对象所包含的全部类别。
-
数据(Data)
执行动作要操作的数据,用指向数据的一个URI来表示。描述Intent所操作数据的URI及类型,可以通过
setData()
进行设置,不同的操作对应着不同的Data。
-
数据类型(Type)
指定要传送数据的MIME类型,可以直接通过
setType()
方法进行设置。 -
附加信息(Extras)
其他所有附加信息的集合,传递的是一组键值对,可以使用
putExtra()
方法进行设置,主要的功能是传递数据(Uri)所需要的一些额外的操作信息。
-
标志(Flags)
用于指示Android系统如何加载并运行一个操作,可以通过
addFlags()
方法进行增加。
常用系统Intent合集
//1.拨打电话
// 给移动客服10086拨打电话
Uri uri = Uri.parse("tel:10086");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);
//===============================================================
//2.发送短信
// 给10086发送内容为“Hello”的短信
Uri uri = Uri.parse("smsto:10086");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");
startActivity(intent);
//3.发送彩信(相当于发送带附件的短信)
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra("sms_body", "Hello");
Uri uri = Uri.parse("content://media/external/images/media/23");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.setType("image/png");
startActivity(intent);
//===============================================================
//4.打开浏览器:
// 打开百度主页
Uri uri = Uri.parse("http://www.baidu.com");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//===============================================================
//5.发送电子邮件:(阉割了Google服务的没戏!!!!)
// 给[email protected]发邮件
Uri uri = Uri.parse("mailto:[email protected]");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
// 给[email protected]发邮件发送内容为“Hello”的邮件
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, "[email protected]");
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("text/plain");
startActivity(intent);
// 给多人发邮件
Intent intent=new Intent(Intent.ACTION_SEND);
String[] tos = {"[email protected]", "[email protected]"}; // 收件人
String[] ccs = {"[email protected]", "[email protected]"}; // 抄送
String[] bccs = {"[email protected]", "[email protected]"}; // 密送
intent.putExtra(Intent.EXTRA_EMAIL, tos);
intent.putExtra(Intent.EXTRA_CC, ccs);
intent.putExtra(Intent.EXTRA_BCC, bccs);
intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
intent.putExtra(Intent.EXTRA_TEXT, "Hello");
intent.setType("message/rfc822");
startActivity(intent);
//===============================================================
//6.显示地图:
// 打开Google地图中国北京位置(北纬39.9,东经116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//===============================================================
//7.路径规划
// 路径规划:从北京某地(北纬39.9,东经116.3)到上海某地(北纬31.2,东经121.4)
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=39.9 116.3&daddr=31.2 121.4");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//===============================================================
//8.多媒体播放:
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/foo.mp3");
intent.setDataAndType(uri, "audio/mp3");
startActivity(intent);
//获取SD卡下所有音频文件,然后播放第一首=-=
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//===============================================================
//9.打开摄像头拍照:
// 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 0);
// 取出照片数据
Bundle extras = intent.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
//另一种:
//调用系统相机应用程序,并存储拍下来的照片
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
time = Calendar.getInstance().getTimeInMillis();
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment
.getExternalStorageDirectory().getAbsolutePath()+"/tucue", time + ".jpg")));
startActivityForResult(intent, ACTIVITY_GET_CAMERA_IMAGE);
//===============================================================
//10.获取并剪切图片
// 获取并剪切图片
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.putExtra("crop", "true"); // 开启剪切
intent.putExtra("aspectX", 1); // 剪切的宽高比为1:2
intent.putExtra("aspectY", 2);
intent.putExtra("outputX", 20); // 保存图片的宽和高
intent.putExtra("outputY", 40);
intent.putExtra("output", Uri.fromFile(new File("/mnt/sdcard/temp"))); // 保存路径
intent.putExtra("outputFormat", "JPEG");// 返回格式
startActivityForResult(intent, 0);
// 剪切特定图片
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setClassName("com.android.camera", "com.android.camera.CropImage");
intent.setData(Uri.fromFile(new File("/mnt/sdcard/temp")));
intent.putExtra("outputX", 1); // 剪切的宽高比为1:2
intent.putExtra("outputY", 2);
intent.putExtra("aspectX", 20); // 保存图片的宽和高
intent.putExtra("aspectY", 40);
intent.putExtra("scale", true);
intent.putExtra("noFaceDetection", true);
intent.putExtra("output", Uri.parse("file:///mnt/sdcard/temp"));
startActivityForResult(intent, 0);
//===============================================================
//11.打开Google Market
// 打开Google Market直接进入该程序的详细页面
Uri uri = Uri.parse("market://details?id=" + "com.demo.app");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//===============================================================
//12.进入手机设置界面:
// 进入无线网络设置界面(其它可以举一反三)
Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
startActivityForResult(intent, 0);
//===============================================================
//13.安装apk:
Uri installUri = Uri.fromParts("package", "xxx", null);
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);
//===============================================================
//14.卸载apk:
Uri uri = Uri.fromParts("package", strPackageName, null);
Intent it = new Intent(Intent.ACTION_DELETE, uri);
startActivity(it);
//===============================================================
//15.发送附件:
Intent it = new Intent(Intent.ACTION_SEND);
it.putExtra(Intent.EXTRA_SUBJECT, "The email subject text");
it.putExtra(Intent.EXTRA_STREAM, "file:///sdcard/eoe.mp3");
sendIntent.setType("audio/mp3");
startActivity(Intent.createChooser(it, "Choose Email Client"));
//===============================================================
//16.进入联系人页面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);
startActivity(intent);
//===============================================================
//17.查看指定联系人:
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.id);//info.id联系人ID
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(personUri);
startActivity(intent);
//===============================================================
//18.调用系统编辑添加联系人(高版本SDK有效):
Intent it = newIntent(Intent.ACTION_INSERT_OR_EDIT);
it.setType("vnd.android.cursor.item/contact");
//it.setType(Contacts.CONTENT_ITEM_TYPE);
it.putExtra("name","myName");
it.putExtra(android.provider.Contacts.Intents.Insert.COMPANY, "organization");
it.putExtra(android.provider.Contacts.Intents.Insert.EMAIL,"email");
it.putExtra(android.provider.Contacts.Intents.Insert.PHONE,"homePhone");
it.putExtra(android.provider.Contacts.Intents.Insert.SECONDARY_PHONE,"mobilePhone");
it.putExtra( android.provider.Contacts.Intents.Insert.TERTIARY_PHONE,"workPhone");
it.putExtra(android.provider.Contacts.Intents.Insert.JOB_TITLE,"title");
startActivity(it);
//===============================================================
//19.调用系统编辑添加联系人(全有效):
Intent intent = newIntent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(People.CONTENT_ITEM_TYPE);
intent.putExtra(Contacts.Intents.Insert.NAME, "My Name");
intent.putExtra(Contacts.Intents.Insert.PHONE, "+1234567890");
intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE,Contacts.PhonesColumns.TYPE_MOBILE);
intent.putExtra(Contacts.Intents.Insert.EMAIL, "[email protected]");
intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE, Contacts.ContactMethodsColumns.TYPE_WORK);
startActivity(intent);
//===============================================================
//20.打开另一程序
Intent i = new Intent();
ComponentName cn = new ComponentName("com.example.jay.test",
"com.example.jay.test.MainActivity");
i.setComponent(cn);
i.setAction("android.intent.action.MAIN");
startActivityForResult(i, RESULT_OK);
//===============================================================
//21.打开录音机
Intent mi = new Intent(Media.RECORD_SOUND_ACTION);
startActivity(mi);
//===============================================================
//22.从google搜索内容
Intent intent = new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY,"searchString")
startActivity(intent);
通过Intent回传数据的操作流程
Activity程序支持的Intent操作方法:
如果现在Receive需要在回传给Send数据的话,则就不能使用startActivity()
方法,只能通过
startActivityForResult()
方法完成了,但是如果要想接收回传数据的话,则需要Activity常量的支持:
- 操作正常状态码:
public static final int RESULT_OK
- 操作取消状态码:
public static final int RESULT_CANCELED
- 用户将自定义操作状态码:
public static final int RESULT_FIRST_USER
Intent传递简单数据:
定义全局数据
如果你想某个数据可以在任何地方都能获取到,你就可以考虑使用 Application全局对象了!
Android系统在每个程序运行的时候创建一个Application对象,而且只会创建一个,所以Application 是单例(singleton)模式的一个类,而且Application对象的生命周期是整个程序中最长的,他的生命周期等于这个程序的生命周期。如果想存储一些静态的值(固定不改变的,也可以变),如果你想使用 Application就需要自定义类实现Application类,并且告诉系统实例化的是我们自定义的Application 而非系统默认的,而这一步,就是在AndroidManifest.xml中为我们的application标签添加:name属性!
-
自定义Application类:
class MyApp extends Application { private String myState; private static MyApp instance; public static MyApp getInstance(){ return instance; } public String getState(){ return myState; } public void setState(String s){ myState = s; } @Override public void onCreate(){ onCreate(); instance = this; } }
-
AndroidManifest.xml中声明:
<application android:name=".MyApp" android:icon="@drawable/icon" android:label="@string/app_name">
-
在需要的地方调用:
class Blah extends Activity { @Override public void onCreate(Bundle b){ ... // 调用 MyApp.getInstance()来获得Application的全局对象 MyApp appState = MyApp.getInstance(); String state = appState.getState(); ... } }
Activity组件
Activity用于显示用户界面,用户通过Activity交互完成相关操作。一个App允许有多个Activity。
Activity生命周期
应用程序组件有其生命周期:由Android初始化它们,以相应Intent响应意图,直到结束,实例被销毁。
Activity被一个Activity栈管理。堆栈中保存对象的实例,在一个任务中可能存在多个同一Activity的实例。
生命周期中的五种状态:启动,运行,暂停,停止,销毁。
- 启动:Activity被压入栈顶。
- 运行:Activity可见并获得焦点,与用户进行交互。
- 暂停:Activity可见但失去焦点。
- 停止:Activity被另一个Activity完全覆盖,不可见,系统可以随时将其释放。
- 销毁:系统将Activity从内存中删除,Activity被弹出出栈。
Activity的生命周期状态转变:
Activity生命周期事件处理函数
-
onCreate(Bundle)
:- 首先创建时调用该方法。
- 执行一次性的初始化工作。
- 提供Bundle参数
- 如果Activity之前是被冻结状态,其状态由Bundle提供。
- 接受参数为null或由
onSaveInstanceState()
方法保存的状态信息。
- 其后调用
onStart()
或onRestart()
方法。
-
onStart()
:当Activity对用户即将可见时调用。 -
onResume()
:用户可以开始与活动进行交互时会调用该方法。 -
onPause()
:活动将进入后台时会运行该方法。 -
onStop()
:在一段时间内不需要某个活动时,调用该方法。 -
onRestart()
:将已处于停止状态的活动重新显示给用户。 -
onDestroy()
:销毁活动前调用该方法。如果内存不足,系统会终止进程,可能不需要调用该方法。 -
onSaveInstanceState(Bundle)
:调用该方法让活动可以保存每个实例的状态。 -
onRestoreInstanceState(Bundle)
:使用onSaveInstanceState()
方法保存的状态来重新初始化某个活动时调用该方法。
Activity生命周期中函数的调用过程
生命周期函数调用举例
有两个界面Activity A和Activity B。
1、先启动第一个界面Activity A,方法回调的次序是:
2、Activity A不关闭,跳转第二个Activity B,方法回调的次序是:
3、在点击back回到第一个界面,这时方法回调的次序是:
4、在点击Exit退出应用时,方法回调的次序是:
onSaveInstanceState(Bundle)
会在下述情形中被调用:
- 点击home键回到主页或长按后选择运行其他程序
- 按下电源键关闭屏幕
- 启动新的Activity
- 横竖屏切换时,肯定会执行,因为横竖屏切换的时候会先销毁Act,然后再重新创建
重要原则:当系统"未经你许可"时销毁了你的activity,则onSaveInstanceState
会被系统调用, 这是系统的责任,因为它必须要提供一个机会让你保存你的数据(你可以保存也可以不保存)。
Activity使用流程
用户界面状态保存
一个Activity被激活并运行,即为建立了一个Activity的实例。
Activity实例与用户交互,产生界面状态信息。如用户所选取的值,光标的位置等。
当Activity实例进入“暂停”或“停止”状态时,需要保存这些临时的状态信息。
保存状态信息的方法:SharedPreferences对象和Bundle对象。
-
SharedPreferences对象
每个Activity都有一个无名的SharedPreferences对象。其SharedPreferences对象的访问权限是私有的。
SharedPreferences提供一种基于name/value形式的键值二元组的存储方式。
SharedPreferences支持的数据类型有:String、Long、Float、Integer、Boolean。
方法:
getPreferences()
:获取SharedPreferences对象。- 使用
put...()
方法保存键-值对。例如保存字符串型的使用putString()
方法。
使用SharedPreferences对象的一般步骤:
- 获得
SharedPreferences
对象。 - 获得
SharedPreferences.Editor
对象。 - 使用
put...()
方法保存键-值对。 commit()
方法进行提交。
例,Activity的SharedPreferences对象读写代码:
Protected void saveActivityPreferences(){ SharedPreferences activityPref=getPreferences(Activity.MODE_PRIVATE); //获取Activity的匿名SharedPreferrences对象 Editor editor=activityPref.edit(); TextView textView=(TextView)findViewById(R.id.textView); //获取TextView控件对象 editor.putString(“TextValue”,textView.getText().toString()); //存储TextView控件信息 editor.commit(); }
-
Bundle对象
在Activity生命周期的方法中,使用Bundle来完成相关信息的保存和读写。
生命周期中几种状态调用的方法,其输入参数是Bundle类型的有:
onCreate()
、onSaveInstanceState()
、onRestoreInstanceState()
。在使用Bundle传递数据时,要注意,Bundle的大小是有限制的, Bundle内容大小应小于 0.5MB,如果大于这个值,是会报TransactionTooLargeException异常的!
Bundle与SharedPreferences的区别:
- SharedPreferences是简单的存储持久化的设置,它只是一些简单的键值对存储方式。它将数据保存在一个xml文件中。
- Bundle是将数据传递到另一个上下文中或保存或回复你自己状态的数据存储方式。它的数据不是持久化存储状态。
Service组件
Service是后台运行的,没有用户交互界面的“服务”。如:播放音乐;检测SD卡上文件的变化;后台数据计算,如记录用户的地理信息位置的改变;发出Notification。
Service一般由Activity启动,但不依赖于Activity 。也可以由其他的Service或者Broadcast Receiver启动。
Service生命周期
Service的进程优先级
-
如果service正在调用
onCreate()
、onStart()
或onDestory()
方法,那么用于当前service的进程则变为前台进程以避免被killed。 -
如果service已经被启动,拥有它的进程仅比可见的进程低,而比不可见的进程重要,这就意味着service一般不会被killed.
-
如果客户端已经连接到service ,那么拥有它的进程则拥有最高的优先级,可以认为该service是可见的。
-
如果service可以使用
startForeground(int, Notification)
方法来将service设置为前台状态,那么系统就认为该service是对用户可见的,并不会在内存不足时killed。
Service组件使用示例
TestService2.java:
public class TestService2 extends Service {
private final String TAG = "TestService2";
private int count;
private boolean quit;
//定义onBinder方法所返回的对象
private MyBinder binder = new MyBinder();
public class MyBinder extends Binder
{
public int getCount()
{
return count;
}
}
//必须实现的方法,绑定改Service时回调该方法
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind方法被调用!");
return binder;
}
//Service被创建时回调
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate方法被调用!");
//创建一个线程动态地修改count的值
new Thread()
{
public void run()
{
while(!quit)
{
try
{
Thread.sleep(1000);
}catch(InterruptedException e){e.printStackTrace();}
count++;
}
};
}.start();
}
//Service断开连接时回调
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind方法被调用!");
return true;
}
//Service被关闭前回调
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
Log.i(TAG, "onDestroyed方法被调用!");
}
@Override
public void onRebind(Intent intent) {
Log.i(TAG, "onRebind方法被调用!");
super.onRebind(intent);
}
}
在AndroidManifest.xml中对Service组件进行注册:
<service android:name=".TestService2" android:exported="false">
<intent-filter>
<action android:name="com.jay.example.service.TEST_SERVICE2"/>
</intent-filter>
</service>
MainActivity.java:
public class MainActivity extends Activity {
private Button btnbind;
private Button btncancel;
private Button btnstatus;
//保持所启动的Service的IBinder对象,同时定义一个ServiceConnection对象
TestService2.MyBinder binder;
private ServiceConnection conn = new ServiceConnection() {
//Activity与Service断开连接时回调该方法
@Override
public void onServiceDisconnected(ComponentName name) {
System.out.println("------Service DisConnected-------");
}
//Activity与Service连接成功时回调该方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
System.out.println("------Service Connected-------");
binder = (TestService2.MyBinder) service;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnbind = (Button) findViewById(R.id.btnbind);
btncancel = (Button) findViewById(R.id.btncancel);
btnstatus = (Button) findViewById(R.id.btnstatus);
final Intent intent = new Intent();
intent.setAction("com.jay.example.service.TEST_SERVICE2");
btnbind.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//绑定service
bindService(intent, conn, Service.BIND_AUTO_CREATE);
}
});
btncancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//解除service绑定
unbindService(conn);
}
});
btnstatus.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Service的count的值为:"
+ binder.getCount(), Toast.LENGTH_SHORT).show();
}
});
}
}
IntentService
由于
- Service不是一个单独的进程,它和它的应用程序在同一个进程中
- Service不是一个线程,这样就意味着我们应该避免在Service中进行耗时操作
所以如果把耗时线程放到Service中的onStart()方法中,很容易会引起ANR异常(Application Not Responding)。
IntentService是继承与Service并处理异步请求的一个类,在IntentService中有 一个工作线程来处理耗时操作,请求的Intent记录会加入队列
工作流程:
- 客户端通过
startService(Intent)
来启动IntentService; - 我们并不需要手动地去控制IntentService,当任务执行完后,IntentService会自动停止;
- 可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的 onHandleIntent回调方法中执行,并且每次只会执行一个工作线程,执行完一,再到二这样!
TestService3.java
public class TestService3 extends IntentService {
private final String TAG = "hehe";
//必须实现父类的构造方法
public TestService3()
{
super("TestService3");
}
//必须重写的核心方法
@Override
protected void onHandleIntent(Intent intent) {
//Intent是从Activity发过来的,携带识别参数,根据参数不同执行不同的任务
String action = intent.getExtras().getString("param");
if(action.equals("s1"))Log.i(TAG,"启动service1");
else if(action.equals("s2"))Log.i(TAG,"启动service2");
else if(action.equals("s3"))Log.i(TAG,"启动service3");
//让服务休眠2秒
try{
Thread.sleep(2000);
}catch(InterruptedException e){e.printStackTrace();}
}
//重写其他方法,用于查看方法的调用顺序
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind");
return super.onBind(intent);
}
@Override
public void onCreate() {
Log.i(TAG,"onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void setIntentRedelivery(boolean enabled) {
super.setIntentRedelivery(enabled);
Log.i(TAG,"setIntentRedelivery");
}
@Override
public void onDestroy() {
Log.i(TAG,"onDestroy");
super.onDestroy();
}
}
AndroidManifest.xml注册下Service
<service android:name=".TestService3" android:exported="false">
<intent-filter >
<action android:name="com.test.intentservice"/>
</intent-filter>
</service>
在MainActivity启动三次服务:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent it1 = new Intent("com.test.intentservice");
Bundle b1 = new Bundle();
b1.putString("param", "s1");
it1.putExtras(b1);
Intent it2 = new Intent("com.test.intentservice");
Bundle b2 = new Bundle();
b2.putString("param", "s2");
it2.putExtras(b2);
Intent it3 = new Intent("com.test.intentservice");
Bundle b3 = new Bundle();
b3.putString("param", "s3");
it3.putExtras(b3);
//接着启动多次IntentService,每次启动,都会新建一个工作线程
//但始终只有一个IntentService实例
startService(it1);
startService(it2);
startService(it3);
}
}
BroadcastReceiver组件
BroadcastReceiver是对广播消息进行过滤并响应的控件。
两种广播类型
接收系统广播
接收广播服务的过程
- 开发BroadcastReveiver类的子类,在其中重载onReceive()方法;
- 等待接收广播:
- 我们注册的BroadcastReceiver并非一直在后台运行,一旦当事件或相关的Intent传来,就会被系统调用,处理onReceive()方法里的响应事件。
两种注册广播的方式
- 动态注册:通过调用
registerReceiver()
方法来注册。代码如:MyReceiver receiver=new MyReceiver(); //创建相关对象 IntentFilter filter=new IntentFilter(); filter.addAction(DATE_CHANGED); registerReceiver(receiver, filter); //动态注册BroadcastReceiver
- 静态注册:在AndroidManifest.xml中添加声明:
<receiver android:name=".MyReceiver"> <intent-filter> <action android:name="android.intent.action.DATE_CHANGED"/> <category android:name="android.intent.category.HOME"/> </intent-filter> </receiver>
两种注册方式比较:
- 动态注册方式特点:在代码中进行注册后,当应用程序关闭后,就不再进行监听。
- 静态注册方式的特点:在应用程序安装之后,无论该应用程序是否处于活动状态,BroadcastReceiver始终处于被监听状态。
动态注册实例(监听网络状态变化)
-
自定义一个BroadcastReceiver,在
onReceive()
方法中完成广播要处理的事务,比如使用Toast提示"网络状态发生改变"。public class MyBRReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context,"网络状态发生改变~",Toast.LENGTH_SHORT).show(); } }
-
MainActivity.java中动态注册广播:
public class MainActivity extends AppCompatActivity { MyBRReceiver myReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //核心部分代码: myReceiver = new MyBRReceiver(); IntentFilter itFilter = new IntentFilter(); itFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); registerReceiver(myReceiver, itFilter); } //别忘了将广播取消掉哦~ @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(myReceiver); } }
静态注册实例(接收开机广播)
-
自定义一个BroadcastReceiver,重写onReceive完成事务处理
public class BootCompleteReceiver extends BroadcastReceiver { private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED"; @Override public void onReceive(Context context, Intent intent) { if (ACTION_BOOT.equals(intent.getAction())) Toast.makeText(context, "开机完毕~", Toast.LENGTH_LONG).show(); } }
-
在AndroidManifest.xml中对该BroadcastReceiver进行注册,添加开机广播的intent-filter!别忘了加上android.permission.RECEIVE_BOOT_COMPLETED的权限!
<receiver android:name=".BootCompleteReceiver"> <intent-filter> <action android:name = "android.intent.cation.BOOT_COMPLETED"> </intent-filter> </receiver> <!-- 权限 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
重启下手机会发现过了一会儿,就会弹出开机完毕这个Toast。
使用广播的注意事项
不要在广播里添加过多逻辑或者进行任何耗时操作,因为在广播中是不允许开辟线程的, 当onReceiver( )方法运行较长时间(超过10秒)还没有结束的话,那么程序会报错(ANR), 广播更多的时候扮演的是一个打开其他组件的角色,比如启动Service,Notification提示, Activity等!
发送广播
发送广播的方式
-
标准广播:用sendBroadcast和sendStickyBroadcast发送广播
- 所有满足条件的BroadcastReceiver都会执行其onReceive()方法来处理响应,是对广播消息进行过滤并响应的控件。
- 当有多个满足条件的BroadcastReceiver时,不能保证其onReceive()方法的执行顺序。
-
有序广播:用sendOrderBroadcast发送
- 通过sendOrderBroadcast发送的Intent,会根据BroadcastReceiver注册时IntentFilter设置的优先级顺序来执行onReceive()方法。优先级范围为-1000~1000。
- 对于相同优先级的BroadcastReceiver,不能保证其onReceive()方法的执行顺序。
sendStickyBroadcast 与其它发送方式的不同之处:Intent在发送之后一直存在,并且在以后调用registerReceive注册相匹配的Receive时会把这个Intent直接返回给新注册的Receive。
可以调用abortBroadcast()
截断广播的继续传递。
标准广播示例
-
自定义广播子类来接收广播
public class MyBroadcastReceiver extends BroadcastReceiver { private final String ACTION_BOOT = "com.example.broadcasttest.MY_BROADCAST"; @Override public void onReceive(Context context, Intent intent) { if(ACTION_BOOT.equals(intent.getAction())) Toast.makeText(context, "收到告白啦~",Toast.LENGTH_SHORT).show(); } }
-
在AndroidManifest.xml中注册下,写上Intent-filter:
<receiver android:name=".MyBroadcastReceiver"> <intent-filter> <action android:name="com.example.broadcasttest.MY_BROADCAST"/> </intent-filter> </receiver>
-
把上面这个程序项目运行下,然后关掉,接下来我们新建一个项目, 在这个项目里完成广播发送~新建Demo2,布局就一个简单按钮,然后在MainActivity中完成广播发送:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_send = (Button) findViewById(R.id.btn_send); btn_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendBroadcast(new Intent("com.example.broadcasttest.MY_BROADCAST")); } }); } }
LocalBroadcastManager本地广播
前面写的广播都是全局广播!这意味着我们APP发出的广播,其他APP都会接收到, 或者其他APP发送的广播,我们的APP也同样会接收到,这样容易引起一些安全性的问题!而 Android中给我们提供了本地广播的机制,使用该机制发出的广播只会在APP内部传播,而且 广播接收者也只能收到本应用发出的广播!
使用流程
- 调用
LocalBroadcastManager.getInstance()
获得实例; - 调用
LocalBroadcastManager.registerReceiver()
注册广播; - 调用
LocalBroadcastManager.sendBroadcast()
发送广播; - 调用
LocalBroadcastManager.unregisterReceiver()
取消广播。
注意事项
- 本地广播无法通过静态注册方式来接受,相比起系统全局广播更加高效;
- 在广播中启动Activity的话,需要为intent加入
FLAG_ACTIVITY_NEW_TASK
的标记,不然会报错,因为需要一个栈来存放新打开的Activity; - 广播中弹出AlertDialog的话,需要设置对话框的类型为
TYPE_SYSTEM_ALERT
,不然是无法弹出的。
代码示例(别处登陆踢用户下线)
像微信一样,正在运行的微信,如果我们用别的手机再次登陆自己的账号,前面这个是会提醒账户"在别的终端登录",然后把我们打开的所有Activity都关掉,然后回到登陆页面。
-
准备一个关闭所有Activity的ActivityCollector
public class ActivityCollector { private static List<Activity> activities = new ArrayList<Activity>(); public static void addActivity(Activity activity) { activities.add(activity); } public static void removeActivity(Activity activity) { activities.remove(activity); } public static void finishAll() { for (Activity activity : activities) { if (!activity.isFinishing()) { activity.finish(); } } } }
-
先写要给简单的BaseActivity,用来继承,接着写下登陆界面!
public class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityCollector.addActivity(this); } @Override protected void onDestroy() { super.onDestroy(); ActivityCollector.removeActivity(this); } }
LoginActivity.java:
public class LoginActivity extends BaseActivity implements View.OnClickListener{ private SharedPreferences pref; private SharedPreferences.Editor editor; private EditText edit_user; private EditText edit_pawd; private Button btn_login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); pref = PreferenceManager.getDefaultSharedPreferences(this); bindViews(); } private void bindViews() { edit_user = (EditText) findViewById(R.id.edit_user); edit_pawd = (EditText) findViewById(R.id.edit_pawd); btn_login = (Button) findViewById(R.id.btn_login); btn_login.setOnClickListener(this); } @Override protected void onStart() { super.onStart(); if(!pref.getString("user","").equals("")){ edit_user.setText(pref.getString("user","")); edit_pawd.setText(pref.getString("pawd","")); } } @Override public void onClick(View v) { String user = edit_user.getText().toString(); String pawd = edit_pawd.getText().toString(); if(user.equals("123")&&pawd.equals("123")){ editor = pref.edit(); editor.putString("user", user); editor.putString("pawd", pawd); editor.commit(); Intent intent = new Intent(LoginActivity.this, MainActivity.class); startActivity(intent); Toast.makeText(LoginActivity.this,"哟,竟然蒙对了~",Toast.LENGTH_SHORT).show(); finish(); }else{ Toast.makeText(LoginActivity.this,"这么简单都输出,脑子呢?",Toast.LENGTH_SHORT).show(); } } }
-
自定义一个BroadcastReceiver,在onReceive里完成弹出对话框操作,以及启动登陆页面。
public class MyBcReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); dialogBuilder.setTitle("警告:"); dialogBuilder.setMessage("您的账号在别处登录,请重新登陆~"); dialogBuilder.setCancelable(false); dialogBuilder.setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCollector.finishAll(); Intent intent = new Intent(context, LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } }); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.getWindow().setType( WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); alertDialog.show(); } }
别忘了AndroidManifest.xml中加上系统对话框权限:
< uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
-
在MainActivity中,实例化localBroadcastManager,拿他完成相关操作,另外销毁时 注意unregisterReceiver!
public class MainActivity extends BaseActivity { private MyBcReceiver localReceiver; private LocalBroadcastManager localBroadcastManager; private IntentFilter intentFilter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); localBroadcastManager = LocalBroadcastManager.getInstance(this); //初始化广播接收者,设置过滤器 localReceiver = new MyBcReceiver(); intentFilter = new IntentFilter(); intentFilter.addAction("com.jay.mybcreceiver.LOGIN_OTHER"); localBroadcastManager.registerReceiver(localReceiver, intentFilter); Button btn_send = (Button) findViewById(R.id.btn_send); btn_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.jay.mybcreceiver.LOGIN_OTHER"); localBroadcastManager.sendBroadcast(intent); } }); } @Override protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(localReceiver); } }
ContentProvider组件
ContentProvider是实现两个程序间进行数据交换的组件。
Android程序中的数据(如:SharedPreferences、文件数据和数据库数据等)都是私有的。
当我们想允许自己的应用的数据允许别的应用进行读取操作,可以让我们的APP实现ContentProvider类,同时注册一个Uri,然后其他应用只要使用ContentResolver根据Uri就可以操作我们的APP中的数据了。
设置权限
对ContentProvider中的数据进行操作,都需要在AndroidManifest.xml文件中添加相应的权限。
例如,对手机的通讯录进行查询和修改操作,则在AndroidManifest.xml文件的<manifest>
标签内需要添加下列权限设置:
…
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
…
ContentProvider和ContentResolver方法
ContentProvider类为程序提供一组标准的抽象接口,程序通过该接口暴露自己的数据。
当外部应用程序需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 接口来完成。
ContentResolver提供的接口与ContentProvider中需要实现的接口对应。
获取ContentResolver对象的方法:getContentResolver()
例:ContenteResolver cr=getContentResolver();
Uri
在ContentProvider和ContentResolver中用到的Uri形式:
- 指定全部数据。例:
content://contacts/people/
—— 指定全部的联系人数据 - 指定某个ID的数据。例:
content://contacts/people/2
—— 指定ID为2的联系人数据
Uri类常用的操作方法
Uri的辅助操作类:ContentUris
由于所有的数据都要通过Uri进行传递,以增加操作为例,当用户执行完增加数据操作后往往需要将增加后的数据ID通过Uri进行返回,当接收到这个Uri的时候就需要从里面取出增加的ID,为了方便用户这种取出数据的操作,在Android中又提供了一个android.content.ContentUris的辅助工具类,帮助用户完成Uri的若干操作;
ContentUris类常用的操作方法
- 从指定Uri之中取出ID:public static long parseId(Uri contentUri)
- 在指定的Uri之后增加ID参数:public static Uri withAppendedId(Uri contentUri, long id)
Uri的辅助操作类:UriMatcher类
由于在使用ContentProvider类操作的时候,某一个方法都可能要传递多种Uri,例如:以query()
方法为例,有可能表示查询全部,有可能表示按ID查询,所以必须对这些传递的Uri
进行判断后才可以决定最终的操作形式,为了方便的用户的判断,专门提供了android.content.UriMatcher类,进行Uri的匹配;
UriMatcher类常用的操作方法: