Android之Service设置android:process作用

Posted cps666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android之Service设置android:process作用相关的知识,希望对你有一定的参考价值。

原文地址 blog.csdn.net

在AndroidManifest.xml中定义service时会看到这样的代码android:process=”:remote”,例如:



1.  <service
2.      android:
3.      android:enabled="true"
4.      android:exported="false"
5.      android:process=":remote" />


这个代码是什么意思呢?

我们先来了解一下Service在AndroidManifest.xml中的声明语法,其格式如下:

<service android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:isolatedProcess=["true" | "false"]
    android:label="string resource"
    android:
    android:permission="string"
    android:process="string" >
    . . .

  • android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
  • android:name:对应Service类名
  • android:permission:是权限声明
  • android:process:是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote。
  • android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。
  • android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

1)默认情况(不指定process属性)

1、一个app只运行在一个进程中,进程名字为包名。
2、一个service(所有组件都一样)只作为一个线程运行在app的进程中,没有自己独立的进程。

2)****设置android:process="xxxx"后

1、设置了这行代码,系统就会为service创建新的进程

service将运行在这个新的独立的进程,它所在的apk依旧运行在原来进程。这样就实现了Android使用多进程
2、属性值可以随意定义
       xxxx是自定义的,上面代码的remote是随便写的
3、当属性值以冒号开头   :,如 android:process  = “ :xxxx ”
       表示:将为 app 创建一个私有进程,其他 app 无法访问,进程名称是:包名: xxxx
4、当属性值以小写字母开头,如 android:process = “xxxx”
       表示:这个进程是对外公开的,其他app可以访问它,进程名称是:xxxx

注意:a) 和四大组件节点都可设置
             b)设置 可以指定app的进程名称
             c)若 节点和四大组件都设置了android:process="xxx:xxxx"属性,以组件的属性为准

下面援引官方说明文档:

3)多进程引发的问题

静态成员和单例失效:每个进程保持各自的静态成员和单例,相互独立。
线程同步机制失效:每个进程有自己的线程锁。
SharedPreferences可靠性下降:不支持并发写,会出现脏数据
Application多次创建:不同进程跑在不同虚拟机,每个虚拟机启动会创建自己的Application,自定义Application时生命周期会混乱。
综上,不同进程拥有各自独立的虚拟机,Application,内存空间,由此引发一系列问题,因此在跨进程通信开发时,要注意避开上述问题。

重点来了,因为设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。

让我们到 Framework 中看看新建进程的逻辑,请打开老罗的博客 : Android系统在新进程中启动自定义服务过程(startService)的原理分析

详细介绍了新进程启动的过程,其中我们重点看到 Step 17. ActivityThread.handleCreateService中

`1.  public final class ActivityThread   

3.      ......  

5.      private final void handleCreateService(CreateServiceData data)   
6.          // If we are getting ready to gc after going to the background, well  
7.          // we are back active so skip it.  
8.          unscheduleGcIdler();  

10.          LoadedApk packageInfo = getPackageInfoNoCheck(  
11.              data.info.applicationInfo);  
12.          Service service = null;  
13.          try   
14.              java.lang.ClassLoader cl = packageInfo.getClassLoader();  
15.              service = (Service) cl.loadClass(data.info.name).newInstance();  
16.           catch (Exception e)   
17.              if (!mInstrumentation.onException(service, e))   
18.                  throw new RuntimeException(  
19.                      "Unable to instantiate service " + data.info.name  
20.                      + ": " + e.toString(), e);  
21.                
22.            

24.          try   
25.              if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);  

27.              ContextImpl context = new ContextImpl();  
28.              context.init(packageInfo, null, this);  

30.              Application app = packageInfo.makeApplication(false, mInstrumentation);  
31.              context.setOuterContext(service);  
32.              service.attach(context, this, data.info.name, data.token, app,  
33.                  ActivityManagerNative.getDefault());  
34.              service.onCreate();  
35.              mServices.put(data.token, service);  
36.              try   
37.                  ActivityManagerNative.getDefault().serviceDoneExecuting(  
38.                      data.token, 0, 0, 0);  
39.               catch (RemoteException e)   
40.                  // nothing to do.  
41.                

43.           catch (Exception e)   
44.              if (!mInstrumentation.onException(service, e))   
45.                  throw new RuntimeException(  
46.                      "Unable to create service " + data.info.name  
47.                          + ": " + e.toString(), e);  
48.                
49.            
50.        

52.      ......  

54.  ` ![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)

