Android四大组件之服务

news2024/12/29 10:02:01

为什么要使用服务呢?

从上面的文字说,我们知道这个服务是用于执行长期后台运行的操作。有些时候,我们没有界面,但是程序仍然需要工作。比如说,我们播放音乐,在后台播放音乐。比如说,我们下载任务,在后台下载文件。这些都是没有界面 的后台运行程序,这些都是用服务做的。

第二个原因是什么呢?先给大家讲几个概念:0
1、前台进程:可以理解为是最顶部的,直接跟用户交互的。比如说我们操作的Activity界面.
2、可见进程:可以见的,但是不操作的,比如说我们在一个Activity的顶部弹出一个Dialog,这个Dialog就是前台进程,但是这个Activity则是可见进程。(操作的只是Dialog,而不是Activity但可见)
3、服务进程:服务可以理解为是忙碌的后台进程,虽然是在后台,但是它很忙碌。
4、后台进程:后台进程就是退隐到后台,不做事的进程。(比如按home键程序在后台但没有被干掉/)
5、空进程:空进程是不做事的,没有任何东西在上面跑着,仅作缓存作用。(比如按返回键退出此时就是空进程,销毁掉的进程就是空进程)
假设,内存不够用了,会先杀谁呢?
首先杀的是空进程,要是还不够就杀后台进程,要是还不够,那么就杀服务,但是服务被杀死以后,等内存够用了,服务又会跑起来了。

所以:如果我们需要长期后台操作的任务,使用Service就对了!其实Framework里多数是服务。如果我们进行音乐播放,即使退到了后台,也可以播放,我们使用服务完成吧!如果我们下载东西,退到后台也能下载,那么我们就使用服务吧!如果我们在不停地记录日志,那我们就用服务吧!

服务是Android中实现程序后台运行的方案,他非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

每一个服务都需要在配置文件AndroidManifest.xml文件里进行生命,怎么生命呢?
使用标签,其实跟前面的activity,广播接收者receiver一样生命。
通过Context.startService()来开启服务,通过Context.stop()来停止服务。当然啦,还有一种启动形式就是通过Context.bindService()的方法。

如果面试问到:服务用于执行耗时操作,这是对的吗?
如时服务直接执行耗时操作,也会出现anr。首先ANR的意思是android no response,也就是无响应或者理解为操作超时。
如果在服务中直接做耗时操作,也是会出现ANR异常的。服务可以长期在后台运行,所以你可以这么做:如果要做耗时操作,比如说网络的访问,数据库的读写之类的,可以开线程去做。
首先,创建一个类,继承Service,就像我们之前写Activity要继承自Activity,而广播则继承自BroadcastReceiver。

package com.example.servicetest;

import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable

public class MyService extends Service {

    //onCreate()方法再服务创建的时候调用
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
    
    }

    //onStartCommand()方法在每次服务启动的时候调用
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("MyService", "onStartCommand executed");
        return super.onStartCommand(intent, flags, startId);
    }

    //onDestroy()方法在服务销毁的时候调用
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyService", "onDestroy executed");
    }
}

接着,我们写一个Activity去控制服务:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startService=(Button) findViewById(R.id.start_service);
        Button stopService=(Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);@Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.start_service:
                Intent startIntent=new Intent(this,MyService.class);
                startService(startIntent);//启动服务
                break;
            case R.id.stop_service:
                Intent stopIntent=new Intent(this,MyService.class);
                stopService(stopIntent);//停止服务
                break;

onCreate()方法是在服务第一次创建的时候调用的,而onStartCommand()方法则在每次启动服务的时候都会调用,由于是我们第一次点击Start Service按钮,服务此时还未创建过,所以两个方法都会执行,之后如果你再连续多点击几次Start Service按钮,你会发现只有onStartCommand()方法可以得到执行。
前面的开启服务方式,有一个弊端。就是没法进行通讯。所以我们接直来呢会学习另外一种启动服务的方式–通过绑定服务的形式来启动服务。

绑定服务,对应的停止服务则是解绑服务了!

活动和服务进行通信(绑定服务)

例子:通过创建一个专门的Binder对象来对下载功能进行管理

public class MyService extends Service {

    private DownloadBinder mBinder=new DownloadBinder();


    class DownloadBinder extends Binder
  {
      public void startDownload()
      {
          Log.d("MyService", "startDownload: executed");
      }
      public int getProgress()
      {
          Log.d("MyService", "getProgress: executed");
          return 0;
      }
  }

