Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android四大组件之Service #39

Open
soapgu opened this issue May 13, 2021 · 0 comments
Open

Android四大组件之Service #39

soapgu opened this issue May 13, 2021 · 0 comments
Labels
Demo Demo 安卓 安卓

Comments

@soapgu
Copy link
Owner

soapgu commented May 13, 2021

  • 前言

其实Service在五一前已经简单过了一遍了,一直没好好拿出来整理。正好这次的规划的程序应该会使用相关组件。正好写个小Demo边验证边巩固下知识点。

  • 什么是Service?

还是要啰嗦下,什么是Service

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。
长时间是个关键字,长时间是多长?和Application一样长?长时间保持是Service
应用的主要场景。

服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行
基本把启动调用的的位置讲清楚了

此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)
这也是关键一点。

概况下来Service具有长时间、启动灵活、可交互三个主要特性。

创建服务的两个类

  1. Service
    所有服务的基类,具有高度可扩展性
  2. IntentService
    简化的Service,Service的派生类。IntentService会将该Intent加入到队列中,然后对每一个Intent开启一个worker thread来进行处理,执行完所有的工作之后自动停止Service。主要针对类似工作队列的模型应用(比如打印)。这里我们不做深入研究

参考资料

  • 本次验证的重点

这次我们不可能去应用Service的全部知识点,本着实用主义。我们以侧重点去关注以下知识点

  • 后台服务
    前台服务暂时不在验证范围内

  • 隐式 Intent 启动服务
    为了让服务和调用端进一步解耦,是必须考察的点。虽然文档里面并没有提

  • 绑定服务
    可交互性的服务是必须的

  • Demo实现

首先,调查的背景是多模块解耦为前提的,所以我把程序分为3个模块

  • com.soapgu.countservice

Application级别的模块,主要提供主界面及应用入口

  • com.soapgu.core

Service接口的定义

  • com.soapgu.service

Service接口的实现

  1. 隐式 Intent 启动服务实现

首先在core定义隐式Intent的名称

public final class Intents {
    private Intents() {
    }
    public static final String ACTION = "com.soapgu.core.intent.action.COUNT";
}

接下来在com.soapgu.service模块实现

public class MyCountService extends Service{
}

Service需要注册到AndroidManifest.xml

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

    <application>
        <service
            android:name=".MyCountService"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="com.soapgu.core.intent.action.COUNT"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>
    </application>

</manifest>

再转战到application的module去实现服务的启动

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
                .showThreadInfo(true)
                .tag("SoapAPP")
                .build();
        Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));
        Logger.i("-------APP Create-------");

        Intent intent = new Intent();
        intent.setPackage( this.getPackageName() );
        intent.setAction( Intents.ACTION );
        this.startService(intent);
        Logger.i("-------Start MyCountService-------");
    }
}

运行起来,报错。。。。

java.lang.IllegalArgumentException: Service Intent must be explicit

看出错意思就是只能用显示Intent
查下资料也翻到前面文档里面的黄色小字

注意:为确保应用的安全性,在启动 Service 时,请始终使用显式 Intent,且不要为服务声明 Intent 过滤器。使用隐式 Intent 启动服务存在安全隐患,因为您无法确定哪些服务会响应 Intent,而用户也无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 Intent 调用 bindService(),则系统会抛出异常

难道就这么结束了?不甘心。再去翻下SO,
Android 5.0 (L) Service Intent must be explicit in Google analytics

好像也不是没希望,只要指定Package就行,就是指定运行的Package。让Service明确定位,防止不安全的调用。这到可以接受。因为Intent的设计本来就宏大一点,是可以跨进程跨应用调用的。对于我们的应用场景来说,暂时不用出应用。
所以只要加上**intent.setPackage( this.getPackageName() );**就行了。

  1. 绑定服务实现

  • Binder 绑定服务关键中间人
    调用端:
    调用端创建ServiceConnection 调用bindService,然后从ServiceConnection的onServiceConnected回调中获取IBinder接口实例
    Service端:
    通过IBinder onBind(Intent intent)回调函数返回IBinder接口

再把IBinder封装成自定义的接口,可以达到解耦目的

/**
 * 计数器接口
 */
public interface ICounter {
    /**
     * 获取当前计数
     * @return 计数值
     */
    Long getCount();
}

现在Core定义ICounter 接口,也是准备让中间人实现的接口

public class MyCountService extends Service {

    private Long countValue;
    private final CompositeDisposable disposables = new CompositeDisposable();

    public MyCountService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        Logger.i( "------MyCountService onBind-------" );
        return new MyBinder();
    }

    @Override
    public void onCreate() {
        Logger.i("-------MyCountService onCreate-------");
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.i("-------MyCountService onStartCommand-------");
        StartCountEngine();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Logger.i("-------MyCountService onDestroy-------");
        disposables.dispose();
        super.onDestroy();
    }

    private void StartCountEngine() {
        disposables.add(
                Observable.interval( 3 , TimeUnit.SECONDS )
                        .subscribe( t -> {
                                    countValue = t;
                                } ,
                                e -> Logger.e( e, "On Error" ),
                                ()-> Logger.i("Stop Engine"))
        );

    }

    private class MyBinder extends Binder implements ICounter {

        @Override
        public Long getCount() {
            return countValue;
        }
    }
}

定义了MyBinder,继承了基础Binder类,实现ICounter 接口
其中countValue在服务被开始后,会自动定时计数。

public class MainActivity extends AppCompatActivity {

    private ICounter iService;
    private  MainViewModel viewModel;
    private final ServiceConnection conn = new ServiceConnection(){
        //当服务被成功绑定的时候调用的方法.
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {//第二个参数就是服务中的onBind方法的返回值
            Logger.i( "-----onServiceConnected----" );
            iService = (ICounter) service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Logger.w( "-----onServiceDisconnected----" );
            //add commit
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this,
                ViewModelProvider.AndroidViewModelFactory.getInstance(this.getApplication()))
                .get(MainViewModel.class);
        ActivityMainBinding binding  = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setDataContext( viewModel);
        binding.buttonGetCount.setOnClickListener( v -> viewModel.setMessage( String.format( "Count:%s",iService.getCount() ) ));
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onStart() {
        Intent intent = new Intent();
        intent.setPackage( this.getPackageName() );
        intent.setAction(Intents.ACTION);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
        super.onStart();
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(conn);
        iService = null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}
  • 这里在Activity里定义了ServiceConnection

  • onServiceConnected回调里获取ICounter接口

  • onStart回调里执行bindService来绑定到服务

  • onStop回调里unbindService来解绑

  • 按钮监听来调用ICounter的getCount来获取当前计数显示到界面

  • 总结

使用起来还算方便,解耦也比较好。不需要用Hilt或者Dragger入场。不过对于那种回调的通知要怎么解决?我们在下一篇解决这个问题

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Demo Demo 安卓 安卓
Projects
None yet
Development

No branches or pull requests

1 participant