看到这行 Application app = packageInfo.makeApplication(false, mInstrumentation); 在这里创建了 Application 。

解决方案

获取当前运行进程的名称:



1.  方案1
2.  public static String getProcessName(Context cxt, int pid)   
3.      ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);  
4.      List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();  
5.      if (runningApps == null)   
6.          return null;  
7.        
8.      for (RunningAppProcessInfo procInfo : runningApps)   
9.          if (procInfo.pid == pid)   
10.              return procInfo.processName;  
11.            
12.        
13.      return null;  
14.    
15.  目前网上主流的方法,但效率没有方案2高,感谢由王燚同学提供的方案2

17.  方案2
18.  public static String getProcessName() 
19.    try 
20.      File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
21.      BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
22.      String processName = mBufferedReader.readLine().trim();
23.      mBufferedReader.close();
24.      return processName;
25.     catch (Exception e) 
26.      e.printStackTrace();
27.      return null;
28.    
29.  
30.  然后在 Application 的 onCreate 中获取进程名称并进行相应的判断,例如:

32.  String processName = getProcessName(this, android.os.Process.myPid());

34.  if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) //判断进程名,保证只有主进程运行
35.      //主进程初始化逻辑
36.      ....
37.  


4)如何实现跨进程通讯?

使用AIDL

Android四大组件之Service

文章目录

Android四大组件之默默劳动的Service

什么是Service

  • Service是Android当中实现后台运行的解决方案,它非常适合执行那些不需要和用户交互而且还要求长期运行的任务.
  • Service的执行不依赖于任何用户界面,即使程序被迫切换到后台,或者用户打开了另外一个应用程序,Service仍然能够保持正常运行.
  • 需要注意的是,Service并不是运行在一个独立的进程当中的,而是依赖于创建Service时所在的应用程序的进程
  • 当某个应用进程被杀掉的时候,所有依赖与该进程的Service也会停止运行
  • 实际上Service并不会自动开启线程,所有的代码都是默认运行在主线程当中的
  • 也就是说我们需要在Service的内部手动创建子线程,并在这里执行具体的任务.否则有可能会出现主线程被阻塞的情况

Android多线程编程

  • 当我们需要执行一些耗时操作的时候,比如发起一条网络请求的时候,考虑到网络的情况,服务器未必能够立刻去响应我们的请求,如果不将这类操作放在子线程当中去执行,就会导致主线程被阻塞,从而影响用户对软件的正常使用.

线程的基本用法

  • Android多线程和Java当中的多线程,使用的语法也比较相似,如果定义一个线程只需要创建一个类继承自Thread,然后重写父类的run()方法,并在里面编写耗时逻辑即可,如下所示:
class MyThread : Thread 
    override fun run() 
        //编写具体代码逻辑
    

  • 那么如何启动这个线程呢,其实很简单,只需要创建MyThread的实例,然后调用它的start()方法,这样run()方法中的代码就会在子线程当中运行了,如下所示
val myThread = MyThread()
myThread.start()
  • 使用继承的方式使得代码的耦合度还是比较高的,我们更多的情况下会选择实现Runnable接口的方式来定义一个线程,如下所示
class MyThread : Runnable 
    override fun run() 
        //编写具体的代码逻辑
    

  • 如果使用这种方法,启动线程的方法就要用下面这种方式
val myThread = MyThread()
Thread(myThread).start()
  • 可以看到Thread的构造函数接受一个Runnale参数,而我们创建的MyThread实例正是一个实现了Runnale接口的对象,所以可以直接将他传入到Thread的构造函数当中,接着调用Thread的start()方法,run()方法当中的代码逻辑就会在子线程当中运行了.
  • 当然如果不想专门在定义一个类去实现Runnale接口,也可以直接使用Lambda的方式,这种写法比较常用
Thread 
    //编写具体的代码逻辑