    public MyService() {
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。

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) {
    //多态  IBinder a=new MyService.DownloadBinder();
    //多态的类型判断service对象是MyService.DownloadBinder类型的实例
        if(service instanceof MyService.DownloadBinder) {
            //这里的downloadBinder就是MyService返回的mBinder
            //向下转型得到DownloadBinder的实例,可以调用DownloadBinder的任何方法
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};
 @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 v) {
        switch (v.getId())
        {
    case R.id.bind_service:
                //点击后回调onBind()方法
                Intent bindIntent=new Intent(this,MyService.class);
                //BIND_AUTO_CREATE表示绑定服务的时候就会没有创建服务就会去创建服务,如果已经创建就不会再创建
                //绑定后就返回Binder给onServiceConnected
                bindService(bindIntent,connection,BIND_AUTO_CREATE);//绑定服务
                break;
            case R.id.unbind_service:
                unbindService(connection);//解绑服务
                break;
         }


首先创建一个ServiceConnection的匿名类,在里面重写了onServiceConnected和onServiceDisconnected方法,这两个方法分别会在绑定成功后以及活动与服务的连接断开的时候调用。我们可以根据活动的具体场景来调用DownloadBinder中的任何public方法,从而实现指挥服务干什么服务就去干什么的功能。做了个简单的测试,在onServiceConnected()方法中调用了用DownloadBinder中startDownload和getProgress方法。
绑定服务在Bind Service按钮的点击事件里完成,构建一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数是刚构建的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
当需要解除活动和服务之间的绑定需要调用一下unbindService()方法。
在这里插入图片描述
首先是MyService的onCreate()方法得到了执行,然后startDownload()和getProgress()方法都得到了执行,说明我们确实已经在活动里成功调用了服务里提供的方法。
任何一个服务在整个应用程序的范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以任何一个其他活动进行绑定,而且绑定完成后他们可以获取相同的DownloadBinder实例。
这样子我们就可以控制服务了,假设说我们有一个服务在后台跑着,用它来播放音乐的,因为我们音乐可以后台播放呀,对吧!这个时间 ,我们需要控制音乐的播放和暂停了,就可以通过这种形式去控制音乐了
总结一下绑定服务的特点:
1、绑定服务在系统设置里是没有显进服务正在跑着的;
2、如果onBind方法返回的是null,那么onServiceConnected方法不会被调用;
3、绑定服务的生命周期跟Activity是不求同时生,但求同时死,Activity没了,服务也要解绑;
4、服务在解除绑定以后会停止运行,执行unBind方法—>onDestroy方法;
5、绑定服务开启的服务,只可以解绑一次,多次解绑会抛异常;
6、绑定的connection要跟解绑的connection要对应着,否则没法解绑。

绑定服务和开启服务的区别

绑定服务:可以间接调用服务里面的方法;如果绑定的Activity被销毁了,服务也会跟着销毁。当Activity销毁时要释放服务资源即unbind否则会导致泄露问题,也就是说service的与activity不求同生但求同死。
开启服务:不可以调用服务里面的方法;如果开启服务的Activity销毁,服务还可以长期的在后台运行。
既要保证服务长期在后台运行,又想去调用服务里面的方法:混合使用服务

混合服务

start->bind->unbind查看效果
unbindService之后service并没有destroy,所以说以start开启服务之后,只有stop才可以正常的销毁服务。
start->bind->stop bind之后未unbind,stop是不会起作用的,也就是说当bind之后必须解绑才可以正常的释放资源。

推荐的混合开发服务模式:

  1. 开启服务->为了确保服务可以长期于后台运行
  2. 绑定服务->为了可以进行通讯
  3. 调用服务内部的方法,该干嘛就干嘛,比如说,我们控制音乐的播放/暂停 /停止/快进
  4. 退出Activity,要记得解绑服务->释放资源
  5. 如果不使用服务了,要让服务停止,那么就调用stopService

服务生命周期

在这里插入图片描述
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand方法。如果这个服务之前未创建过,onCreate()方法会先于onStartCommand方法执行 。服务启动后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。虽然每次调用一次startService()方法,onStartCommand方法就会执行一次,但实际上每个服务都只会存在一个实例。不管你调用多少次startService(),只需调用一次stopService()或stopSelf()方法,服务就会停止下来。
还可以调用Context的bindService()获取一个服务的持久连接,这时就会回调服务的onBind()方法。类似的如果这个服务之前未创建过,onCreate()方法会先于onBind方法执行 。之后调用方就可以获取到onBind()方法返回的IBinder对象的实例,这样就能自由地和服务进行通信(比如绑定成功后,活动调用服务的相关方法)
当调用了startService()方法后,又去调用stopService()方法,这是服务中的onDestory()方法就会执行,表示服务已经销毁。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestory()方法也会执行。但是注意当我调用startService()又调用bindService(),这个时候需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

前台服务

发送通知

  @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyService", "onCreate executed");
        String ID="com.example.servicetest";
        String NAME="Channel One";
        Intent intent=new Intent(MyService.this,MainActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
        NotificationCompat.Builder notification;
        NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O)
        {
            NotificationChannel channel=new NotificationChannel(ID,NAME,manager.IMPORTANCE_HIGH);
            channel.enableLights(true);
            channel.setShowBadge(true);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            manager.createNotificationChannel(channel);
            notification=new NotificationCompat.Builder(MyService.this).setChannelId(ID);
        }else {
            notification=new NotificationCompat.Builder(MyService.this);
        }
        notification.setContentTitle("标题")
                .setContentText("内容")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
                .setContentIntent(pendingIntent)
                .build();
        Notification notification1=notification.build();
        startForeground(1,notification1);
    }

使用IntentService

如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR的情况,这个时候需要用Android多线程技术。我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。
Android提供了一个IntentService类,首先提供一个无参的构造函数,并且需要在其内部调用父类的有参构造。然后要在子类中去实现onHandleIntent这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR问题,因为这个方法是在子线程中运行的。这个服务在运行结束之后会自动停止的

package com.example.servicetest;

import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

import androidx.annotation.Nullable;


public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MyIntentService");//调用父类的有参构造函数
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("MyIntentService", "onDestroy: executed");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        //打印当前线程的id
        Log.d("MyIntentService", "Thread id is: "+Thread.currentThread().getId());
    }

}

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button startIntentService=(Button) findViewById(R.id.start_intent_service);
        startIntentService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.start_intent_service:
                //打印主线程的id
                Log.d("MainActivity", "Thread id is"+Thread.currentThread().getId());
                Intent intentService=new Intent(this,MyIntentService.class);
                startService(intentService);
                break;
            default:
                break;
        }
    }

