展开说说:Android四大组件之Service使用

Service一定要开启子线程才可以执行耗时任务吗?不完全是吧。

Service是Android系统中的四大组件之一,它是一种没有可视化界面,运行于后台的一种服务程序属于计算型组件,用来在后台执行持续性的计算任务,重要性仅次于Activity活动。

本文分四块来记录,分别是普通Service、IntentService、ForegroundService,其中普通service根据运行状态不同分为startService启动的Service和bindService绑定的Service。

先定义一个Service子实现类,普通Service的启动的Service和bindService绑定的Service共用这一个Service类。

public class ServiceJia extends Service {
    private static final String TAG = ServiceJia.class.getName();
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: " );
        return new JiaBinder();
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: " );

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(30000);
                    Log.e(TAG, "onStartCommand:    等了半分钟" );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Log.e(TAG, "onStartCommand:    -end" );
        return super.onStartCommand(intent, flags, startId);
    }

//用于bindService时和activity交互
    public static class JiaBinder extends Binder {
        public void doSomething(String something){
            Log.e(TAG, "JiaBinder  --doSomething: "+something );
        }

        public void onClick(String something){
            Log.e(TAG, "JiaBinder  --onClick: "+something );
        }
    }

}

1、startService启动Service和停止

通过startService()创建启动的service可以一直运行下去,必须自己调用stopSelf()方法或者其他组件调用stopService()方法来停止。适用于不和其他组件通讯的业务。

 //不可以阻塞线程做耗时操作,比如这样:

try {
     Thread.sleep(30000);
     } catch (InterruptedException e) {
           e.printStackTrace();
      }
 程序闪退报错:
2023-12-02 09:46:39.584 1592-1795/? E/ActivityManager: ANR in com.example.testdemo3 PID: 29467
Reason: executing service com.example.testdemo3/.service.ServiceJia
Load: 46.58 / 46.1 / 46.01
CPU usage from 70164ms to 0ms ago (2023-12-02 09:45:28.698 to 2023-12-02 09:46:38.862) with 99% awake:

启动和关闭服务:

startService.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String text = startService.getText().toString();
        if ("startService".equals(text)){
            startService.setText("stopService");
            serviceIntent = new Intent(ServiceActivity.this, ServiceJia.class);
            startService(serviceIntent);
        }else if ("stopService".equals(text)){
            if (stopService(serviceIntent)){ //真停了再修改按钮的文字显示
                startService.setText("startService");
            }
        }

    }
});

2、bindService绑定Service和解绑

调用bindService()来创建,调用方可以通过一个IBinder接口和service进行通信,需要通过ServiceConnection建立连接多用于有交互的场景。

只能调用方通过unbindService()方法来断开连接。调用方可以和Service通讯,并且一个service可以同时和多个调用方存在绑定关系解除绑定也需要所有调用全部解除绑定之后系统会销毁service

绑定和解绑服务:

String text = bindService.getText().toString();
if ("bindService".equals(text)){
if (serviceIntent == null)
    serviceIntent = new Intent(ServiceActivity.this, ServiceJia.class);
    if (bindService(serviceIntent,serviceConnection, Context.BIND_AUTO_CREATE)){
        bindService.setText("unBindService");
    }
}else if ("unBindService".equals(text)){
    unbindService(serviceConnection);
    bindService.setText("bindService");
}

//连接成功,可以通过Binder调用绑定的service中的方法,比如jiaBinder.doSomething("start conncetion");

serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
        Log.e(TAG, "onServiceConnected: " );
        jiaBinder = (ServiceJia.JiaBinder) iBinder;
        //连接成功,调用绑定的service中的方法
        jiaBinder.doSomething("start conncetion");
    }

    @Override
    public void onServiceDisconnected(ComponentName componentName) {
        Log.e(TAG, "onServiceDisconnected: " );
    }

    @Override
    public void onBindingDied(ComponentName name) {
        Log.e(TAG, "onBindingDied: " );
    }

    @Override
    public void onNullBinding(ComponentName name) {
        Log.e(TAG, "onNullBinding: " );
    }
};

