前言
众所周知,Android的四大组件包括:活动(Activity)、服务(Service)、广播接收器(Broadcast Receiver)、内容提供器(Content Provider)。本文先来介绍一下活动以及服务,广播接收器与内容提供器在下一篇文章介绍。
活动
概述
活动是用户进行操作的可视化界面,它为用户提供了一个完成指令的窗口,在App中几乎所有可见的的内容都要依托Activity,所以Activity是开发中使用最频繁的一个组件。在这我们主要来讲解一下活动间的通信、活动的生命周期、活动的启动模式。
活动间的通信
Intent通常用于启动活动、启动服务以及发送广播等场景,这里就来介绍一下启动活动。
显示Intent:
Intent intent=new Intent(this,AnotherActivity.class);
startActivity(intent);
- 向下一个活动传递数据
例如此时我们有FirstActivity和SecondActivity两个活动
在FirstActivity:
String data="你好";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);//extra_data相当于一个标识,在下一个活动我们也是通过extra_data来接收数据
startActivity(intent);
这样我们就将“你好”这个数据传递到了SecondActivity,接下来只需要在SecondActivity取出来即可
在SecondActivity:
Intent intent=getIntent();
String data=intent.getStringExtra("extra_data");
字符串类型数据使用getStringExtra()方法接收,整型数据使用getIntExtra()方法接收,布尔型数据使用getBooleanExtra()方法接收
- 返回数据给上一个活动
当我们从一个活动退出返回到上一个活动时,如果我们想传递数据给上一个活动,就可以使用到这里。
在启动活动时,我们不再使用startActivity()方法,而是使用startActivityForResult()方法,这个方法期望我们启动的活动在销毁时可以返回一个结果给上一个活动。
在FirstActivity:
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);// 设置请求码为1
在SecondActivity:
Intent intent=new Intent();
intent.putExtra("data_return","你好");
setResult(RESULT_OK,intent);
finish();
接着我们需要在FirstActivity中定义一个onActivityResult()方法来接收返回的数据:
@Override
protected void onActivityResult(int requestCode,int resultCode,Intent data){ //requestCode是我们在启动活动时传入的请求码
switch(requestCode){
case 1:
if(resultCode==RESULT_OK){
String returnData=data.getStringExtra("data_return");
}
break;
default:
}
}
另外需要注意的是,如果我们是通过点击按钮返回FirstActivity,那么按钮的点击事件就和上面SecondActivity的代码那样即可,但如果我们是通过按下Back键返回到FirstActivity,那我们就要在SecondActivity重写onBackPressed方法了
@Override
public void onBackPressed(){
Intent intent=new Intent();
intent.putExtra("data_return","你好");
setResult(RESULT_OK,intent);
finish();
}
启动活动的最佳写法
在原本的启动活动的写法中,当我们要向启动的活动传数据时,我们可能并不知道传什么类型的数据,此时我们就可以换一种启动活动的写法。
在SecondActivity中定义一个actionStart()方法:
public static void actionStart(Context context,String data1,String data2){
Intent intent=new Intent(context,SecondActivity.class);
intent.putExtra("data_1",data1);
intent.putExtra("data_2",data2);
context.startActivity(intent);
}
这样一来,我们就清楚了,要启动SecondActivity要传递两个String类型的数据,接着在FirstActivity中启动SecondActivity:
SecondActivity.actionStart(FirstActivity.this,"data1","data2");
活动的生命周期
Android使用栈来管理活动。
活动状态
- 运行状态:活动位于栈顶时。
- 暂停状态:活动不再处于栈顶但却仍然可见,如一个对话框形式的活动A出现在一个活动B上面时,对话框并没有占据整个屏幕,此时活动B就处于暂停状态。
- 停止状态:不处于栈顶且完全不可见的时候。此时系统仍会保存相应的状态和成员变量。但当其他地方需要内存时,处于停止状态的活动可能被回收。
- 销毁状态:从栈中被移除了。
活动的生存期
Activity中定义了7个回调方法,覆盖了活动声明周期的每一个环节:
- onCreate():在活动第一次创建时被调用(完成一些初始化操作,加载布局、绑定事件等)
- onStart():活动由不可见变为可见时调用
- onResume():活动准备好和用户进行交互时调用,此时活动一定处于栈顶
- onPause():在系统准备去启动或恢复另一个活动时调用。通常在这个方法中将一些消耗CPU的资源释放,保留一些关键数据。
- onStop():在活动完全不可见时调用。其与onPause()方法的区别在于当启动的活动是对话框形式的,onPause()会执行,onStop()不会执行
- onDestory():在活动被销毁前调用,调用后活动的状态将变为销毁状态
- onRestart():在活动由停止状态变为运行状态之前调用,也就是活动重新启动了。
可以将活动分为三种生存期:
- 完整生存期:onCreate()方法与onDestroy()方法之间经历的。在onCreate()完成各种初始化操作,在onDestroy()完成释放内存操作。
- 可见生存期:onStart()方法与onStop()方法间经历的。我们可以通过这两个方法对资源经行合理管理,如在onStart()对资源经行加载,在onStop()对资源经行释放。
- 前台生存期:onResume()方法和onPause()方法间经历的,此时活动总是处于运行状态,活动位于返回栈的栈顶,常在这里与用户进行交互。
活动的启动模式
在AndroidManifest.xml中的<activity></activity>标签通过android:launchMode来指定
- standard:活动默认的启动模式,在该模式下,系统不会在乎这个活动在栈中是否已经存在,每次启动该活动都会创建一个新的实例。
- singleTop:在启动这个活动时如果发现返回栈中栈顶已经是该活动了,直接去使用它,不再去新建一个活动实例。
- singleTask:在启动这个活动时每次去检查返回栈中是否存在活动的实例,存在则直接使用,不存在则创建新的活动实例。
- singleInstance:指定为该模式的活动会启动一个新的返回栈来管理这个活动。这样做可以实现其他程序和我们这个程序共享这个活动实例,不管哪个程序来访问这个活动,都公用同一个返回栈。
服务
概述
服务是实现程序后台运行的解决方案,允许用户在切换到另一个程序界面的情况下本程序依然能够正常运行,即切换到后台(例如我们听音乐时将音乐软件切到后台)。但需要注意的是,切到后台此时进程并未结束,如果进程被杀掉,依赖于该进程的服务也会停止。
服务并不会自动开启线程,所有代码都是默认在主线程运行的,但这样可能出现主线程被阻塞的情况,所以我们需要在服务内部手动创建子线程,并在这里面执行具体任务,本文后面也会讲解在子线程处理服务的耗时逻辑,所以我们需要了解一下Android多线程编程的知识,可以看看这篇博客Android多线程编程。
启动和停止服务
在此之前,我们先来创建出一个服务,右击项目包名->New->Service->Service,在弹出的窗口有两个可勾选的属性,Exported表示是否允许当前程序被其他程序访问,Enabled表示是否启用这个服务,这里我们全部勾选上。
将创建好的服务添加一些代码,如下所示:
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
//服务创建时调用
@Override
public void onCreate(){
super.onCreate();
Log.d("MyService","onCreate executed");
}
//服务启动时调用
@Override
public int onStartCommand(Intent intent,int flags,int startId){
Log.d("MyService","onStartCommand executed");
return super.onStartCommand(intent,flags,startId);
}
//服务销毁时调用
@Override
public void onDestroy(){
super.onDestroy();
Log.d("MyService","onDestroy executed");
}
}
通常情况,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法中。当服务被销毁时,在onDestroy()方法中去回收不再使用的资源。还有一点就是onCreate()只有在服务被第一次创建是才会调用,onStartCommand()在每次启动服务时都会调用。
服务的启动和停止也是借助Intent来实现的,如下所示:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button start=(Button) findViewById(R.id.start);
Button stop=(Button) findViewById(R.id.stop);
start.setOnClickListener(this);
stop.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start:
Intent startIntent=new Intent(this, MyService.class);
//启动服务
startService(startIntent);
break;
case R.id.stop:
Intent stopIntent=new Intent(this, MyService.class);
//暂停服务
stopService(stopIntent);
break;
default:
break;
}
}
}
其中startService()方法和stopService()方法都是定义在Context类中的,可以直接调用。当然我们也可以在MyService中来自行停止服务,只需在任意地方调用stopSelf()即可。
接着我们就可以通过点击按钮来测试了,如果没问题的话点击开启服务按钮控制台会打印onCreate executed、onStartCommand executed,点击暂停服务按钮控制台会打印onDestroy executed
活动和服务间的通信
还记得我们在创建服务时有一个onBind()方法吗,它允许我们在活动中去控制服务,将服务与活动联系起来。
例如我们想在MyService中实现一个下载功能(模拟),然后希望在活动中可以决定什么时候开始下载,以及可以查看下载进度,要想实现这个功能就需要我们创建一个专门的Binder对象(这里就DownloadBinder了)来对下载功能进行管理了。MyService中代码如下:
public class MyService extends Service {
private DownLoadBinder mBinder=new DownLoadBinder();
class DownLoadBinder extends Binder{
public void startDownload(){
Log.d("MainActivity","startDownload executed");
}
public int getProcess(){
Log.d("MainActivity","getProcess executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
......
}
在MyService服务中,我们新建了一个DownloadBinder类,让它继承自Binder,并在它的内部实现了开始下载和查看下载进度的方法,当然只是模拟一下。接着在MyService中创建了DownloadBinder的实例mBinder,最后别忘了在onBind()方法中将mBinder返回。
接下来我们就可以在活动中去写绑定服务和解绑服务的逻辑了:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyService.DownLoadBinder downLoadBinder;
private ServiceConnection connection=new ServiceConnection() {
//绑定成功时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//在这获取了downLoadBinder对象我就可以在任意地方去调用downLoadBinder对象的任意方法了
downLoadBinder=(MyService.DownLoadBinder) service;
//开始下载
downLoadBinder.startDownload();
//获取进度
downLoadBinder.getProcess();
}
//活动与服务断开连接时调用
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
Button bindService=(Button) findViewById(R.id.bind_service);
Button unbindService=(Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
...
case R.id.bind_service:
Intent bindIntent=new Intent(this, MyService.class);
//绑定服务
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
//解绑服务
unbindService(connection);
default:
break;
}
}
}
我们创建出一个ServiceConnection匿名类,在里面重写了onServiceConnection()方法和onServiceDisconnected()方法,分别在活动与服务成功绑定时和断开连接时调用。尤其注意onServiceConnected()这个方法,在这里面,我们通过向下转型获得了DownloadBinder的实例,那我们是不是就可以在活动中去随意调用服务中的方法了呢,这样一来我们的活动与服务不就紧密连接起来了。
当然真正让我们的活动与服务绑定起来的代码是bindService(bindIntent,connection,BIND_AUTO_CREATE);,第二个参数是匿名类ServiceConnection的实例,BIND_AUTO_CREATE表示活动与服务绑定之后自动创建服务,这样只会使MyService中的onCreate()方法得到执行,onStartCommand()不执行。
最后:任何一个服务在整个应用程序都是通用的,即MyService可以与任意活动绑定,绑定完获得的是相同的DownloadBinder实例。
服务的生命周期
- 我们前面在MyService中编写的onCreate()、onStartCommand()、onBind()、onDestory()等方法都是在服务的生命周期中可能回调的方法
- 一旦调用Context的startService()方法,服务就会启动起来,并回调onStartCommand()方法,如果这个服务之前还没被创建过,onCreate()方法会先于onStartCommand()方法执行。之后服务会一直保持运行状态,直到stopService()或stopSelf()方法被调用。需要注意的是,虽然每调用一次startService()方法onStartCommand()方法就会执行一次,但始终服务只存在一个实例,只需调用一次stopService()或stopSelf()方法服务就会停止。
- 还可以调用Context的bindService()方法来获取一个服务的持久连接,这时会调用服务中的onBind()方法,如果这个服务之前还没被创建过,onCreate()方法会先于onBind()方法执行。调用方(活动)获得onBind()方法返回的IBinder即可进行通信了。
- 当调用了startService()方法,又调用了stopService()方法,服务中的onDestroy()方法才会执行;同样的调用了bindService()方法,又调用了unbindService()方法,服务中的onDestroy()方法才会执行;那当同时调用了startService()方法和bindService()方法要咋样服务中的onDestroy()方法才会执行呢?答案是同时调用stopService()方法和unbindService()方法。
使用前台服务
由于服务的优先级较低,当系统出现内存不足时,就有可能回收掉正在后台运行的服务,如果你不想服务因为内存不足被回收,可以使用前台服务。当然有时候你可能并不是为了防止服务被回收才使用前台服务的,有些特殊需求必须使用前台服务来完成。
前台服务和后台服务最大的区别就是,前台服务会有一个正在运行的图标在系统的状态栏显示,下拉状态栏就可以看到更加详细的信息,类似于通知的效果。
创建一个前台服务如下,在onCreate()方法中:
@Override
public void onCreate(){
super.onCreate();
Log.d("MyService","OnCreate");
//创建notificationManager对通知进行管理
NotificationManager notificationManager = getSystemService(NotificationManager.class);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
String channelId = "channelId";//通知渠道的标识符
CharSequence channelName = "...";//通知渠道的位置
String channelDescription = "...";//通知渠道的描述
//设置通知渠道的级别
int importance = NotificationManager.IMPORTANCE_DEFAULT;
//创建通知渠道
NotificationChannel notificationChannel = new NotificationChannel(channelId,channelName,importance);
notificationChannel.setDescription(channelDescription);
//在系统中注册消息
notificationManager.createNotificationChannel(notificationChannel);
}
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
//创建通知
Notification notification = new NotificationCompat.Builder(this,"001")
.setContentTitle("标题")
.setContentText("内容")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pi)
.build();
//显示通知
startForeground(1,notification);
}
调用startForeground()方法可以让你的MyService变成一个前台服务,并在系统状态栏显示出来。
使用IntentService
以上我们的所有代码都是运行在主线程的,如果直接在服务里去做一些耗时逻辑,就非常容易出现ANR(Application Not Responding)的情况。
这里我们先来了解一下什么是ANR,ANR是指在主线程上执行耗时操作或阻塞操作时,无法响应用户输入或与系统交互的情况。当发生ANR时,应用程序会被系统视为未响应的,并可能导致应用崩溃或被用户强制关闭。
这时我们就需要用到Android多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,在这里去处理那些耗时逻辑,所以一个标准的服务就可以写成以下形式(同时让服务执行完毕自动停下来):
public class MyService extends Service {
...
@Override
public int onStartCommand(Intent intent,int flags,int startId){
new Thread(new Runnable(){
@Override
public void run(){
//具体逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent,flags,startId);
}
}
为了简化上述步骤,Android提供了一个IntentService类,这个类允许我们简单地创建一个异步的、会自动停止的服务。
首先创建一个MyIntentService继承自IntentService,记得在AndroidManifest.xml文件中注册这个服务:
public class MyIntentService extends IntentService {
public MyIntentService() {
//调用父类的有参构造
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
//打印当前线程id
Log.d("MyIntentService","Thread id is "+Thread.currentThread().getId());
}
@Override
public void onDestroy(){
super.onDestroy();
Log.d("MyIntentService","onDestroy executed");
}
}
在这个类中我们实现了onHandleIntent()这个方法,在这个方法里面我们可以去实现那些耗时逻辑,并且不用担心ANR问题,因为这个方法是在子线程中运行的(通过打印线程id来验证),这里我们还重写了onDestroy()方法,来验证服务执行完会不会自动停止。
在MainActivity中:
Button startIntentService=(Button) findViewById(R.id.start_intent_service);
startIntentService.setOnClickListener(this);
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.start_intent_service:
Log.d("MainActivity","Thread id is "+Thread.currentThread().getId());
Intent intentService=new Intent(this, MyIntentService.class);
startService(intentService);
break;
default:
break;
}
}
在这通过打印主线程id来与onHandleIntent()方法中线程的id做对比,点击按钮后,你会发现:
根据打印结果来看,首先MyIntentService的onHandleIntent()方法确实是在子线程中执行的,其次服务执行完后会自动停止。