在这里插入图片描述

隐式意图服务(银行例子)

绑定服务

package com.example.bankservicedemo;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;

import com.example.bankservicedemo.interfaces.INormalUserAction;

public class NormalUserActivity extends AppCompatActivity {

    private static final String TAG = "NormalUserActivity";
    private INormalUserAction mNormalUserAction;
    private NormalUserConnection mNormalUserConnection;
    private boolean mIsBind;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_normal_user);
        doBindService();
    }

    //绑定服务
    private void doBindService() {
        Log.d(TAG, "doBindService: ");
        //由于service那边是返回action,所以需要用隐式意图来进行绑定
        Intent intent = new Intent();
        intent.setAction("com.example.ACTION_NORMAL_USER");
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        //Android5.0后,要在服务使用隐式意图,需要加上目标绑定包名packName,应用的包名
        intent.setPackage("com.example.bankservicedemo");
        mNormalUserConnection = new NormalUserConnection();
        mIsBind = bindService(intent, mNormalUserConnection, BIND_AUTO_CREATE);

    }
    private class NormalUserConnection implements ServiceConnection{

        //服务连接成功时
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: "+name);
            mNormalUserAction = (INormalUserAction) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: "+name);

        }
    }

    //下面三个是按钮点击事件
    public void saveMoneyClick(View view){
        Log.d(TAG, "saveMoneyClick: ");
        mNormalUserAction.saveMoney(10000);
    }
    public void getMoneyClick(View view){
        Log.d(TAG, "getMoneyClick: ");
        mNormalUserAction.getMoney();
    }
    public void loadMoneyClick(View view){
        Log.d(TAG, "loadMoneyClick: ");
        mNormalUserAction.loadMoney();
    }

    //销毁时记得解开服务
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mIsBind&&mNormalUserConnection!=null){
            unbindService(mNormalUserConnection);
            Log.d(TAG, "unbind service.. ");
            mNormalUserConnection=null;
            mIsBind=false;
        }
    }
}

服务创建

package com.example.bankservicedemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;

import androidx.annotation.Nullable;

import com.example.bankservicedemo.impl.BankBossActionImpl;
import com.example.bankservicedemo.impl.BankWorkerActionImpl;
import com.example.bankservicedemo.impl.NormalUserActionImpl;

public class BankService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        String action=intent.getAction();
        if(!TextUtils.isEmpty(action)){
            if("com.example.ACTION_NORMAL_USER".equals(action)){
                return new NormalUserActionImpl();
            }else if("com.example.ACTION_BANK_WORKER".equals(action)){
                return new BankWorkerActionImpl();
            }else if("com.example.ACTION_BANK_BOSS".equals(action)){
                return new BankBossActionImpl();
            }
        }

        return null;
    }
}

隐式服务声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bankservicedemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ServiceTest">
        <activity android:name=".bankWorkerActivity"></activity>
        <activity android:name=".bank_bossActivity" />
        <activity android:name=".NormalUserActivity" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- 服务隐式意图 -->
        <!--  android:exported="true"可以在外部的第三方应用拉起服务-->
        <service
            android:name=".BankService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.ACTION_NORMAL_USER" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.example.ACTION_BANK_WORKER" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.example.ACTION_BANK_BOSS" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
    </application>

</manifest>

Android跨进程通信AIDL

  • AIDL概述
    AIDL意思即Android Interface Definition Language(安卓接口定义语言),用于定义服务器与客户端进行通信的一种描述语言,本质是AIDL其实是android端为我们定义的一个模板文件.aidl,最终还是会编译为.java文件。

在Android中,默认每个应用(application)执行在它自己的进程中,无法直接调用到其他应用的资源,这也符合沙箱(SandBox)的理念。所谓沙箱原理,一般来说用在移动电话业务中,简单地说旨在部分地或全部地隔离应用程序。

Android沙箱技术: Android“沙箱”的本质是为了实现不同应用程序和进程之间的互相隔离,即在默认情况 下,应用程序没有权限访问系统资源或其它应用程序的资源。 每个APP和系统进程都被分配唯一并且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。 每个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。 运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,因此Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序如果想要访问系统资源或者其它应用程序的资源必须在自己的manifest文件中进行声明权限或者共享uid。

  • AIDL的引入
    android开发中一项任务可能需要多个进程相互协作,相互委托,比如支付服务,某app需要进行支付,那么他需要调起第三方支付,进程之间需要通信,当支付完毕,第三方返回支付数据给app进程之间也需要通信,所以说如果一个应用只是单单的一个UI主进程而不涉及多个进程间的通信,那么这个app是不完美的!通过AIDL就可以满足进程间通信的需求,本质上也是通过Binder对象来进行传递数据。

通常,暴露接口方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。

AIDL模拟支付宝支付

1.明确需求
某app需要进行SQ币充值,需要调用第三方支付服务,然后第三方支付服务拉起一个新的Activity提供用户账单信息
2.项目结构
服务端(Server):
在这里插入图片描述
客户端(Client):
在这里插入图片描述
3. 代码实现

  • 编写支付服务和支付界面(Server端)