.start()
  • 以上几种线程的使用方式,在Java中创建和启动线程的方式也是一样,而在Kotlin当中还给我们提供了一种更加简单的开启线程的方式,写法如下:
thread 
    //编写具体的代码逻辑

  • 这里的thread是Kotlin内置的一个顶层函数,我们只需要在Lambda表达式中编写具体的逻辑,连start()方法都不需要调用,thread函数在内部全部帮我们处理好了
  • 下面是就Android多线程和Java多线程不同的地方了

在子线程中更新UI

  • 和许多的GUI一样,Android的UI也不是线程安全的,也就是说,想要更新应用程序里的UI元素,必须在主线程进行,否则就会出现异常
  • 新建一个AndroidThreadTest项目来验证一下,创建完成之后修改activity_main.xml的代码
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/changeTextBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello world"
        android:textSize="20sp" />
    
</RelativeLayout>
  • 布局文件当中定义了两个控件,TextView用于在屏幕的正中央显示一个Hello World字符串,Button用于改变TextView中显示的内容,我们希望在点击Button后可以把TextView中显示的字符串改成Nice to meet you
  • 接下来修改MainActivity当中的代码
class MainActivity : AppCompatActivity() 
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        changeTextBtn.setOnClickListener 
            thread  
                textView.text = "nice to meet you"
            
        
    

  • 可以看到我们在按钮的点击事件里面开启了一个子线程,然后在子线程当中调用TextView的setText()方法将显示的字符改成了nice to meet you
  • 需要注意的是我们是在子线程当中更新UI
  • 运行程序之后会发现程序崩溃了,报下面的错

  • 由此可以证实Android确实不允许在子线程中进行UI操作的,但是有些时候我们又必须在子线程里面执行一些耗时的任务,然后根据任务的执行结果来更新相应的UI控件
  • 对于这种情况,Android提供了一套异步消息处理机制,完美解决了在子线程中更新UI操作的问题,修改MainActivity当中的代码逻辑如下所示:
class MainActivity : AppCompatActivity() 
    val updateText = 1
    private val handler = object : Handler(Looper.getMainLooper()) 
        override fun handleMessage(msg: Message) 
            //在这里可以进行UI操作
            when(msg.what) 
                updateText -> textView.text = "Nice to meet you"
            
        
    
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        changeTextBtn.setOnClickListener 
            thread 
                val msg = Message()
                msg.what= updateText
                handler.sendMessage(msg) //将Message对象发送出去
            
        
    

  • 这里先定义了一个整形变量updateText,用来更新TextView这个动作,然后新增了一个Handler对象,并重写了父类的handlerMessage()方法这里对具体的Message进行处理,如果发现Message的what字段的值,等于updateText,就将TextView显示的内容改成Nice to meet you
  • 然后再按钮的点击事件当中,这次并没有直接在子线程当中更新UI的内容,而是先创建了一个Message对象的实例,然后调用该实例的setWhat()方法设置它的what字段值为updateText,然后调用handler的sendMessage()方法将这条Message发送出去,很快Handler就能够收到这条Message,并在handleMessage()方法当中对这条消息进行处理,然后我们UI就自然而然地被处理了.
  • 接下来分析Android异步消息处理机制到底是如何进行工作的

解析异步消息处理机制

  • Android中的异步消息处理主要分为4个部分进行组成:Message,Handler,MessageQueue和Looper

Message

  • Message是在线程之间传递消息的,它可以在内部携带少量地信息,用于在不同线程之间传递数据,在上个示例当中使用了Message地what字段,除此之外还可以使用arg1和arg2字段来携带一些整形数据,使用obj字段携带一个Object对象.

Handler

  • Handler顾名思义地意思就是,它主要用于发送和处理消息,发送消息一般是使用Handler的sendMessage()方法,post()方法等,而发出的消息经过一系列的辗转处理之后,最终会传递到Handler的handlerMessage()方法当中.

MessageQueue

  • MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息,这部分消息会一直存放在消息队列当中,等待被处理,每一个线程中只会有一个MessageQueue对象.

Looper

  • Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限的循环中,然后每当发现MessageQueue中存在一条消息的时候,就会将他取出,并传递到Handler的handlerMessage()方法当中,每个线程只会有一个Looper对象

