android四大组件之service知识点最强总结
2016-11-01 19:45
344 查看
1.基本概念
Service只能在后台运行,适合那些不需要界面的操作,像是播放音乐或者监听动作等,因为它 的名字就已经提示了:它就是一个服务。Service同样也是运行在主线程中,所以不能用它来做耗时的请求或者动作,否则就会阻塞住主线程。如果真的要这么做,可以跟Activity一样的做法:新开一个线程。
2.启动方式
Service根据启动方式分为两类:Started和Bound。其中,Started()是通过startService()来启动,主要用于程序内 部使用的Service,而Bound是通过bindService()来启动,允许多个应用程序共享同一个Service。如果单单只是为了使用Service,两种方式都可以,但正如我们上面所看到 的,Bound启动的Service可以允许多个应用程序绑定到Service,所以,如果该Service是多个程序共享的,必须使用Bound来启动
2.1声明Service:
<application> <service android:name=".MyService"/> </application>
2.2共享Service:
如果没有定义Intent Filters,默认下是私有的,无法访问,当然,我们也可以显示的指定该访问权限:android:exported=false。2.3startService:
服务一旦开启就跟开启者(activity)没有什么关系.activity挂了,服务还会开心的在后台运行.
创建Service,定义类继承Service,AndroidManifest.xml中定义
开启Service,在其他组件中调用startService方法
停止Service,调用stopService方法
2.4bindService
服务跟activity就有了千丝万缕的联系. 如果调用者activity挂了服务也跟着挂了. 不求同时生,但求同时死.
服务只能被显示的解除绑定一次,多次的解除绑定服务 应用程序会报异常
创建Service,定义类继承Service,AndroidManifest.xml中定义
开启Service,在其他组件中调用Context.bindService()来启动
绑定Service,onBind()
解绑Service,onUnbind()
停止Service,调用stopService()方法
2.5混合开启服务
需求: 服务长期的后台运行, 又想调用服务里面的方法.startservice 开启服务 (保证服务长期后台运行)
unbindService 解除绑定服务
//解除绑定,不解除无法直接关闭服务
stopService 停止服务.
3.生命周期
服务常用生命周期回调方法:
Started
Service的生命周期不像Activity那样复杂,当我们通过Context.startService()启动Service的时候,系统就会调 用Service的onCreate(),接着就是onStartCommand(Intent, int, int),过去这个方法是onStart(),但现在onStart()已经不被鼓励使用了,onStartCommand()里面的代码就是我们 Service所要执行的操作,接着就是onDestroy(),关闭Service。
Bound
通过Context.bindService()来启动,系统同样会调用Service的onCreate(),接着并不是 onStartCommand()而是onBind(),它会将多个客户端绑定到同一个服务中。如果我们想要停止Service,必须先对客户端解绑,也 就是调用onUnbind(),然后就是onDestroy()。
混合模式
如果先采用startService()方法启动服务,然后调用bindService()方法绑定到服务,再调用unbindService()方法解除绑定,最后调用bindService()方法再次绑定到服务
注意:
服务一旦被创建,就不会重新创建了,服务的oncreate方法只会执行一次,如果服务已经开启,就不会再去执行oncreate方法,服务在停止的时候,会调用ondestroy方法,每一次开启服务的时候 都会调用 onStartCommand() onStart();
4.Started Service 详细用法
接下来我们就来创建一个简单的Service:MyService,用于在后台播放MP3。这是介绍Service时经常用到的例子。public class MyService extends Service { MediaPlayer mediaPlayer = null; @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onCreate() { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); // uri 为要播放的歌曲的路径 super.onCreate(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { mediaPlayer.start(); return START_STICKY; } @Override public void onDestroy() { mediaPlayer.stop(); super.onDestroy(); } }
由于我们是通过Started启动Service,所以onBind()返回的是NULL。
其实,onStartCommand()中的代码是我们Service主要的操作,它会在每次Service运行的时候被调用,而onCreate()是 当Service第一次被创建的时候才会被调用,当Service已经在运行的时候,不会调用该方法,可以将一些初始化的操作放在这里。 定义好Service后,我们就可以在Activity中启动该Service:
Intent intent = new Intent(this, MyService.class); startService(intent);
这就像是在Activity中跳转到另一个Activity一样。
看上去Service就好像是在Activity中新开一个Thread一样,事实上,这两者也是挺像的,那么,我们是如何确定我们需要的是Thread还是Service?
如果是当用户使用我们的应用程序,像是点击按钮的时候,才执行一个在主线程外面的操作,我们需要的就是一个Thread而不是Service。
还是我们上面的例子,如果我们想要播放音乐是在我们应用程序运行的时候,那么我们可以再onCreate()中新建一个Thread, 然后在onStartCommand()中开始该Thread,接着是在onStop()中停止该Thread。这样的做法非常自然,是一般的新手都会想 到的,但是android鼓励我们使用AsyncTask或者HandlerThread来处理这个过程。关于这个话题,我们还是放在其他文章里讲吧,毕 竟这是一个不小的话题。
当我们开启了一个Service,我们就有义务去决定什么时候关闭该Service。可以通过stopSelf()让Service自己关闭自己或者调用stopService()来关闭该Service。
当内存不够的时候,系统也会自动关闭Service。这时系统是如何选择哪些Service应该被关闭呢?如果一个Service被绑定到 Activity并且得到用户的焦点,那么它就很少可能会被关闭,但如果它是被声明运行在前台,它就永远也不可能会被关闭。
知道这个事实对我们有什么用呢?当然是考虑如果内存足够的时候我们如何重启Service,解决这个问题的关键就在于onStartCommand()的返回值。
onStartCommand()指定要求返回一个整数值,这个整数值描述的就是系统应该以何种方式来重启该Service,一共有下面三种方式:
1.START_NOT_STICKY:系统在关闭Service后,不需要重新创建该Service,除非我们传递Intent要求创建Service。这是避免不必要的Service运行的最安全的方式,因为我们可以自己指定什么时候重新启动该Service。
2.START_STICKY:要求重新创建Service并且系统会通过一个空的Intent来调用onStartCommand()除非我们传递Intent。我们看到,上面的例子就使用了该返回值,这样当音乐播放被停止后,重新启动的时候就会重新播放音乐。
3.START_REDELIVER_INTENT:这种方式会通过关闭前的Intent来调用onStartCommand()。它非常适合像是下载文件这类的操作,这样当重新启动的时候,就会继续之前的下载而不会丢失之前的下载进度。
关闭Service有两种方式:stopSelf()和stopService(),它们是有区别的,而且区别非常大。如果我们在一个应用程序中开启了多 个Service,那么我们就不能贸然的使用stopService()来关闭Service,因为前一个Service的关闭可能会影响到后面 Service的使用。这时我们就需要使用stopSelf(int startId)来决定关闭哪个Service。关闭Service是非常重要的,这对于减少内存消耗和电量消耗来说,都是一件好事。
要想使用Started Service,我们除了继承自Service之外,还可以继承自另一个类:IntentService。IntentService是Service的子类。我们一般都是一个Intent就开启一个Service,但是 IntentService是专门有一个线程来处理所有的开启请求,每次处理一个。这是我们需要在应用程序中开启多个Service但又想避免多线程的最佳选择。
public class MyService extends IntentService { MediaPlayer mediaPlayer = null; public MyService() { super("MyService"); } @Override protected void onHandleIntent(Intent intent) { if (mediaPlayer == null) { mediaPlayer = MediaPlayer.create(this, uri); }// uri 为要播放的歌曲的路径 mediaPlayer.start(); } }
和继承自Service不一样,我们需要一个构造器,该构造器的主要作用就是为工作线程命名,我们仅需要覆写onHandleIntent()一个方法,因为该方法会为我们处理所有的Intent,并且会在处理完毕后关闭该Service,IntentService就像是一个封装好的Service,方便我们处理多个Intent的情况,但如果我们想要覆写其他方法,也是可以的,但要确保每个方法最后都有调用super的实现。
5. Bound Service 详细用法
5.1本地服务
绑定本地服务调用服务里面方法的流程:在activity 采用bindService方式开启服务…
写一个实现类 MyConn implements ServiceConnection
接口里面有一个方法onServiceConnected 在服务成功绑定的时候调用的方法
在service代码 实现 onBind方法 返回一个IBinder接口的实现( 里面必须一个调用服务方法的api)
在服务成功绑定的时候 服务里面返回的IBinder对象会传给 activity里面onServiceConnected 方法
获取IBinder对象 中间人, 调用中间人的方法. 间接调用了服务里面的方法
我们先从简单的本地服务开始:
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 { 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); } }
这个例子非常简单,就是为每个绑定到该服务的客户产生一个0-100的随机数。
我们首先必须提供一个IBinder的实现类,该类返回的是我们Service的一个实例,这是为了方便客户调用Service的公共方法,接着我们在onBind()方法中返回这个IBinder的实现类,这种做法适合Service只在应用程序内部共享,我们可以这样使用这个Service:
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); } @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; } }; }
我们通过bindService()将Activity和Service绑定到一起,接着必须定义一个 ServiceConnection,为的就是通过绑定的IBinder的getService()方法来获得Service,然后我们再调用 Service的getRandomNumber()。使用完后,我们需要通过unbindService()来解除绑定,这里充分利用了回调的价值,这种方式仅仅适合客户和服务都在同一个应用程序和一个进程内,像是音乐程序的后台播放。
混合调用:
结合我们上面有关Started Service的讨论,我们知道,可以用两种方式来开启服务,StartedService完全可以变成Bound Service,这样我们就不用显式的关闭服务,当没有任何客户和该Service绑定的时候,系统会自动的关闭该Service。但如果我们在一个 Started Service中也同样使用了onBind()呢?像是这样的情况:我们在一个音乐播放程序中利用Started Service开启音乐播放,然后用户离开程序后,音乐会在后台播放,当用户重新返回到程序中时,Activity可以绑定到Service上以便对音乐 进行控制。我们可以使用onRebind()方法来做到这点。
这就是既是Started Service又是Bound Service的整个生命周期。
5.2 远程服务
5.2.1 通过Messenger实现进程间通信
但我们有时候需要和远程的进程进行通信,这时就需要使用Messenger,这是为了实现进程间通信但又不想使用AIDL的唯一方式。我们还是先来个简单例子:
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 { @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()); /** * 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(); } }
可以看到,我们必须先在Service里面实现一个Handler,然后将这个Handler传递给Messenger,然后在onBind()中返回的是该Messenger的getBinder()的返回值。
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); 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) { 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); } @Override protected void onStop() { super.onStop(); // Unbind from the service if (mBound) { unbindService(mConnection); mBound = false; } } }
Activity所要做的就是创建一个Messenger,该Messenger拥有Service返回的IBinder,接着就是发送消息给 Service,然后Service中的Handler根据消息进行处理。我们可以看到,Messenger之所以能够实现进程间的通信,靠的还是 Handler。
5.2.2 通过AIDL实现进程间通信
在Android中, 每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,该如何实现呢? 显然, Java中是不支持跨进程内存共享的。因此要传递对象, 需要把对象解析成操作系统能够理解的数据格式, 以达到跨界对象访问的目的。在JavaEE中,采用RMI通过序列化传递对象。在Android中, 则采用AIDL(Android Interface Definition Language:接口定义语言)方式实现。AIDL是一种接口定义语言,用于约束两个进程间的通讯规则,供编译器生成代码,实现Android设备上的两个进程间通信(IPC)。AIDL的IPC机制和EJB (Enterprise Java Bean JAVA中的商业应用组件技术) 所采用的CORBA (Common Object Request Broker Architecture 是一组用来定义“分布式对象系统”的标准) 很类似,进程之间的通信信息,首先会被转换成AIDL协议消息,然后发送给对方,对方收到AIDL协议消息后再转换成相应的对象。由于进程之间的通信信息需要双向转换,所以android采用代理类在背后实现了信息的双向转换,代理类由android编译器生成,对开发人员来说是透明的。
假设A应用需要与B应用进行通信,调用B应用中的download(String path)方法,B应用以Service方式向A应用提供服务。需要下面四个步骤:
1>>在B应用中创建*.aidl文件,aidl文件的定义和接口的定义很相类,
如:在cn.itcast.aidl包下创建IDownloadService.aidl文件,内容如下:
package cn.itcast.aidl; interface IDownloadService { void download(String path); }
当完成aidl文件创建后,eclipse会自动在项目的gen目录中同步生成IDownloadService.java接口文件。接口文件中生成一个Stub的抽象类,里面包括aidl定义的方法,还包括一些其它辅助方法。值得关注的是asInterface(IBinder iBinder),它返回接口类型的实例,对于远程服务调用,远程服务返回给客户端的对象为代理对象,客户端在onServiceConnected(ComponentName name, IBinder service)方法引用该对象时不能直接强转成接口类型的实例,而应该使用asInterface(IBinder iBinder)进行类型转换。
编写Aidl文件时,需要注意下面几点: 1.接口名和aidl文件名相同。 2.接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。 3.Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),使用这些类型时不需要import声明。对于List和Map中的元素类型必须是Aidl支持的类型。如果使用自定义类型作为参数或返回值,自定义类型必须实现Parcelable接口。 4.自定义类型和AIDL生成的其它接口类型在aidl描述文件中,应该显式import,即便在该类和定义的包在同一个包中。 5.在aidl文件中所有非Java基本类型参数必须加上in、out、inout标记,以指明参数是输入参数、输出参数还是输入输出参数。 6.Java原始类型默认的标记为in,不能为其它标记。
2>>在B应用中实现aidl文件生成的接口(本例是IDownloadService),但并非直接实现接口,而是通过继承接口的Stub来实现(Stub抽象类内部实现了aidl接口),并且实现接口方法的代码。内容如下:
public class ServiceBinder extends IDownloadService.Stub { @Override public void download(String path) throws RemoteException { Log.i("DownloadService", path); } }
3>>在B应用中创建一个Service(服务),在服务的onBind(Intent intent)方法中返回实现了aidl接口的对象(本例是ServiceBinder)。内容如下:
public class DownloadService extends Service {
private ServiceBinder serviceBinder = new ServiceBinder();
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
public class ServiceBinder extends IDownloadService.Stub { @Override public void download(String path) throws RemoteException { Log.i("DownloadService", path); } }
}
其他应用可以通过隐式意图访问服务,意图的动作可以自定义,AndroidManifest.xml配置代码如下:
<service android:name=".DownloadService" > <intent-filter> <action android:name="cn.itcast.process.aidl.DownloadService" /> </intent-filter> </service>
4>>把B应用中aidl文件所在package连同aidl文件一起拷贝到客户端A应用,eclipse会自动在A应用的gen目录中为aidl文件同步生成IDownloadService.java接口文件,接下来就可以在A应用中实现与B应用通信,代码如下:
public class ClientActivity extends Activity { private IDownloadService downloadService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.bindService(new Intent("cn.itcast.process.aidl.DownloadService"), this.serviceConnection, BIND_AUTO_CREATE);//绑定到服务 } @Override protected void onDestroy() { super.onDestroy(); this.unbindService(serviceConnection);//解除服务 } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { downloadService = IDownloadService.Stub.asInterface(service); try { downloadService.download("http://www.itcast.cn"); } catch (RemoteException e) { Log.e("ClientActivity", e.toString()); } } @Override public void onServiceDisconnected(ComponentName name) { downloadService = null; } }; }
5.2.3 通过Parcelable实现在AIDL间传递自定义类型参数
Aidl默认支持的类型包话java基本类型(int、long、boolean等)和(String、List、Map、CharSequence),如果要传递自定义的类型该如何实现呢?Parcelable接口
要传递自定义类型,首先要让自定义类型支持parcelable协议,实现步骤如下:
1>自定义类型必须实现Parcelable接口,并且实现Parcelable接口的public void writeToParcel(Parcel dest, int flags)方法 。
2>自定义类型中必须含有一个名称为CREATOR的静态成员,该成员对象要求实现Parcelable.Creator接口及其方法。
3> 创建一个aidl文件声明你的自定义类型。
Parcelable接口的作用:实现了Parcelable接口的实例可以将自身的状态信息(状态信息通常指的是各成员变量的值)写入Parcel,也可以从Parcel中恢复其状态。 Parcel用来完成数据的序列化传递。
进程间传递自定义类型的实现过程:
1>>创建自定义类型,并实现Parcelable接口,使其支持parcelable协议。如:在cn.itcast.domain包下创建Person.java:
package cn.itcast.domain; import android.os.Parcel; import android.os.Parcelable; public class Person implements Parcelable private Integer id; private String name; public Person(){} public Person(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) {//把javanbean中的数据写到Parcel dest.writeInt(this.id); dest.writeString(this.name); } //添加一个静态成员,名为CREATOR,该对象实现了Parcelable.Creator接口 public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>(){ @Override public Person createFromParcel(Parcel source) {//从Parcel中读取数据,返回person对象 return new Person(source.readInt(), source.readString()); //*按write入的顺序读取 } @Override public Person[] newArray(int size) { return new Person[size]; } }; }
2>>在自定义类型所在包下创建一个aidl文件对自定义类型进行声明,文件的名称与自定义类型同名。
package cn.itcast.domain; parcelable Person;
3>>在接口aidl文件中使用自定义类型,需要使用import显式导入,本例在cn.itcast.aidl包下创建IPersonService.aidl文件,内容如下:
package cn.itcast.aidl; import cn.itcast.domain.Person; interface IPersonService { void save(in Person person); }
4>>在实现aidl文件生成的接口(本例是IPersonService),但并非直接实现接口,而是通过继承接口的Stub来实现(Stub抽象类内部实现了aidl接口),并且实现接口方法的代码。内容如下:
public class ServiceBinder extends IPersonService.Stub { @Override public void save(Person person) throws RemoteException { Log.i("PersonService", person.getId()+"="+ person.getName()); } }
5>>创建一个Service(服务),在服务的onBind(Intent intent)方法中返回实现了aidl接口的对象(本例是ServiceBinder)。内容如下:
public class PersonService extends Service {
private ServiceBinder serviceBinder = new ServiceBinder();
@Override
public IBinder onBind(Intent intent) {
return serviceBinder;
}
public class ServiceBinder extends IPersonService.Stub { @Override public void save(Person person) throws RemoteException { Log.i("PersonService", person.getId()+"="+ person.getName()); } }
}
6> 把应用中的aidl文件和所在package一起拷贝到客户端应用的src目录下,eclipse会自动在客户端应用的gen目录中为aidl文件同步生成IPersonService.java接口文件,接下来再把自定义类型文件和类型声明aidl文件及所在package一起拷贝到客户端应用的src目录下。
最后就可以在客户端应用中实现与远程服务的通信,代码如下:
public class ClientActivity extends Activity { private IPersonService personService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.bindService(new Intent("cn.itcast.process.aidl.PersonService"), this.serviceConnection, BIND_AUTO_CREATE);//绑定到服务 } @Override protected void onDestroy() { super.onDestroy(); this.unbindService(serviceConnection);//解除服务 } private ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { personService = IPersonService.Stub.asInterface(service); try { personService.save(new Person(56,"liming")); } catch (RemoteException e) { Log.e("ClientActivity", e.toString()); } } @Override public void onServiceDisconnected(ComponentName name) { personService = null; } }; }
5.3 绑定远程服务和本地服务的区别
本地服务 直接定义IService.java的接口远程服务 定义一个IService.adil文件 更改扩展名 删除public
本地服务 extend Binder implement IServcie
远程服务 extend IService.Stub
本地服务 绑定成功 直接强制类型转换 IServcie
远程服务 绑定成功 IService.Stub.asInterface(service)
小结
本篇对service进行了一个比较深层的总结,更是介绍了远程服务、本地服务、进程间通信的方面,也是平时面试经常会问到但是大部分人打不好的点,所以大家可以多看下理解理解。相关文章推荐
- android四大组件之activity知识点最强总结
- Android开发总结笔记 四大组件之Service(上) 1-2-4
- android 四大组件之service学习总结(二)
- Android Service即四大组件总结
- android四大组件之Service个人总结
- android 四大组件之service学习总结(一)
- Android四大组件之Service(服务)简单总结
- Android四大组件之二:Service总结
- android四大组件(总结)activity、service、content provider、broadcast receiver
- Android开发四大组件之Service总结
- Android开发总结笔记 四大组件之Service(中) 1-2-5
- Android开发总结笔记 四大组件之Service(下) 1-2-6
- Android基础知识总结---四大组件之一Service(一)
- android 四大组件之Service(4)IntentService
- android四大组件(Activity,Service,BroadcastReceiver,Content Provider)
- android 四大组件之Service(7) 结合通知
- android 四大组件之Service(8) 通过信使进行远程通信
- Android四大组件之Service
- Android四大组件之Service
- android 编写Service(四大组件之一)的方法