客户端(Client)通过bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);绑定服务端的服务后执行服务端的onBind() 会返回return mThirdPartPlay;然后执行 public void onServiceConnected(ComponentName name, IBinder service)方法中的IBinder对象就是Server通过调用onBind方法返回的一个间接继承Binder类的对象。私有内部类ThirdPartPayImpl继承ThirdPartPayAction.Stub.Stub类实现了AIDL接口并且继承了Binder类(AIDL通信的本质)。PayAction支付动作类,因为app绑定第三方支付后,当调用requestPay时,service会拉起一个支付的PayActivity,这个Activity也需要与该支付服务做绑定因为支付操作都是在该界面进行的,与服务通信的IBinder对象是return new PayAction()所给。
整个程序中PayActivity绑定了两次,第一次是客户端(return mThirdPartPlay),第二次是拉起的第三方支付(return new PayAction())。

package com.example.aliplay;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class PayService extends Service {
    private static final String TAG = "PayService";
    private ThirdPartPlayImpl mThirdPartPlay;

    public PayService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        String action = intent.getAction();
        Log.d(TAG, "onBind: ->action"+action);
        if(action!=null&&"com.example.aliplay.THIRD_PART_PAY".equals(action)){
            //说明这个是第三方要求我们自付宝进行支付
            mThirdPartPlay = new ThirdPartPlayImpl();
            return mThirdPartPlay;
        }
        return new PayAction();
    }

    public class PayAction extends Binder{
        public void Play(float payMoney){
            Log.d(TAG, "Play money is-->"+payMoney);
            //实际的支付是比较复杂的,比如加密,比如向服务器发起请求,等待服务器的结果
            //支付方法
            if(mThirdPartPlay!=null){
                mThirdPartPlay.paySuccess();
            }
        }
        public  void onUserCancel(){
            //用户点击界面上的取消/退出
            if(mThirdPartPlay!=null){
                mThirdPartPlay.payFailed(1,"user cancel pay");
            }
        }
    }
    private class ThirdPartPlayImpl extends ThirdPartPayAction.Stub{

        private ThirdPartPayResult mCallback;
       //ThirdPartPayResult 参数作为回调接口
        @Override
        public void requestPay(String orderInfo, float payMoney, ThirdPartPayResult callback) throws RemoteException {
            Log.d(TAG, "oderInfo->"+orderInfo+"pay Money->"+payMoney);
            this.mCallback=callback;
            //第三方应用发起请求,打开一个支付界面
            Intent intent = new Intent();
            intent.setClass(PayService.this,PayActivity.class);
            intent.putExtra(Constants.KEY_BILL_INFO,orderInfo);
            intent.putExtra(Constants.KEY_PAY_MONEY,payMoney);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//表示Service和Activity跑在不同的任务上
            startActivity(intent);

        }

        public void paySuccess(){
            if(mCallback!=null) {
                try {
                    mCallback.onPaySuccess();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        public void payFailed(int errorCode,String errorMsg) {
            if (mCallback != null) {
                try {
                    mCallback.onPayFailed(errorCode, errorMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

AIDL文件
requestPay方法中ThirdPartPayResult参数作为回调接口供客户端使用。说到这提下接口,接口可以隐藏内部细节,对调用者来说使用方便,对开发者来说接口使代码的健壮性增强,对调用者所使用的权限做了一定的限定。接口提高了开发效率,各端人员各司其职(面向接口编程)。

// ThirdPartPayAction.aidl
package com.example.aliplay;
import com.example.aliplay.ThirdPartPayResult;
// Declare any non-default types here with import statements

interface ThirdPartPayAction {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     //发起支付
  void requestPay(String orderInfo,float payMoney,ThirdPartPayResult callback);
}
// ThirdPartPayResult.aidl
package com.example.aliplay;

// Declare any non-default types here with import statements

interface ThirdPartPayResult {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
      void onPaySuccess();
      void onPayFailed(in int errorcode,in String msg);
}

PayActivity支付交互界面

package com.example.aliplay;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class PayActivity extends AppCompatActivity {

    private boolean mMIsBind;
    private EditText mMPasswordBox;
    private PayService.PayAction mPlayAction;
    private static final String TAG="PayActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pay);
        Log.d(TAG, "onCreate: 重新启动");
        //因为我们的Activity也要跟服务进行通讯,告诉服务支付结果,所以的话我们也要绑定服务
        doBindService();
        initView();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        if (mPlayAction!=null){
            mPlayAction.onUserCancel();
        }
    }

    private void initView() {
        Intent intent = getIntent();
        String orderInfo = intent.getStringExtra(Constants.KEY_BILL_INFO);
        float payMoney = intent.getFloatExtra(Constants.KEY_PAY_MONEY, 0f);
        TextView orderInfoTv = this.findViewById(R.id.order_info_tv);
        orderInfoTv.setText("支付信息:"+orderInfo);
        TextView payMoneyTv = this.findViewById(R.id.pay_money);
        payMoneyTv.setText("支付金额:"+payMoney);
        mMPasswordBox = this.findViewById(R.id.pay_password_input);
        Button commitButton = this.findViewById(R.id.pay_commit);
        commitButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //提交点击了
                String trim=mMPasswordBox.getText().toString().trim();
                if("123456".equals(trim)&&mPlayAction!=null){
                    mPlayAction.Play(payMoney);

                 Toast.makeText(PayActivity.this,"支付成功",Toast.LENGTH_SHORT).show();
                    finish();
                    Log.d(TAG, "onClick: pay finish");
                }else {

                 Toast.makeText(PayActivity.this,"密码错误",Toast.LENGTH_SHORT).show();

               }
            }
        });
    }


    private void doBindService() {
        Intent intent = new Intent(this, PayService.class);
        mMIsBind = bindService(intent,mConnection , BIND_AUTO_CREATE);
    }

    private ServiceConnection mConnection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "onServiceConnected: '"+iBinder);
            mPlayAction = (PayService.PayAction) iBinder;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG, "onServiceDisconnected: "+componentName);
           mPlayAction=null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mMIsBind&&mConnection!=null){

            unbindService(mConnection);
            Log.d(TAG, "onDestroy: unbindService");
            mMIsBind=false;
            mConnection=null;
        }
    }
}