异步消息的整个流程

  • 首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法,然后当子线程中需要进行UI操作的时候,就会创建一个Message对象,并通过Handler将这条消息发送出去,之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理的消息,最后分发回Handler的handleMessage()方法中.
  • 由于Handler的构造函数中我们传入了Looper.getMainLooper(),所以此时handleMessage()方法中的代码也会在主线程当中运行,于是我们就可以安心的进行UI操作了,一整个异步消息的执行流程图如下所示


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GhpgkCuk-1671952178078)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E5%AE%89%E5%8D%93/image-20221222232358115.png)]

  • 一条Message经过上述流程的辗转之后,也就从子线程进入了主线程,从不能更新UI变成了可以更新UI,整个异步消息处理的核心思想就是如此.

使用AsyncTask

  • 为了能够更加方便我们在子线程中对UI进行操作,Android还提供了另外一些好用的工具,比如AsyncTask,借助AsyncTask,即使你对异步消息机制完全不了解,也可以十分简单的从子线程切换到主线程
  • AsyncTask背后原理也是基于异步消息处理机制的,只是Android帮我们做了很好的封装而已.
  • AsyncTask是一个抽象类,所以如果我们想使用它,就必须创建一个子类去继承它,在继承的时候我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下.
    • Params.在执行AsyncTask时需要传入的参数,可用于在后台任务当中使用
    • Progress.在后台任务执行的时候,如果需要在界面显示当前进度,则使用这里指定的泛型作为进度单位.
    • Result.当任务执行完毕之后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型.
  • 因此定义一个最简单的AsyncTask就可以写成如下这种形式
class DownloadTask : AsyncTask<Unit, Int, Boolean>() 
    ...

  • 这里我们将AsyncTask的第一个泛型参数指定为Unit,表示在执行AsyncTask的时候不需要传入参数给后台任务
  • 第二个泛型参数指定为Int,表示使用整形数据来作为进度显示的单位
  • 第三个泛型参数指定为Boolean,则表示用布尔类型来反馈执行的结果
  • 我们在继承了AsyncTask抽象类之后,还需要重写AsyncTask中的几个方法来完成对任务的定制,经常需要重写的方法有以下4个:
  1. onPreExecute() : 这个方法在后台任务执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等
  2. doInBackground(Params…) 这个方法中所有的代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务,任务一旦完成,就可以通过return语句将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Unit,就可以不返回任务的执行结果,需要注意的是在这个方法当中是不能进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用publishProgres(Progress…)方法来完成.
  3. onProgressUpdate(Progress…)当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate(Progress…)方法很快就会被调用,该方法中携带的参数就是在后台任务中传递过来的,在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新.
  4. onPostExecute(Result)当后台任务执行完毕并通过return语句返回时,这个方法很快就会被调用,返回的数据作为参数传递到此方法当中,可以利用返回的数据进行一些UI操作,比如说提醒任务执行的结果,以及关闭进度条对话框等.
  • 因此,一个比较完整的自定义AsyncTask就可以写成如下形式:
class DownloadTask : AsyncTask<Unit, Int, Boolean>() 
    override fun onPreExecute() 
        progressDialog.show() //显示进度对话框
    
    override fun doInBackground(vararg params : Unit?) = try 
        while(true) 
            val downloadPercent = doDownload() //这是一个虚构方法
            publishProgress(downloadPercent)
            if(downloadPercent >= 100) 
                break
            
        
        true
     catch (e:Exception) 
        false
    
    
    override fun onProgressUpdate(vararg values: Int?) 
        //在这里更新下载速度
        progressDialog.setMessage("Download $values[0]%")
    
    
    override fun onPostExecute(result: Boolean) 
        progressDialog.dismiss() //关闭进度条对话框
        //在这里提示下载结果
        if(result) 
            Toast.makeText(context, "Download succeeded", Toast.LENGTH_SHORT).show()
         else 
            Toast.makeText(context, "Download failed", Toast.LENGTH_SHORT).show()
        
    

  • 在这个DownloadTask中,在doInBackground()方法里执行具体的下载任务.这个方法里面的代码都是在子线程中运行的,因而不会影响主线程的运行
  • 这里虚构一个doDownload()函数用于计算当前的下载进度,假设这个方法已经存在了,在得到下载的进度之后就是考虑如何把它显示在界面上了,由于doInBackgroun()方法时在子线程当中运行的,在这里肯定不能进行UI操作,所以我们可以调用publishProgress()方法并传入当前的下载进度,这样onProgressUpdate()方法就会很快被调用,在这里就可以进行UI操作了.
  • 当下载完成之后,doInBackground()方法会返回一个布尔变量,这样onPostExecute()方法很快就会被调用,这个方法也是在主线程中进行的,然后在这里我们会根据下载的结果弹出相应的Toast提示,从而完成整个DownloadTask任务.
  • 简单来说,使用AsyncTask的诀窍就是,在doInBackground()方法中执行某个具体耗时的任务,在onProgressUpdate()方法中进行UI操作,在doPostExecute()方法中执行一些任务的收尾工作.
  • 如果想要启动这个任务,只需要编写一下代码即可:
DownloadTask().execute()
  • AsyncTask相比于之前异步消息处理机制来说,变得简单了很多,也不需要专门使用一个Handler来发送和接收消息,只需要调用一下publishProgress()方法,就可以轻松地从子线程切换到主线程了.

Service的基本用法

  • 作为Android的四大组件之一,Service由于多非常重要的知识

定义一个Service

  • 新建一个ServiceTest项目,点击com.zb.servicetest->New->Service->Service,然后就可以定一个Service

  • 可以看到Service中的初始代码如下所示
class MyService : Service() 

    override fun onBind(intent: Intent): IBinder 
        TODO("Return the communication channel to the service.")
    

  • 可以看到MyService类继承系统的Service类,在该类中有一个onBind()方法,这个方法时Service中唯一的抽象方法,所以必须在子类里实现
  • 想要在Service中处理一些事情,那么就需要重写Service中的一些其他方法了,如下所示
    override fun onCreate() 
        super.onCreate()
    

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
        return super.onStartCommand(intent, flags, startId)
    

    override fun onDestroy() 
        super.onDestroy()
    
  • 在类中又重写了onCreate(),onStartCommand()和onDestory()这三个方法,他们是每一个Service最常用的三个方法
  • 其中onCreate()方法会在Service创建的时候调用
  • onStartCommand()方法会在每次Service启动的时候调用
  • onDestroy()方法会在Service销毁的时候进行调用
  • 通常情况下,如果我们希望Service一旦启动就立即去执行某个动作,那么我么就将这个代码逻辑写在onStartCommand()方法当中,而当当Service被销毁的时候,我们可以在onDestory()方法当中回收那些不再使用的资源.
  • 另外需要注意的是,每一个Service都需要在AndroidManifest.xml文件中进行注册才能生效,这也是四大组件共有的特点,但是通过刚才的创建Service的方法,AS已经很智能的帮我们进行了注册.

启动和停止Service

  • 定义好一个Service之后,需要考虑的就是如何来启动和停止这个Service了,启动和停止的方法,是借助Intent来进行实现的,下面在ServiceTest项目当中尝试启动以及停止MyService
  • 首先修改activity_main.xml中的代码,如下所示
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/startServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/stopServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
  • 在布局文件当中添加了两个按钮,分别用于启动和停止Service,然后修改MainActivity当中的代码,如下所示
class MainActivity : AppCompatActivity() 
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startServiceBtn.setOnClickListener 
            //启动Service的方式
            val intent = Intent(this, MyService::class.java)
            startService(intent)
        
        
        stopServiceBtn.setOnClickListener 
            //停止Service的方法
            val intent = Intent(this, MyService::class.java)
            stopService(intent)
        
    

  • 上述就是启动和停止Service的方法了,那么如何证实Service已经启动或者停止了呢?
  • 最简单的方法就是在Service当中打印日志,如下所示