3、IntentService

它可以解决第一句中“Service一定要开启子线程才可以执行耗时任务吗?”的问题。

IntentService 是 Android 中的一个服务,继承自 Service 类,并在单独的工作线程中执行任务,避免了多线程处理异步任务我们可以在onHandleIntent生命周期方法中执行耗时任务,不用开启子线程:

注意这里是继承IntentService

还有就是继承IntentService必须声明一个空参构造方法否则会报错删除 

public class MyIntentService extends IntentService {
    public static final String TAG = "MyIntentService";
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     * @param name Used to name the worker thread, important only for debugging.
     * 如果name字段为空,字符串就是worker thread的名字
     */
    public MyIntentService(String name) {
        super(name);
    }

    public MyIntentService() {
        super(TAG);

    }

    /**
     * 可以做耗时任务
     * @param intent
     */
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String type = intent.getStringExtra("type");
        try {
            Log.e(TAG, "onHandleIntent:  -start" );
            Thread.sleep(10000);
            Log.e(TAG, "onHandleIntent:  -end" );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }

启动和关闭IntentService

intentService.setOnClickListener(new View.OnClickListener() {
    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onClick(View v) {

        String text = intentService.getText().toString();
        if ("startIntentService".equals(text)){
            intentService.setText("stopIntentService");
            intentServiceIntent = new Intent(ServiceActivity.this, MyIntentService.class);
            startService(intentServiceIntent);
        }else if ("stopIntentService".equals(text)){
            boolean b = stopService(intentServiceIntent);
            Log.e(TAG, "onClick: stopIntentService= "+b );
            if (b){ //真停了再修改按钮的文字显示
                intentService.setText("startIntentService");
            }
        }
    }
});

这里有一个比较坑的地方就是,必须指定一个空参构造函数,否则运行就会报错。

4、前台服务

前台服务用于执行用户可察觉的操作。前台服务会显示状态栏通知,让用户知道当前应用正在前台执行任务并消耗系统资源。用户可以通过点击通知来与应用进行交互。

值得注意的两个点:

  • 除了在AndroidManifest.xml注册服务之外,还需要申请前台服务的权限,否则会闪退。

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

  • Build.VERSION_CODES.O及以上的系统,需要单独NotificationManager需要设置NotificationChannel否则会闪退,具体见下面代码。

闪退报错信息:“android.app.RemoteServiceException: Bad notification for startForeground

使用上和普通Service类似同样继承Service,但是要设置前台服务通知

startForeground(1000,notification);

定义前台服务:
public class MyForegroundService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Notification.Builder builder = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            builder = new Notification.Builder(this,getChannelId("com.example.testdemo.service","MyForegroundService"));
            Notification notification = builder.setContentTitle("我是前台服务")
                    .setContentText("目前运行平稳")
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .build();
            //设置前台服务
            startForeground(1000,notification);
        }else {
            //设置前台服务
            startForeground(1000,new Notification());
        }
        //被杀以后还会再次创建
        return START_STICKY;
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private String getChannelId(String channelId, String channelName) {
        NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE);
        notificationChannel.setLightColor(Color.BLUE);
        notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(notificationChannel);
        return channelId;
    }
}

启动和关闭前台服务:

 foregroundService.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.O)
            @Override
            public void onClick(View v) {
                String text = foregroundService.getText().toString();
                if ("startForegroundService".equals(text)){
                    foregroundService.setText("stopForegroundService");
                    foregroundServiceIntent = new Intent(ServiceActivity.this, MyForegroundService.class);
//                    startService(intentServiceIntent);
                    startForegroundService(foregroundServiceIntent);
                }else if ("stopForegroundService".equals(text)){
                    boolean b = stopService(foregroundServiceIntent);
                    Log.e(TAG, "onClick: stopIntentService= "+b );
                    if (b){ //真停了再修改按钮的文字显示
                        foregroundService.setText("startForegroundService");
                    }
                }
            }
        });

本文主要是记录了四种使用服务经常遇到的问题,后面再分析生命周期和源码。

才疏学浅,如有错误,欢迎指正,多谢。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值