类型和区别
- 特点:没有UI,后台运行
- Started
- 启动方式startSevice()
- 一旦启动,在后台永远运行,除非主动停止或者被kill
- 通常不返回值(可发一个PendingIntent, 用广播返回值)
- Bound
- 启动方式bindService()
- 只要有应用绑定到它,就在运行。当没有应用绑定时,service被摧毁。
- 多个应用可以绑定到一个bind service.
- BroadcastReceiver不能绑定到服务
- 可以返回值,以及做进程间通信(IPC)
- 一个service可以同时实现onStartCommand()和onBind()
-
注意:Service是在主线程上运行,不要进行耗时操作
image.png
创建一个新的service
-
重写父类方法
- onStartCommand(). 被startService()调用,需要用stopSelf() or stopService()手动停用
- onBind(),当调用bindSerivce()的时候会调用,需要返回一个IBinder
- onCreate(). 一次性,如service已经在运行,则不再调用
- onDestroy(). 前台服务>绑定到活动的服务>后台服务>长时间运行的后台服务. 服务会被kill,当有资源时,会尝试恢复。对于started服务,需要考虑当系统自动恢复时的处理。这根据onStartCommand()时的返回值有所不同:
- START_STICKY, 重新启动时,传入的intent是null
- START_STICKY_COMPATIBILITY, 不保证会重新调用onStartCommand()
- START_NOT_STICKY, 不自动重启
- START_REDELIVER_INTENT, 总是重新发送上次的intent
- 可以通过flag来判断服务的性质START_FLAG_REDELIVERY, START_FLAG_RETRY
-
修改Manifest
- android:enabled=["true" | "false"]
- android:exported=[“true” | “false”],如果为false,只有相同userID的能访问。如果有intent filter,默认为true,如果没有,默认为false
- android:icon="drawable resource“
- android:isolatedProcess=[“true” | “false”], 为true时,将运行在独立进程中
- android:label="string resource“
- android:name="string“
- android:permission=“string“,如果设置,只有拥有权限的才能访问
- android:process=“string”
IntentService & Stop Service &前台服务
- Intent Service
- Service的子类,推荐的实现Started Service的方法。会自动为每个request创建worker thread.
- 自动创建队列进行处理,不用担心线程冲突
- 队列处理完毕自动stopSelf()
- 自动为不可bind的(可以通过重写来实现可bind)
- 只需要两点:
- 构造函数中调用父类
- 实现onHandleIntent()方法
- 可以重写其它方法,但要记得返回父类函数
- 停止Service
- 当stop service时,如果多个request同时在处理,stopSelf()可能会把新发起的request结束掉,所以推荐使用stopSelf(int),这样会比较当前正在处理的request是不是结束的目标。
- 记得stopSelf(),这是消耗系统资源的。
- 前台Service
- 提供必需的notification和id, 用startForeground(ONGOING_NOTIFICATION_ID, notification) 启动,用stopForeground(boolean removeNotification)停止
- stopForeground并不停止服务,而只是会让前台notification消失
Bounded Service
创建bounded service的三种方式
- 扩展Binder类,如果只是自己的应用内使用,用这种方式。
- 使用Messenger,可实现IPC的最简单(?)的方法,线程安全,C/S端都用Handler来处理消息
- 使用AIDL,优点是可以同时处理多个请求,对于大部分的app,Google并不推荐使用这种方式(但是在framework里面有很多)
onBind()只在第一次绑定时执行一次,后续再绑定时返回第一次的Ibinder对象
当调用服务的方法时,要捕获DeadObjectException异常,这个异常标明service不存在了 - bindServices的flag
- BIND_AUTO_CREATE
- BIND_DEBUG_UNBIND,只用于调试。当设置时,以后的unbind请求的callstack会被保存
- BIND_IMPORTANT,允许客户端把服务升级为“前台”优先级,一般只能升级到“可见”优先级
- BIND_NOT_FOREGROUND,不允许把被绑定的服务升级为“前台”优先级。
- BIND_ABOVE_CLIENT,道友比贫道更重要
- BIND_ADJUST_WITH_ACTIVITY,如从activity发起,那么允许优先级随其可见性来调整
- BIND_ALLOW_OOM_MANAGEMENT, 让拥有该服务的进程来自己管理优先级
- BIND_WAIVE_PRIORITY,放弃调整优先级,让系统像对待普通应用一样对待
扩展Binder类方式
- Service端
- 实现一个Binder实例,其中包含可调用的方法或者其它包含可调用方法的类示例(包括当前的service类)
- 在onBind()时返回该实例
- Client端
- 绑定时传入connection参数(一个ServiceConnection的实例),绑定是异步的,并不返回Binder
- 在connection的onServiceConnected()方法中接收实例,并转换类型为Binder类型,然后调用需要的方法
- 实现connection的onServiceDisconnected()方法,处理服务crash和被kill时的情况
- 示例代码
//服务端:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();//实例化
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. 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 {//实现Binder,其中有可返回当前service的方法
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
//客户端:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);//开启Activity时绑定service,绑定后立即返回,connection作为参数传入
//绑定完成后,将会调用connection的onServiceConnected方法,客户端将在
//那里接收Binder,并转型,然后得到要可调用方法的类,或者该Binder就包含可调方法
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
使用Messenger方式
- Service端
- 创建Handler来处理消息
- 用Handler创建一个Messenger
- 在onBind()中返回Messenger创建的IBinder
- Client端
- 用得到的IBinder去创建Messenger
- 用Messenger发消息指挥Service的Handler运行
- (如需要回复),创建Client端的Messenger,在send()命令的replyTo参数中传入这个Messenger
- Sample:
//服务端:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {//定义一个Handler
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());//用Handler创建一个Messenger
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();//返回用Messenger创建的IBinder
}
}
//客户端:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* 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 object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);//用IBinder创建Messenger
mBound = true;
}
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;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {//记得Catch Exception
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE); //实际应用中,这里应该是隐式intent
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
AIDL方式
-
服务端
- 创建AIDL
- 创建接口定义和方法签名
- 可识别的数据类型包括基本类型(int,long,boolean),String,CharSequence,List(支持泛型,对端接收到的总是一个arrayList),Map接口(不支持泛型,对端接收到的总是一个HashMap),List和Map必须加in/out/inout参数(in:参数会传输到服务端,out:参数只是用来获取输出值,没有传输,inout:两者结合)
- 只能有方法,不能有静态域
- 实现接口,SDK自动生成名为Stub的抽象函数(派生于Binder),我们需要派生这个Stub类并实现它的抽象方法
- 调用通常是以同步方式执行,所以注意不要在调用方的主线程调用,可能引起调用方的ANR
- 在服务端发出的exception并不会被返回到调用方。
实现服务,在onBind()中将stub的实例返回去
- 创建AIDL
-
客户端
- 也要导入AIDL
- 调用bind之后,在onServiceConnected()中用IRemoteService.Stub.asInterface(service)将Binder转换为接口,就可以直接使用了
-
本地调用发生在与调用方相同的线程,远程调用发生在一个系统维护的线程池中(这里,跟Service在主线程上运行是有出入的)
-
example:
AIDL:
package com.example.testservice;
interface ITestAidl {
List<String> testListGeneric();
List testListNoGeneric();//支持泛型List
//List testListWithGeneric1(in List param); //不支持非泛型参数
List testListWithGeneric(in List<String> param);
//Map<String,String> testMap1(); //不支持泛型Map
//Map testMap(out Map param);//Map做参数,gen里面会报错,原因应该是因为不支持非泛型参数
Map testMap(String Param);
List testIn(in List<String> param);//in:参数会被输送到服务端
//@Override public java.util.List testIn(java.util.List<java.lang.String> param) throws android.os.RemoteException
//{
//android.os.Parcel _data = android.os.Parcel.obtain();
//android.os.Parcel _reply = android.os.Parcel.obtain();
//java.util.List _result;
//try {
//_data.writeInterfaceToken(DESCRIPTOR);
//_data.writeStringList(param);
//mRemote.transact(Stub.TRANSACTION_testIn, _data, _reply, 0);
//_reply.readException();
//java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader();
//_result = _reply.readArrayList(cl);
//}
//finally {
//_reply.recycle();
//_data.recycle();
//}
//return _result;
//}
List testOut(out List<String> param);//out:参数只是用来获取输出值
//{
//android.os.Parcel _data = android.os.Parcel.obtain();
//android.os.Parcel _reply = android.os.Parcel.obtain();
//java.util.List _result;
//try {
//_data.writeInterfaceToken(DESCRIPTOR);
//mRemote.transact(Stub.TRANSACTION_testOut, _data, _reply, 0);
//_reply.readException();
//java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader();
//_result = _reply.readArrayList(cl);
//_reply.readStringList(param);
//}
//finally {
//_reply.recycle();
//_data.recycle();
//}
//return _result;
//}
List testInOut(inout List<String> param);//参数会被传输,处理后成为输出值
//{
//android.os.Parcel _data = android.os.Parcel.obtain();
//android.os.Parcel _reply = android.os.Parcel.obtain();
//java.util.List _result;
//try {
//_data.writeInterfaceToken(DESCRIPTOR);
//_data.writeStringList(param);
//mRemote.transact(Stub.TRANSACTION_testInOut, _data, _reply, 0);
//_reply.readException();
//java.lang.ClassLoader cl = (java.lang.ClassLoader)this.getClass().getClassLoader();
//_result = _reply.readArrayList(cl);
//_reply.readStringList(param);
//}
//finally {
//_reply.recycle();
//_data.recycle();
//}
//return _result;
//}
}
Service:
package com.example.testservice;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class SampleService extends Service {
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
private ITestAidl.Stub mBinder = new ITestAidl.Stub() {
@Override
public List testOut(List<String> param) throws RemoteException {
for (String s : param) {
Log.i("TestAIDL", "Before manipulate,param is:" + s);
}
param.add("5");
param.add("6");
param.add("7");
return param;
}
@Override
public Map testMap(String Param) throws RemoteException {
Map<Integer, String> myMap = new Hashtable<Integer, String>();
myMap.put(1, Param);
myMap.put(2, Param + " " + Param);
return myMap;
}
@Override
public List testListWithGeneric(List<String> param)
throws RemoteException {
for (String s : param) {
Log.d("TestAIDL", "Received:" + s);
}
return param;
}
@Override
public List testListNoGeneric() throws RemoteException {
List<String> myList = new ArrayList<String>();
myList.add("A");
myList.add("B");
return myList;
}
@Override
public List<String> testListGeneric() throws RemoteException {
List<String> myList = new ArrayList<String>();
myList.add("A");
myList.add("B");
return myList;
}
@Override
public List testInOut(List<String> param) throws RemoteException {
for (String s : param) {
Log.i("TestAIDL", "Before manipulate,param is:" + s);
}
param.add("5");
param.add("6");
param.add("7");
return param;
}
@Override
public List testIn(List<String> param) throws RemoteException {
for (String s : param) {
Log.i("TestAIDL", "Before manipulate,param is:" + s);
}
param.add("5");
param.add("6");
param.add("7");
return param;
}
};
}
Client:
package com.example.testserviceclient;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import com.example.testservice.ITestAidl;
import com.example.zzp.IMyinterface;
public class TestServiceClient extends Activity {
public static final String ActionName = "com.zzp.test.TESTSERVICE";
public static final String ActionNameUseJar = "com.zzp.test.USEJAR";
private ITestAidl myService;
private IMyinterface myJarService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ttest_service);
}
@Override
protected void onStart() {
bindService(new Intent(ActionName), myConnection,
Context.BIND_AUTO_CREATE);
bindService(new Intent(ActionNameUseJar), myJarConnection,
Context.BIND_AUTO_CREATE);
super.onStart();
}
@Override
protected void onStop() {
unbindService(myConnection);
unbindService(myJarConnection);
super.onStop();
}
private ServiceConnection myConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
return;
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
myService = ITestAidl.Stub.asInterface(arg1);
}
};
private ServiceConnection myJarConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
return;
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
myJarService = (IMyinterface) arg1;
}
};
public void onTestLG(View view) {
try {
List<String> myList = myService.testListGeneric();
for (String s : myList) {
Log.d("TestAIDL-Client", "Test List Generic:" + s);
// D/TestAIDL-Client(20810): Test List Generic:A
// D/TestAIDL-Client(20810): Test List Generic:B
}
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void onTestLNG(View view) {
try {
// List<String> myList = myService.testListNoGeneric();
List myList = myService.testListNoGeneric();
for (Object s : myList) {
Log.d("TestAIDL-Client",
"Test List Non Generic:" + s.toString());
// D/TestAIDL-Client(25015): Test List Non Generic:A
// D/TestAIDL-Client(25015): Test List Non Generic:B
}
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void TestLwG(View view) {
try {
// List<String> myList = myService.testListNoGeneric();
ArrayList<String> param = new ArrayList<String>();
param.add("Client-A");
param.add("Client-B");
List myList = myService.testListWithGeneric(param);
for (Object s : myList) {
Log.d("TestAIDL-Client",
"Test List With Generic:" + s.toString());
// D/TestAIDL(27513): Received:Client-A
// D/TestAIDL(27513): Received:Client-B
// D/TestAIDL-Client(27500): Test List With Generic:Client-A
// D/TestAIDL-Client(27500): Test List With Generic:Client-B
}
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void TestM(View view) {
try {
Map<Integer, String> myMap = myService.testMap("ABC");
for (Entry<Integer, String> s : myMap.entrySet()) {
Log.d("TestAIDL-Client",
"Test Map:" + s.getKey() + "_" + s.getValue());
// D/TestAIDL-Client(29739): Test Map:1_ABC
// D/TestAIDL-Client(29739): Test Map:2_ABC ABC
}
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void TestIn(View view) {
try {
// List<String> myList = myService.testListNoGeneric();
ArrayList<String> param = new ArrayList<String>();
param.add("Client-A");
param.add("Client-B");
for (String s : param) {
Log.d("TestAIDL-Client", "Test In, param:" + s.toString());
}
List myList = myService.testIn(param);
for (String s : param) {
Log.d("TestAIDL-Client", "Test In, param:" + s.toString());
}
for (Object s : myList) {
Log.d("TestAIDL-Client",
"Test In, return value:" + s.toString());
}
// D/TestAIDL-Client(31223): Test In, param:Client-A
// D/TestAIDL-Client(31223): Test In, param:Client-B
// I/TestAIDL(29511): Before manipulate,param is:Client-A
// I/TestAIDL(29511): Before manipulate,param is:Client-B
// D/TestAIDL-Client(31223): Test In, param:Client-A
// D/TestAIDL-Client(31223): Test In, param:Client-B
// D/TestAIDL-Client(31223): Test In, return value:Client-A
// D/TestAIDL-Client(31223): Test In, return value:Client-B
// D/TestAIDL-Client(31223): Test In, return value:5
// D/TestAIDL-Client(31223): Test In, return value:6
// D/TestAIDL-Client(31223): Test In, return value:7
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void TestOut(View view) {
try {
// List<String> myList = myService.testListNoGeneric();
ArrayList<String> param = new ArrayList<String>();
param.add("Client-A");
param.add("Client-B");
for (String s : param) {
Log.d("TestAIDL-Client", "Test Out, param:" + s.toString());
}
List myList = myService.testOut(param);
for (String s : param) {
Log.d("TestAIDL-Client", "Test Out, param:" + s.toString());
}
for (Object s : myList) {
Log.d("TestAIDL-Client",
"Test Out, return value:" + s.toString());
}
// D/TestAIDL-Client( 2359): Test Out, param:Client-A
// D/TestAIDL-Client( 2359): Test Out, param:Client-B
// 这里,服务端并没有收到param
// D/TestAIDL-Client( 2359): Test Out, param:5
// D/TestAIDL-Client( 2359): Test Out, param:6
// D/TestAIDL-Client( 2359): Test Out, param:7
// D/TestAIDL-Client( 2359): Test Out, return value:5
// D/TestAIDL-Client( 2359): Test Out, return value:6
// D/TestAIDL-Client( 2359): Test Out, return value:7
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
public void TestInOut(View view) {
try {
// List<String> myList = myService.testListNoGeneric();
ArrayList<String> param = new ArrayList<String>();
param.add("Client-A");
param.add("Client-B");
for (String s : param) {
Log.d("TestAIDL-Client", "Test InOut, param:" + s.toString());
}
List myList = myService.testInOut(param);
for (String s : param) {
Log.d("TestAIDL-Client", "Test InOut, param:" + s.toString());
}
for (Object s : myList) {
Log.d("TestAIDL-Client",
"Test InOut, return value:" + s.toString());
}
// D/TestAIDL-Client( 2359): Test InOut, param:Client-A
// D/TestAIDL-Client( 2359): Test InOut, param:Client-B
// I/TestAIDL(29511): Before manipulate,param is:Client-A
// I/TestAIDL(29511): Before manipulate,param is:Client-B
// D/TestAIDL-Client( 2359): Test InOut, param:Client-A
// D/TestAIDL-Client( 2359): Test InOut, param:Client-B
// D/TestAIDL-Client( 2359): Test InOut, param:5
// D/TestAIDL-Client( 2359): Test InOut, param:6
// D/TestAIDL-Client( 2359): Test InOut, param:7
// D/TestAIDL-Client( 2359): Test InOut, return value:Client-A
// D/TestAIDL-Client( 2359): Test InOut, return value:Client-B
// D/TestAIDL-Client( 2359): Test InOut, return value:5
// D/TestAIDL-Client( 2359): Test InOut, return value:6
// D/TestAIDL-Client( 2359): Test InOut, return value:7
} catch (RemoteException e) {
Log.e("TestAIDL-Client", e.getMessage());
e.printStackTrace();
}
}
// public void TestUseJar(View view) {
// Log.d("TestAIDL-Client", "Use Jar:" + myJarService.test());
// Log.d("TestAIDL-Client", "Static value:" + myJarService.DEFINE);
// Hashtable<Integer, String> param = new Hashtable<Integer, String>();
// param.put(5, "壹");
// param.put(6, "贰");
//
// Hashtable<Integer, String> myTable = myJarService.testGenericMap(param);
// for (Entry<Integer, String> s : myTable.entrySet()) {
// Log.d("TestAIDL-Client",
// "Use Jar Map:" + s.getKey() + "_" + s.getValue());
// }
//
// }
}
附:进程优先级
当系统决定杀掉一个进程的时候,会按照进程优先级的从低到高来决定先杀哪个。
- 前台
- 拥有Activity并用户正在交互(onResume)
- 拥有服务且服务与一个前台页面绑定
- 拥有服务,且通过startForeground()调用
- 正在执行服务的生命周期(onCreate等)
- 广播正在执行onReceive
- 可见
- 页面已暂停
- 服务绑定到前台或可见页面
- 服务进程
- 含有用Start方式启动的服务
- 后台进程
- 页面已不再可见(onStop被调用)
- LRU (least recently used) list, 最早停止的最早kill
- 空进程
- 不含有效组件,只为缓存
转载请注明出处。
image更多视频教程请在网易云课堂搜索黑山老雕。
网友评论