🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 1.Service的创建 1. **手动创建** - 新建Class文件,继承Service - 在AndroidMainfest中进行注册 2. **使用AndroidStudio进行自动创建** **AndroidMainfest.xml** ```java <service //Service所在的类 android:name=".MyService" //Service是否可用 android:enabled="true" //是否可以被其他应用访问 android:exported="true"/> ``` --- # 2.Service的生命周期 ## 先介绍一下Service的生命周期函数 * **OnCreate()** 当Service被创建时调用,在整个生命周期过程中只调用一次 * **OnStartCommand()** 当在Activity中调用startService()时被调用,一般用与Activity无关的Service,如播放音乐,下载等 * **OnBind()** 在Activity调用bindService()函数时调用 * **OnUnBind()** 在Activity调用unBindService()函数时调用 * **OnDestory()** 在Service销毁时调用,调用该方法代表这本次生命周期结束,再次启动该Service时,需调用OnCreate()方法 ## Service生命周期的两种模式 ![Service生命周期图解](http://upload-images.jianshu.io/upload_images/13971762-a9920d73aa10519a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 由上图我们可以很清晰的看到,Service的生命周期分为两种,**绑定模式**和**非绑定模式**,除此之外,还有经常可见的两种模式的混合使用,只要我们把这两种模式理解清楚了,相信混合模式也不在话下了 **非绑定模式:** > onCreate() -> onStartCommand() -> onDestory() 通过Context的startService() 方法启动一个Service,执行OnCreate()和OnStartCommand()方法,再次调用startService()则会再次执行OnStartCommand()方法,不管执行多少次startService(),只需要调用一次stopService()方法,Service就会调用OnDestory()方法,来结束本次服务; **绑定模式:** > onCreate() -> onBind() -> onUnBind() -> onDestory() 通过Context的bindService()方法绑定一个Service,执行OnCreate() 和 OnBind() 方法,再次调用bindService()无效,直到调用UnBindService() 方法,执行OnUnBind() 和 OnDestory() 方法,结束本次服务; > > ==**注意:** 可能存在未调用onBindService()方法就调用OnUnBindService()或者调用一次onBindService()方法调用两次OnUnBindService()方法,造成 *java.lang.IllegalArgumentException: Service not registered:xxx*的异常,可能导致程序崩溃== > > > - [x] **解决方法**:在调用OnUnBindService()进行异常捕获,对异常进行处理,最后给予提示信息 代码示例: **MyService.java** ```Java public class MyService extends Service { public MyService() { } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind "); return null; }; } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); isRunning = false; Log.i(TAG, "onDestroy: "); } } ``` **MainActivity.java** ```java public class MainActivity extends AppCompatActivity implements View.OnClickListener { private Button startServiceBtn; private Button stopServiceBtn; private Button bindServiceBtn; private Button unbindServiceBtn; private Intent serviceIntent; private static final String TAG = "MainActivity"; private ServiceConnection serviceConnection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "onServiceConnected: "); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); serviceIntent = new Intent(this,MyService.class); startServiceBtn=findViewById(R.id.start_service); stopServiceBtn=findViewById(R.id.stop_service); bindServiceBtn=findViewById(R.id.bind_service); unbindServiceBtn=findViewById(R.id.unbind_service); startServiceBtn.setOnClickListener(this); stopServiceBtn.setOnClickListener(this); bindServiceBtn.setOnClickListener(this); unbindServiceBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.start_service: startService(serviceIntent); break; case R.id.stop_service: stopService(serviceIntent); break; case R.id.bind_service: bindService(serviceIntent, serviceConnection,BIND_AUTO_CREATE); break; case R.id.unbind_service: try { unbindService(serviceConnection); }catch (IllegalArgumentException e){ Log.i(TAG, "unBindService: "+e.getMessage()); Toast.makeText(this, "未绑定任何服务!", Toast.LENGTH_SHORT).show(); } break; default: } } } ``` **activity_main.xml** ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/start_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="启动本地服务" /> <Button android:id="@+id/stop_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="停止本地服务" /> <Button android:id="@+id/bind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="绑定本地服务" /> <Button android:id="@+id/unbind_service" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="解除绑定本地服务" /> </LinearLayout> ``` ## 运行截图 ### 非绑定模式 > **为验证多次点击效果,点击了5次启动(绑定)和3次停止(解除绑定)按钮** ![非绑定模式.png](https://upload-images.jianshu.io/upload_images/13971762-017b4c86f882c73b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ### 绑定模式 > **为验证多次点击效果,点击了5次绑定和2次解除绑定按钮,发现第二次点击解除绑定按钮出现提示** ![绑定模式.png](https://upload-images.jianshu.io/upload_images/13971762-f84e0c40657c73fd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) --- # 3. 跨应用启动、绑定Service > 相信在第一小节Service的创建中大家已经注意到了Service的一个属性: android:exported="true",我在上面也给大家标明了注释--是否可以被其他应用访问,那么说明了Service是可以在不同的应用之间使用的,这也符合了我们所了解的,Android四大组件都是可以跨进程访问的(在通常情况下,Android的一个应用就是一个进程),那么我们该如何操作呢? **步骤如下:** 1. 创建一个新的Module--AnotherApp,将MainActivity.java和activity_main.xml拷贝一份 2. 对Intent的定义进行修改 ```java Intent intent = new Intent(); intent.setComponent(new ComponentName("com.duoshilin.aidltestapplication", "com.duoshilin.aidltestapplication.MyService")); ``` > 下面是源码对ComponentName(String pkg, String cls)的方法介绍 因为是跨应用的访问Service,所有我们不能直接通过平常使用的方法去启动的一个Service; ```java /** * Create a new component identifier. * * @param pkg The name of the package that the component exists in. Can * not be null. * @param cls The name of the class inside of <var>pkg</var> that * implements the component. Can not be null. */ public ComponentName(String pkg, String cls) { if (pkg == null) throw new NullPointerException("package name is null"); if (cls == null) throw new NullPointerException("class name is null"); mPackage = pkg; mClass = cls; } ``` > String pkg 指的是要访问的Service所在的包名; > String cls 指要访问的Service的完整类名; **这样我们就可以跨应用去访问一个Service了,是不是很简单呢?下面我们来运行一下看看效果吧!** ![image.png](https://upload-images.jianshu.io/upload_images/13971762-6b9000b48ff64fa2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > 要注意的是,这个时候我们还是要在原APP中去查看日志打印结果哈! --- # 4. 跨进程的Service通信 > 在上一小节中,我们访问到了其他应用中的Service,但是我们访问到了之后应该干什么呢?当然是进行数据交互了,哈哈!不然我们为什么要去访问他呢?(这里我就不说是跨应用了,专业点,还是跨进程吧!) 在这一小节,我们会涉及到一个新的知识点,AIDL(Android Interface Define Language 安卓接口定义语言),在AndroidStudio中我们可以创建一个AIDL文件。方法如下:在Android项目目录结构的src/main文件夹,右击 -> New -> Floder -> AIDL Floder,来创建一个AIDL文件夹,然后右击该文件夹, New -> AIDL -> AIDL File, 即可创建一个AIDL文件。准备工作做的差不多了,下面开始进入今天的正题: **1. 对MyService进行一些改进,为了方便理解,还是把整个文件放上吧** ```java public class MyService extends Service { private static final String TAG = "MyService"; //要访问的数据,默认值为"default" private String data="default"; //判断该服务是否正在运行 private boolean isRunning = false; //线程池,不了解的同学可以去了解一下,这个东西对Java来说也是挺重要的 private ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1,1,1,TimeUnit.MINUTES,new SynchronousQueue<Runnable>()); //定义一个接口:当Service在运行期间每隔1s打印一下当前的数据 private Runnable task = new Runnable() { @Override public void run() { while (isRunning){ Log.i(TAG, "当前数据: "+data); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; public MyService() { } @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: "); return new IMyAidlInterface.Stub() { @Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException { } @Override public void setData(String data) throws RemoteException { MyService.this.data = data; } }; } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate: "); isRunning = true; //启动线程 poolExecutor.execute(task); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: "); return super.onStartCommand(intent, flags, startId); } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind"); return super.onUnbind(intent); } @Override public void onDestroy() { super.onDestroy(); isRunning = false; Log.i(TAG, "onDestroy: "); } ``` **2. 创建一个AIDL文件,内容如下:** ```java interface IMyAidlInterface { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString); void setData(String data); } ``` > 那些自动生成的内容我们暂时就先不看了,可以看到的是我增加了一个setData(String data) 接口,用来给Service的data变量设置值 **3. 增加新的控件,以便查看效果,重复代码就不在这里贴了** **activity_main.xml** ```java <EditText android:id="@+id/edit_data_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="这是另一个应用中的数据"/> <Button android:id="@+id/save_data_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="保存数据"/> ``` **MainActivity.java** ```java public class MainActivity extends AppCompatActivity implements View.OnClickListener { ... private Intent intent; private IMyAidlInterface binder = null; private EditText editData; private Button savaDataBtn; private ServiceConnection serviceConnection=new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "onServiceConnected: "); //新增语句,将service转换为IMyAidlInterface的binder对象 binder=IMyAidlInterface.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... intent = new Intent(); intent.setComponent(new ComponentName("com.duoshilin.aidltestapplication", "com.duoshilin.aidltestapplication.MyService")); editData=findViewById(R.id.edit_data_input); savaDataBtn=findViewById(R.id.save_data_btn); savaDataBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ ... case R.id.save_data_btn: try { if(binder != null) { binder.setData(editData.getText().toString()); } } catch (RemoteException e) { e.printStackTrace(); } break; default: } } } ``` **4. 重新启动两个APP,点击并查看效果。** ![image.png](https://upload-images.jianshu.io/upload_images/13971762-f99d11d82222353b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) > ***需要注意的是,我们需要在绑定模式下才能修改数据,非绑定模式下我还不知道用什么方法可以修改数据,希望大神指教!*** # 5. IntentService的使用 # 6. 前台服务的使用