编写某App,导入AIDL接口文件
通过ThirdPartPay.Stub.asInterface得到第三方服务对象,继承ThirdPartPayResult.Stub重写回调接口方法,然后调用服务端的requestPay方法的时候将相应对象传入即可。

package com.example.sobclient;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
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 android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.example.aliplay.ThirdPartPayAction;
import com.example.aliplay.ThirdPartPayResult;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private Button mBuySobBtn;
    private TextView mSobCountTv;
    private AlipayConnection mAlipayConnection;
    private boolean mIsBind;
    private ThirdPartPayAction mThirdPartPayAction;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG, "onCreate: 重新启动");
        bindAlipayService();
        //找到控件
        initView();
        setListener();

    }

    private void bindAlipayService() {
        Intent intent = new Intent();
        intent.setAction("com.example.aliplay.THIRD_PART_PAY");
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        intent.setPackage("com.example.aliplay");

        mAlipayConnection = new AlipayConnection();

        mIsBind = bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);

    }

    private class AlipayConnection implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected: "+mIsBind);
            mThirdPartPayAction = ThirdPartPayAction.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: "+name);
        }
    }

    private void setListener() {
        mBuySobBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //进行充值
                try {
                if(mThirdPartPayAction!=null) {
                    mThirdPartPayAction.requestPay("充值100SO币", 100.00f, new PayCallback());
                }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    //回调接口
    private class PayCallback extends ThirdPartPayResult.Stub{

        @Override
        public void onPaySuccess() {
            //支付成功,那么我们就去修改UI上的内容
            //实际上失去修改数据库,其实,支付宝是通过回调URL地址,通知我们的服务器
//点击支付回调方法
            /*
          跨进程通信的时候,走的是子线程,请不要在子线程操作ui,比如更新text,toast等等
             */
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mSobCountTv.setText("100");
                    Toast.makeText(MainActivity.this,"充值成功!",Toast.LENGTH_SHORT).show();
                }
            });


        }

        @Override
        public void onPayFailed(int errorcode, String msg)  {

            Log.d(TAG, "error code is: "+errorcode+"error Msg-"+msg);
            Toast.makeText(MainActivity.this,"充值失败!",Toast.LENGTH_SHORT).show();
        }
    }

    private void initView() {
        mSobCountTv=findViewById(R.id.sob_count_tv);
        mBuySobBtn=findViewById(R.id.buy_sob_btn);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mIsBind&&mAlipayConnection!=null){
            Log.d(TAG, "unbind service... ");
            unbindService(mAlipayConnection);
            mAlipayConnection=null;
            mIsBind=false;
        }
    }
}

服务例子音乐播放器

Activity负责UI界面,Service负责逻辑,之间通过接口进行通讯

两个接口

package com.example.musicplayer.Interfaces;

/**
 * 控制音乐的接口
 */
public interface IPlayerControl {

    //播放状态
    //播放
    int PLAY_STATE_PLAY=1;
    int PLAY_STATE_PAUSE=2;
    int PLAY_STATE_STOP=3;



    /**
     * 把UI的控制接口设置给逻辑层(让逻辑层通知更新UI界面)
     *
     */
    void setViewController(IPlayerViewControl viewController);

    /**
     * 取消接口通知的注册(防止设置过来内存泄露)
     */

    void unRegisterViewController();
    /**
     * 播放音乐
     */
    void playOrPause();


    /**
     * 停止播放
     */
    void stopPlay();

    /**
     * 设置播放进度
     */
    void seekTo(int seek);
}

package com.example.musicplayer.Interfaces;


/**
 * 通知UI更新的接口
 */
public interface IPlayerViewControl {

    /**
     * 播放状态改变的通知
     * @param state
     */
    void onPlayerStateChange(int state);

    /**
     * 播放进度的改变
     * @param seek
     */
    void onSeekChange(int seek);

}

主界面也就是UI层

package com.example.musicplayer;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Toast;

import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;

import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PAUSE;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PLAY;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_STOP;

public class MainActivity extends AppCompatActivity {

