Android四大组件——Service详解

        Service 为后台运行,不可见,没有界面。优先级高于Activity(内存不足时先杀掉Activity),运行在主线程且不能做耗时操作。

一、Service 启动方式

1、startService()

        通过 startService 启动后,service会一直无限期运行下去,当外部调用了 stopService() 或stopSelf() 方法时,该Service才会停止运行并销毁。当系统资源不足时, 会回收一些不重要的service,service 被系统回收也会停止运行并被销毁。

生命周期

onCreate()

1、如果 service 没被创建过,调用 startService() 后会执行 onCreate() 回调;
2、如果 service 已处于运行中,调用 startService() 不会执行 onCreate() 方法。

        此方法适合完成一些初始化工作。

onStartCommand()

        如果多次执行了 startService() 方法,那么 Service 的 onStartCommand() 方法也会相应的多次调用。

onBind()

        Service中的onBind()方法是抽象方法,Service类本身就是抽象类,所以onBind()方法是必须重写的,即使我们用不到。

         使用 startService 方法启动 Service,onBind() 方法基本用不到,重写时返回一个 null 即可。

onDestory()

        在销毁的时候会执行Service该方法。

代码实例

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 启动service
        Intent intent = new Intent(MainActivity.this, TestService.class);
        startService(intent);
    }
}

TestService.java

public class TestService extends Service {
    private static final String TAG = "TestService";

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

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

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

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xiaoxu.testdemo">
    <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.MainActivity ">
        <service
            android:name=".TestService"
            android:enabled="true"
            android:exported="true" />

        <activity
            android:name=".MainActivity"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

2、bindService

        bindService 启动的服务和调用者之间是典型的 client-server 模式。调用者是 client,service则是 server 端。service只有一个,但绑定到 service 上面的 client 可以有一个或多个。这里所提到的 client 指的是组件,比如某个Activity。
        client 可以通过 IBinder 接口获取 Service 实例,从而实现在 client 端直接调用 Service 中的方法以实现灵活交互,这在通过 startService 方法启动中是无法实现的。
        bindService 启动服务的生命周期与其绑定的 client 息息相关。当 client 销毁时,client 会自动与 Service 解除绑定。当然,client 也可以明确调用 Context 的 unbindService() 方法与 Service 解除绑定。当没有任何 client 与 Service 绑定时,Service 会自行销毁。

生命周期

onCreate()

        当服务通过 onStartCommand() 和 onBind() 被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装。

onBind()

        当其他组件想要通过 bindService() 来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回 IBinder 对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回 null。

onUnbind()

        当客户中断所有服务发布的特殊接口时,系统调用该方法。

onRebind()

        当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法。

onDestroy()

        当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。

代码实例

MainAcivity.java

public class MainAcivity extends Activity{
    private TextService mService = null;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(this, TestService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }


    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            MyBinder mBinder = (MyBinder)binder;
            mService = mBinder.getService();
            String name = mService.getUserName();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }
}

MyService.java

public class TestService extends Service{
    //通过 binder 实现调用者 client 与 Service 之间的通信
    private MyBinder binder = new MyBinder();

    // client 可以通过 Binder 获取 Service 实例
    public class MyBinder extends Binder {
        public MyService getService() {
            return TestService.this;
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_NOT_STICKY;
    }

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

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    // getUserName 是 Service 暴露出去供 Client 调用的公共方法
    public int getUserName() {
        return "XiaoXu";
    }
}

3、startForegroundService

        Android 8.0 系统不允许后台应用创建后台服务,只能使用 startForegroundService() 启动服务,创建服务后,应用必须在 5 秒内调用该服务的 startForeground() 显示一条可见通知,声明有服务在挂着,不然系统会停止服务 + ANR 提示。
        系统的要求 Notification 要加 Channel,并且要在 onStartCommand 中执行 startForeground(),因为这个主要是针对后台保活的服务,如果在服务运行期间,再次使用 startForegroundService() 启动服务,那么这次就不会调用服务的 onCreate 方法,只会调用onStartCommand 方法。如果不在 onStartCommand 方法里再挂个通知的话,系统会认为你使用了 startForegroundService 却不在 5 秒内给通知,很傻地就停止服务 + ANR 提示。

代码实例

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 启动service
        Intent intent = new Intent(this, TestService.class) ;
        startForegroundService(intent);
    }
}

TestService.java

public class TestService extends Service {
    private static final String TAG = "TestService";

    private Notification notification;
    public static final String CHANNEL_ID_STRING = "service_01";

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

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_STRING,
                getString(R.string.app_name),
                NotificationManager.IMPORTANCE_LOW);
        manager.createNotificationChannel(mChannel);
        notification = new Notification.Builder(getApplicationContext(), CHANNEL_ID_STRING).build();
        // 下面第一个参数不能为 0
        startForeground(1, notification);
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

        可以看到使用  startForegroundService 启动方式与 startService 基本相同,只是在 Service 的 onStartCommand 增加弹出通知的操作。startForeground 方法中的第一个参数不能为 0,是因为这个通知不能与正常通知 id 相同,否则通知会被“隐藏”,这不是官方想看到的,就会出现 Context.startForegroundService() did not then call Service.startForeground() 异常。

二、其他

1、使用场景

startService

        前台应用调用 startService 启动 Service,Service 独立运行,不需要与 Activity 进行交互。

bindService

        前台应用调用 bindService 启动 Service,Android 与 Service 进行绑定,并且 Activity 可以得到 Service 的实例,实现与 Service 的交互。

startForegroundService

        后台应用调用 startForegroundService 启动 Service,Android 8.0 之后强制要求,以提示用户。例如:在收到开机广播时启动服务,使用 startService 会无法启动,只能使用 startForegroundService 进行启动并弹出通知提示用户。

2、隐藏通知

        有时在使用 startForegroundService 启动服务时,想隐藏常驻通知。

1)显示通知后马上调用 stopForeground(true) 取消通知。在 Android 11 中测试,服务被杀掉。

2)提前注册一个没有声音没有震动的通知channel,注册一个无声无振动的通知渠道。startForeground 时与该通知使用同一个 id,就可以隐藏我们的通知了。(未尝试)

参考:Android8 避免startForeground方法弹出通知

3、AIDL Service死亡监听

        服务的死亡监听我们使用第二种启动方式。

启动 Service

private void bindService(Context context) {
    Intent serviceIntent = new Intent();
    serviceIntent.setAction("com.cx.test");
    serviceIntent.setPackage("com.cx.test.MyService");
    boolean bindSuccess = context.bindService(serviceIntent, mServiceConnection, Service.BIND_AUTO_CREATE);
}

绑定结果回调

private IPushInterface mIPushInterface;
 
private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        if (iBinder != null) {
            //通过AIDL拿到服务
            mIPushInterface = IPushInterface.Stub.asInterface(iBinder);
            try {
                //服务死亡监听
                if (mIPushInterface != null) {
                    mIPushInterface.asBinder().linkToDeath(mDeathRecipient, 0);
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
 
    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        //连接失败
    }
};

死亡监听回调

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mContext != null) {
            //重新绑定服务
            bindService(mContext);
        }
    }
};

猜你喜欢

转载自blog.csdn.net/c19344881x/article/details/129060604