class MyService : Service() 
    private val tag = "MyService"

    override fun onBind(intent: Intent): IBinder 
        TODO("Return the communication channel to the service.")
    

    override fun onCreate() 
        super.onCreate()
        Log.d(tag, "onCreate executed")
    

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
        Log.d(tag, "onStartCommand executed")
        return super.onStartCommand(intent, flags, startId)
    

    override fun onDestroy() 
        super.onDestroy()
        Log.d(tag, "onDestroy executed")
    

  • 启动程序点击按钮分别会打印下面的日志

  • 以上就是Service启动和停止的基本用法,但是从Android8.0开始,应用后台的功能被大幅度的削减,现在只有当应用程序在前台保持可见的时候,Service才能平稳的运行,一旦进入后台,Service随时都有被系统回收的可能.
  • 之所以做这样的改动,是因为要防止恶意的应用程序长期在后台占用手机资源
  • 再回到onCreate()和onStartCommand()方法,onCreate()方法是在Service第一次创建的时候调用的,而onStartCommand()方法则在每次启动Service的时候都会调用
  • 刚才在点击Start Service按钮的时候,Service此时还没有创建过,所以两个方法都会执行,所以两个方法当中的日志都被打印出来了,之后如果连续多点击几次Start Service按钮,就只有onStartCommand()方法会进行执行了.

Activity和Service进行通信

  • 通过上面的代码示例可以发现,虽然Service是在Activity当中启动的,但是在Service启动之后,Activity和Service就没有什么关系了.
  • 在Activity里面调用了startSetvice()方法来启动MyService,然后MyService中的相关方法执行完毕之后,Service一直就处在了运行状态当中,但之后具体是什么逻辑,Activity就控制不了了.
  • 这就类似于Activity通知了Service一下,你可以启动了,然后Service就去忙自己的事情了,但是Activity并不知道Service到底做了什么事情,以及完成的如何.
  • 如果我们想要让Activity和Service的关系更加紧密一些,比如在Activity当中去指挥Service干什么,Service就去干什么,这就要用到onBind()方法了
  • 比如说,我们想要在Service中提供一个下载方法,我们在Activity当中控制什么时候开始下载,以及随时查看下载的进度
  • 实现这个方法的思路是创建一个专门的Binder对象来对下载进行管理,修改MyService当中的代码如下所示:

  • 新建了一个DownloadBinder类,并让他继承自Binder,然后在它的内部提供了开始下载和下载进度两个方法(模拟方法)
  • 下面看看如何在Activity中调用Service里面的这些方法
  • 首先需要在布局文件当中新增两个按钮,修改该activity_main.xml中的代码如下所示
    <Button
        android:id="@+id/binServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/unBindServiceBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="unBindServiceBtn" />
  • 这两个按钮分别是用于绑定和取消绑定Service
  • 所以我们现在需要让Activity进行绑定,然后Activity就可以调用Service里的Binder提供的方法了,修改MainActivity当中的代码,如下:

  • 在代码当中首先创建了一个ServiceConnection的匿名类实现,并在里面重写了onServiceConnected()方法和onServiceDisconnected()方法.
  • onServiceConnected()方法会在Activity和Service成功绑定的时候进行调用,而onServiceDisconnected()方法只有在Service的创建进程崩溃或者被杀掉的时候,才会调用,这个方法不太常用
  • 而在onServiceConnected()方法当中,又通过上下转型,得到了DownloadBinder的实例,有了这个实例,Activity和Service的关系就变得十分的紧密了
  • 我们就可以在Activity当中根据场景调用DownloadBinder中的任何public方法,这样就实现了指挥Service干什么,Service就干什么
  • 当然此时Activity和Service其实还没有进行绑定呢,绑定的操作是在Bind Service按钮当中进行完成的
  • 我们在该按钮的点击事件当中,先构建了一个Intent对象,然后调用bindService()方法,该方法接收三个参数
  • 第一个参数是刚刚构建出来的Intent对象,第二个参数是前面创建出来的ServiceConnection实例,第三个参数是一个标志位,这里传入Context.BIND_AUTO_CREATE,表示Activity和Service进行绑定之后自动创建Service,这就会使得MyService得onCreate()方法得到执行.
  • 如果我们想要解除绑定调用unbindService()方法就可以了,这也就是Unbind Service按钮点击事件里面实现的功能
  • 这样运行程序之后,点击binServiceBtn按钮,就会看到日志中打印出以下得内容

  • 可以看到,首先MyService得onCreate()方法得到了执行,然后startDownload()和getProgress()都得到了执行,说明我们确实在Activity当中调用了Service里面提供的方法.
  • 另外需要注意的是,Service在整个应用程序范围内都是通用的,也就是说MyService不仅可以和MainActivity进行绑定,还可以和任何一个其他的Activity进行绑定,而且在绑定之后,它们都能够获得相同的DownloadBinder实例.