    private SeekBar mSeekBar;
    private Button mPlayOrPause;
    private Button mClose;
    private PlayerConnection mPlayerConnection;
    private IPlayerControl mIPlayerControl;
    private boolean isUserTouchProgressBar=false;
    private final static String TAG="MainActivity" ;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //动态申请权限
        if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED)
        {
            ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
        }else {
        }
        initView();
        //设置相关的事件
        initEvent();
        //启动播放的服务
        initService();
        //绑定服务
        initBindService();
        Log.d(TAG, "onCreate: "+ Environment.getExternalStorageDirectory());
    }

    /**
     * 开启播放的服务
     */
    private void initService() {
        Log.d(TAG, "initService: ");
        startService(new Intent(this,PlayService.class));
    }

    /**
     * 绑定服务
     */
    private void initBindService() {
        Log.d(TAG, "initBindService");
        Intent intent = new Intent(this, PlayService.class);
        mPlayerConnection = new PlayerConnection();
        bindService(intent,mPlayerConnection,BIND_AUTO_CREATE);
    }

    private class PlayerConnection implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "onServiceConnected: "+iBinder);
            mIPlayerControl = (IPlayerControl) iBinder;
            mIPlayerControl.setViewController(mIPlayerViewControl);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG, "onServiceDisconnected");
            mIPlayerControl=null;
        }
    }
    private void initEvent() {
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                //进度条发生改变


            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

                //我的手已经触摸上去拖动
                isUserTouchProgressBar=true;
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

                 int touchProgress=seekBar.getProgress();
                Log.d(TAG, "onStopTrackingTouch: "+touchProgress);
                //停止拖动
                if (mIPlayerControl != null) {
                    mIPlayerControl.seekTo(seekBar.getProgress());
                }
                isUserTouchProgressBar=false;
            }
        });
        mPlayOrPause.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View view) {
                Log.d(TAG, "onClick: ");
                //播放或暂停
                if(mIPlayerControl!=null) {
                    //如果绑定不成功为null就会崩,需要判空一下。在一些接口回调的时候就需要对其判空
                    mIPlayerControl.playOrPause();
                }
            }
        });
        mClose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //关闭按钮被点击了
                Log.d(TAG, "unonClick: ");
                if(mIPlayerControl!=null){
                    mIPlayerControl.stopPlay();
                }
            }
        });
    }

    private void initView() {
        mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
        mPlayOrPause = (Button) findViewById(R.id.play_or_pause_btn);
        mClose = (Button) findViewById(R.id.close_bt);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mPlayerConnection!=null){
            Log.d(TAG, "unbindService  onDestroy: ");
            unbindService(mPlayerConnection);
        }
    }

    //实现通知UI更新的接口
    private IPlayerViewControl mIPlayerViewControl=new IPlayerViewControl() {
        @Override
        public void onPlayerStateChange(int state) {
           switch (state){
               case PLAY_STATE_PLAY:
                   //播放中的话,我们要修改按钮显示成暂停
                   mPlayOrPause.setText("暂停");
                   break;
               case PLAY_STATE_PAUSE:
               case PLAY_STATE_STOP:
                   mPlayOrPause.setText("播放");
                   break;


           }
        }

        @Override
        public void onSeekChange(int seek) {

            //从上面的Log我们可以发现,这个不是主线程,所以不可以用于更新UI
            //为什么更新进度不会崩溃呢?
            //因为在android里面,有两个控件是可以用子线程去更新
            //一个是ProgressBar,另外一个是surfaceView
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if(!isUserTouchProgressBar){
                        mSeekBar.setProgress(seek);
                    }
                }
            });
         //改变播放进度,有一个条件,什么->当用户的手触摸到进度条的时候,就不更新(不然会抖动)

        }
    };
    
    /**
     * 动态申请权限
     * @param context    上下文
     * @param permission 要申请的一个权限,列如写的权限:Manifest.permission.WRITE_EXTERNAL_STORAGE
     * @return  是否有当前权限
     */
    private boolean RequestPermissions(Context context, String permission) {
        if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
            Log.i("requestMyPermissions",": 【 " + permission + " 】没有授权,申请权限");
            ActivityCompat.requestPermissions((Activity) context, new String[]{permission}, 100);
            return false;
        } else {
            Log.i("requestMyPermissions",": 【 " + permission + " 】有权限");
            return true;
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull  String[] permissions, @NonNull  int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode)
        {
            case 1:
                if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED)
                {
                    mIPlayerControl.playOrPause();
                }else {
                    Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
        }
    }
}

逻辑层

package com.example.musicplayer.presenter;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.util.Log;

import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingDeque;

public class PlayerPresenter extends Binder implements IPlayerControl {

    private static final String TAG="PlayerPresenter";
    
    //控制UI
    private IPlayerViewControl mViewControl;

    private int mCurrentState=PLAY_STATE_STOP;
    private MediaPlayer mMediaPlayer;
    private Timer mTimer;
    private SeekTimeTask mTimeTask;

    @Override
    public void setViewController(IPlayerViewControl viewController) {
        this.mViewControl=viewController;
    }

    @Override
    public void unRegisterViewController() {
        mViewControl=null;
    }

    @Override
    public void playOrPause() {
        Log.d(TAG, "playOrPause: ..");

        if(mCurrentState==PLAY_STATE_STOP){
            //创建播放器
            initPlayer();
            //设置数据源
            try {
                mMediaPlayer.setDataSource("/storage/emulated/0/Music/my_look.mp3");
                mMediaPlayer.prepare();
                mMediaPlayer.start();
                mCurrentState=PLAY_STATE_PLAY;
                startTimer();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }else if(mCurrentState==PLAY_STATE_PLAY){
            //如果当前的状态是播放的,那么我们就暂停
            if (mMediaPlayer!=null) {
                mMediaPlayer.pause();
                mCurrentState=PLAY_STATE_PAUSE;
                stopTimer();
            }
        }else if(mCurrentState==PLAY_STATE_PAUSE){
            //如果当前的状态是暂停,那么我们就继续播放
            if(mMediaPlayer!=null){
                mMediaPlayer.start();
                mCurrentState=PLAY_STATE_PLAY;
                startTimer();
            }
        }
        //逻辑层通知UI更新界面
        if(mViewControl!=null){
            mViewControl.onPlayerStateChange(mCurrentState);
        }
    }

    /**
     * 初始化播放器
     */
    private void initPlayer() {
        if(mMediaPlayer==null){
            mMediaPlayer = new MediaPlayer();
        }
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }

    @Override
    public void stopPlay() {
        Log.d(TAG, "stopPlay: ");
        if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){
            //停止的话再次开始会重头再来
            mMediaPlayer.reset();
            mCurrentState=PLAY_STATE_STOP;
            stopTimer();
            //更新播放状态
            if(mViewControl!=null){
                mViewControl.onPlayerStateChange(mCurrentState);
            }
            //释放资源
            mMediaPlayer.release();
            mMediaPlayer=null;
        }
    }