Service的生命周期

  • 和Activity一样,Service也拥有自己得生命周期,在上面得代码示例当中,我们使用到的onCreate(),onStartCommand(),onBind()和onDestory()等方法都是在Service的生命周期内可能回调的方法.
  • 在项目的任何位置调用了Context的startService()方法,相应的Service就会立马启动,并且回调onStartCommand()方法,如果这个Service在之前还没有创建过,还会在onStartCommand()方法回调之前,调用一次onCreate()方法,用来创建Service.
  • Service启动之后就会一直保持运行状态,知道stopService()方法或者stopSelf()方法被调用,或者系统回收之后,就会停止运行.
  • 虽然没当调用一次startService()方法,onStartCommand()方法就会调用一次,但是Service实例只会存在一个,所以只需要调用一次停止方法,Service就能停止运行.
  • 另外还可以调用Context的binService()来获取一个Service持久连接,这时候会回调Service当中的onBind()方法,类似的,如果这个Service之前还没有创建过,那么会优先回调onCreate()方法进行Service的创建,之后调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就可以自由的和Service进行通信了,只要调用方和Service之间的连接还没有断开,Service就会一直保持运行状态直到被系统回收.
  • 当调用了startService()方法后,再去调用stopService()方法,这时Service的onDestory()方法就会执行,表示Service已经被销毁了,类似的当调用了bindService()方法后,再去调用unbinService()方法,onDestory()方法也会得到执行
  • 但是如果对一个Service即调用了startService()方法,又调用了bindService()方法,这两种方法的调用都会使得Service处在运行状态,但是Android系统提供了一种机制就是,当一个Service必须要让以上两种条件同时不满的时候,Service才能被销毁,所以在这种情况下需要同时调用stopService()和unbindService()方法,onDestory()方法才会进行调用.
  • 以上就是Service整个的生命周期,相比较于Activity的生命周期来讲还是比较简单的.

Service的更多的技巧

使用前台Service

  • 从Android8.0之后开始,只有当应用保持在前台可见状态的情况下,Service才能够保证稳定的运行,一旦应用进入后台,Service随时都有可能被系统回收
  • 如果希望Service能够一直保持运行状态,就可以考虑前台Service,前台Service和普通Service最大的区别就是,它一直会有一个正在运行的图标在系统的状态栏进行显示,下拉状态栏之后就可以看到更多的相信的信息,非常类似于通知的效果
  • 由于状态栏中一直有一个正在运行的图标,相当于我们的应用以另外一种形式保持在前台可以见的状态,所以系统不会倾向于回收前台Service.
  • 另外用户也可以通过下拉状态栏清楚的知道当前什么应用正在运行,因此不会存在某一些恶意的应用长期在后台偷偷他占用手机资源的情况.
  • 下面来修改MyService当中的代码,创建一个前台Service

  • 可以看到只是修改了onCreate()当中的代码,在里面创建了一个通知,但是这次在构建Notification对象后并没有使用NotificationManager将通知显示出来,而是调用了startForeground()方法
  • 这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法当中的第一个参数,第二个参数则是构建Notification对象.
  • 调用startForeground()方法后就会让MyService变成一个前台Service,并在系统状态栏中显示出来.
  • 另外从Android9.0开始,使用前台Service必须要在AndroidManifest.xml文件中进行权限声明
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

注意注意!!!->PendingIntent是Android框架的重要组成部分。Android 12创建的每个PendingIntent对象必须

以上是关于Android之Service设置android:process作用的主要内容,如果未能解决你的问题,请参考以下文章

Android之Service

Android学习笔记之Service

Android安卓进阶技巧之——Android Service 服务

Android之Service

Android之Service

Android探索之Service全面回顾及总结