    @Override
    public void seekTo(int seek) {
        Log.d(TAG, "seekTo: .."+seek);

        //0-100之间
        //需要做一个转换,得到的seek其实是一个百分比
        if(mMediaPlayer!=null){
            int tarSeek=(int) (seek*1.0f/100*mMediaPlayer.getDuration());
            //设置进度
            mMediaPlayer.seekTo(tarSeek);
        }

    }

    /**
     * 开启一个timeTask
     */
    private void startTimer(){
        if(mTimer==null){

            mTimer = new Timer();
        }

        if(mTimeTask==null) {
            mTimeTask = new SeekTimeTask();
        }
        //每隔500ms更新进度条
        mTimer.schedule(mTimeTask,0,500);
    }

    private void stopTimer(){
        if(mTimeTask!=null){
         mTimeTask.cancel();
         mTimeTask=null;
        }
        if(mTimer!=null){
            mTimer.cancel();
            mTimer=null;
        }
    }

    private class SeekTimeTask extends TimerTask{

        @Override
        public void run() {
            //获取当前的播放进度
            if(mMediaPlayer!=null&&mViewControl!=null){
                //currentPosition表示当前进度getDuration表示总的进度
                int currentPosition=mMediaPlayer.getCurrentPosition();
                //需要浮点数不然整除为0
                Log.d(TAG, "current play position... "+currentPosition*1.0f);
                //最后除出来是百分比所以需要乘100
                Log.d(TAG, "getDuration... "+mMediaPlayer.getDuration()*100);
                int curPosition= (int) (currentPosition*1.0f/mMediaPlayer.getDuration()*100);
                mViewControl.onSeekChange(curPosition);
            }
        }
    }


}

服务

package com.example.musicplayer.services;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

import androidx.annotation.Nullable;

import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.presenter.PlayerPresenter;

public class PlayService extends Service {

    private PlayerPresenter mPlayerPresenter;

    @Override
    public void onCreate() {
        super.onCreate();
        //进行判空,防止重复创建
        if (mPlayerPresenter == null) {
            mPlayerPresenter = new PlayerPresenter();
        }
    }

    

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mPlayerPresenter;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //释放资源(主要用于防止内存泄露)
        mPlayerPresenter=null;
    }

    /*
   这一部分逻辑层可以抽取出来建个包presenter
   private class InnerBinder extends Binder implements IPlayerControl{


        @Override
        public void setViewController(IPlayerViewControl viewController) {

        }

        @Override
        public void unRegisterViewController() {

        }

        @Override
        public void playOrPause() {

        }

        @Override
        public void stopPlay() {

        }

        @Override
        public void seekTo(int seek) {

        }
    }
*/

}

服务与线程的区别

service:服务是运行在主线程中的,所以不能用来执行耗时操作,必须在服务中新开线程,服务没有Activity界面,即使Activity被销毁,但只要进程还在,服务就会在后台继续执行,服务一般用来在后台执行操作,而线程一般写在服务中,这样activity就能通过服务来控制线程。服务有分为前台服务和后台服务,后台服务优先级比较低,当系统不足时会被系统回收,前台服务优先级比较高,会以通知的形式显示在界面上,不会被系统回收。
线程:线程可以用来执行耗时操作,它与服务最大的区别在于服务是运行在主线程中,线程一般不会直接在activity中创建,因为这不利于Activity对线程的控制,线程一般都在服务中创建。

如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity
没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的
Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/831657.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SLAM精度测评——EVO进阶再进阶

分别观察单个坐标轴差异 1.1 观察x轴差异 evo_ape tum truth.txt pose.txt -r trans_part -va --plot --plot_mode xz

鲁大师7月新机性能/流畅/久用榜:骁龙8 Gen2领先版亮相,性能跑分再破新高

摘要&#xff1a;iQOO 11S突破上限&#xff0c;红魔8S Pro再创新高 继五月六月&#xff0c;搭载天玑9200的机型相继迎来上市之后&#xff0c;高通也终于按耐不住。 本月所有上市的新机均搭载高通骁龙系列芯片&#xff0c;其中骁龙8 Gen2领先版迎来首次亮相&#xff0c;除了主打…

落地数字化管理,提升企业市场竞争力

数字化企业管理方案是一种利用数字技术和信息系统来提升企业管理效率和运营效果的策略。 潜在的数字化企业管理方案 1、企业资源规划&#xff08;ERP&#xff09;系统&#xff1a;建立一个集成的ERP系统来统一管理企业的各项业务流程&#xff0c;包括采购、销售、库存管理、财…

NodeJS版本管理工具——NVM

NodeJS版本管理工具——NVM 准备工作 卸载原 nodejs 版本 1、nvm简介 nvm是一个node版本管理工具&#xff0c;通过它可以安装多种node版本并且可以快速、简单的切换node版本。 2、nvm安装 1、下载链接&#xff1a;https://github.com/coreybutler/nvm-windows/releases 注…

卡片的点击事件通过点击进行路由传参

下面是详情页 通过 接收 <template><div class"detail"><img :src"row.imgUrl"><van-icon name"arrow-left" click"back" /></div> </template><script> export default {created() {let …

STM32CubeMX配置定时器PWM--保姆级教程

———————————————————————————————————— ⏩ 大家好哇&#xff01;我是小光&#xff0c;嵌入式爱好者&#xff0c;一个想要成为系统架构师的大三学生。 ⏩最近在开发一个STM32H723ZGT6的板子&#xff0c;使用STM32CUBEMX做了很多驱动&#x…

原型链污染分析

原型链污染问题 原型链原型的继承原型链污染 原型链 原型的继承 先创建一个对象&#xff0c;查看一下属性 const obj { prop1: 111, prop2: 222,} 这里的Object.prototype就是对象的原型。 原型里面有许多的属性&#xff0c;这里面的constructor是我们需要着重关注的。 除此…

刷题DAY15

第一题 给定一个数组arr 求子数组最大累加和 最暴力的 枚举每一个子数组 出结果 优化解 用一个cur指针保存累加和 每次cur变大 就用它更新max 如果cur累加到0以下 回复成0 假设答案法 假设我们最大的子数组是i 到 j位置上的 那么这个i 到j 之间 必不存在一个k使i...k累加和…

WEB:xff_referer

前提知识 xxf referer 题目 直接在请求头里添加&#xff0c;然后重放后显示内容为 修改referer payload Referer:https://www.google.com 得到flag

运营干货!如何自查亚马逊品牌是否存在滥用情况?

做了这么多年亚马逊&#xff0c;说到底还是没办法摸透亚马逊的脾气。比如亚马逊的推荐算法&#xff0c;也就是大家经常讨论的A9算法。为什么总是没办法摸透亚马逊的想法呢&#xff1f; 毕竟“游戏规则”是由亚马逊来制定&#xff0c;作为参与者只能按照游戏规则去参与游戏&…

【LeetCode-中等】剑指 Offer 35. 复杂链表的复制(详解)

目录 题目 方法1&#xff1a;错误的方法&#xff08;初尝试&#xff09; 方法2&#xff1a;复制、拆开 方法3&#xff1a;哈希表 总结 题目 请实现 copyRandomList 函数&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个节点除了有一个 next 指针指向下一个节…

【Spring】创建一个Spring项目与Bean对象的存储

目录 一、创建Spring项目 1、创建Maven项目 2、配置maven国内源 3、引入spring依赖 4、添加启动类 二、将Bean对象存储到Spring&#xff08;IoC容器&#xff09; 1、创建Bean对象 2、将Bean存储到spring&#xff08;容器&#xff09;中 3、获取Bean对象 3.1、Applicatio…

C++ | 位图与布隆过滤器

目录 前言 一、位图 1、位图的引入 2、位图的实现 &#xff08;1&#xff09;基本结构 &#xff08;2&#xff09;构造函数 &#xff08;3&#xff09;插入数据 &#xff08;4&#xff09;删除数据 &#xff08;5&#xff09;是否存在 3、位图的优缺点 4、位图的应用…

js-匈牙利算法

匈牙利算法 素数伴侣新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个注脚注释也是必不可少的K…

TSINGSEE青犀视频汇聚平台EasyCVR视频广场面包屑侧边栏支持拖拽操作

TSINGSEE青犀视频汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等&#xff0c;能对外分发RTSP、RTMP、FLV、HLS、Web…

第七章:SpringMVC中

第七章&#xff1a;SpringMVC中 7.1&#xff1a;SpringMVC的视图 ​ SpringMVC中的视图是View接口&#xff0c;视图的作用渲染数据&#xff0c;将模型Model中的数据展示给用户SpringMVC视图的种类很多&#xff0c;默认有转发视图和重定向视图。 ​ 当工程引入jstl的依赖&…

react中PureComponent的理解与使用

一、作用 它是一个纯组件&#xff0c;会做一个数据的浅比较&#xff0c;当props和state没改变的时候&#xff0c;不会render重新渲染&#xff0c; 改变后才会render重新渲染&#xff0c;提高性能。 二、使用 三、注意 它不能和shouldComponentUpdate生命周期同时使用。因为它…

【网络基础进阶之路】路由器间的静态综合详解

PS&#xff1a;本实验基于华为的eNSP模拟软件进行 题目内容&#xff1a; 完成步骤&#xff1a; 1、对192.168.1.0/24进行子网划分 2、对每一个路由器进行IP的配置 3、开始静态路由的书写&#xff0c;在写之前&#xff0c;我们可以先对每一个路由器写一条通向右边的缺省路由&…

如何解决跨域问题?

一&#xff0c;什么是跨域 域&#xff08;Origin&#xff09;是由协议、域名和端口组成的&#xff0c;只有这三者完全一致的情况下&#xff0c;浏览器才会认为两个网址同源&#xff0c;否则就认为存在跨域。跨域是指在Web开发中&#xff0c;一个网页的JavaScript代码试图访问另…

机器学习实战13-超导体材料的临界温度预测与分析(决策树回归,梯度提升回归,随机森林回归和Bagging回归)

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下机器学习实战13-超导体材料的临界温度预测与分析(决策树回归,梯度提升回归,随机森林回归和Bagging回归)&#xff0c;这几天引爆网络的科技大新闻就是韩国科研团队宣称发现了室温超导材料-LK-99&#xff0c;这种材料…