Android应用界面开发——Service与IntentService(实现定时更换壁纸)

Service是Android四大组件中与Activity最相似的组件,它们都代表可执行的程序,Service与Activity的区别是:Service一直在后台运行,它没有用户界面,所以绝不会到前台运行。如果某个程序组件需要在运行时向用户呈现某种界面,或者该程序需要与用户交互,就需要使用Activity;否则就应该考虑使用Service了,最后通过使用Service实现定时更换壁纸。

定时更换壁纸效果图:

Service简介


Service是一个可长期在后台运行的应用组件,并且不提供用户界面。

  • Service不是一个单独的进程。
  • Service不是一个线程。

创建、配置Service


开发Service分为两步:

  • 定义一个继承Service的子类。
  • 在AndroidManifest.xml文件中配置该Service。

补充:需要在AndroidManifest.xml中声明的有:

  • activity:活动
  • activity-alias:活动别名
  • service:服务
  • provider:内容提供者
  • receiver:广播接收者
  • meta-data:数据格式
  • uses-library:第三方类库

Service中定义了一系列生命周期方法:

  • IBinder onBind(Intent intent):该方法是Service子类必须实现的方法。该方法返回一个IBinder对象,应用程序可通过该对象与Service组件通信。
  • void onCreate():在Service第一次被创建后立即回调该方法。
  • void onDestroy():在Service被关闭之前回调该方法。
  • void onStartCommand(Intent intent, int flags, int startId):该方法的早期版本是onStart(Intent intent, int startId),每次客户端调用startService(Intent)方法启动Service时都会回调该方法。
  • boolean onUnbind(Intent intent):当该Service上绑定的所有客户端都断开连接时将会回调该方法。

定义一个Service组件如下:

public class FirstService extends Service {
    
    private static final String TAG = FirstService.class.getSimpleName();

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "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() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

上面的Service只是重写了Service组件的onCreate()、onStartCommand()、onDestroy()、onBind()等方法,重写这些方法时只是打印了一个字符串。

在Android系统中运行Service有两种方式:

  • 通过Context的startService()方法:通过该方法启动Service,访问者与Service之间没有关联,即使访问者退出了,Service也仍然运行。
  • 通过Context的bindService()方法:通过该方法启动Service,访问者与Service绑定在一起,访问者一旦退出,Service也就终止了。

启动和停止Service——startService()方式启动


使用Activity作为Service的访问者,该Activity中包含两个按钮,一个用于启动Service,一个用于关闭Service。代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private Button mStartService;
    private Button mStopService;
    private Intent intent;

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

        mStartService = (Button) findViewById(R.id.start_service);
        mStopService = (Button) findViewById(R.id.stop_service);
        intent = new Intent(MainActivity.this, FirstService.class);

        mStartService.setOnClickListener(this);
        mStopService.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_service:
                startService(intent);
                break;
            case R.id.stop_service:
                stopService(intent);
                break;
        }
    }
}

启动、关闭Service十分简单,调用Context的startService()、stopService()方法即可启动、关闭Service。

注意:Android5.0开始,Google要求必须使用显示的Intent启动Service组件。

运行该程序,点击启动按钮启动Service,再点击停止按钮关闭Service,在Logcat面板可以看到如下输出:

如果在不关闭Service的情况下,连续点击三次启动Service按钮,程序会连续启动三次Service,在Logcat面板可以看到如下输出:

从上图可以看出,每当Service被创建时会回调onCreate()方法,每次Service被启动时都会回调onStartCommand()方法;多次启动一个已有的Service不会再回调onCreate()方法,但每次启动时都会回调onStartCommand()方法。

绑定本地Service并与之通信——bindService()方式启动

如果Service和访问者之间需要进行方法调用或者交换数据,则应该使用bindService()和unbindService()方法启动、关闭Service。

Context的bindService()方法包含三个参数,分别如下:

  • service:该参数通过Intent指定要启动的Service。
  • conn:该参数是一个ServiceConnection对象,该对象用于监听访问者与Service之间的连接情况。当访问者与Service之间连接成功时回调该ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service)方法;当Service所在的宿主进程由于异常中止或者其他原因终止,导致该Service与访问者之间断开连接时回调该ServiceConnection对象的onServiceDisconnected(ComponentName name)方法。
    • onServiceConnected(ComponentName name, IBinder service)方法中的IBinder即可实现与被绑定Service之间的通信。
  • flags:指定绑定时是否自动创建Service(如果Service还未创建)。该参数可指定为0(不自动创建)或者BIND_AUTO_CREATE(自动创建)。

实际开发时通常会采用继承Binder(IBinder的实现类)的方式实现自己的IBinder对象。

下面程序示范了如何在Activity中绑定Service,并获取Service的运行状态。该程序的Service类需要真正实现onBind()方法,并让该方法返回一个有效的IBinder对象。Service类代码如下:

public class BindService extends Service {

    private static final String TAG = BindService.class.getSimpleName();

    private int count;
    private boolean quit;

    //定义onBinder方法所返回的对象
    private MyBinder binder = new MyBinder();
    //通过继承Binder实现IBinder类
    public class MyBinder extends Binder{
        public int getCount(){
            return count;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "onBind");
        return binder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "onCreate");
        //启动一条线程,动态修改count状态值
        new Thread(){
            @Override
            public void run() {
                while (!quit){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count++;
                }
            }
        }.start();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "onUnbind");
        return true;
    }

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

Service类实现了onBind()方法,该方法返回一个可访问该Service状态数据(count)的IBinder对象,该对象将被传给该Service的访问者。

接下来定义一个Activity来绑定该Service,并在Activity中通过MyBinder对象访问Service的内部状态。该Activity包含三个按钮,分别为绑定Service、解绑Service、获取Service的运行状态。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = MainActivity.class.getSimpleName();
    private Button mBindService;
    private Button mUnbindService;
    private Button mGetServiceState;
    private Intent intent;
    //保持所启动的Service的IBinder对象
    BindService.MyBinder binder;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected");
            binder = (BindService.MyBinder) service;//①
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

        mBindService = (Button) findViewById(R.id.bind_service);
        mUnbindService = (Button) findViewById(R.id.unbind_service);
        mGetServiceState = (Button) findViewById(R.id.get_service_state);
        intent = new Intent(MainActivity.this, BindService.class);

        mBindService.setOnClickListener(this);
        mUnbindService.setOnClickListener(this);
        mGetServiceState.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bind_service:
                bindService(intent, conn, BIND_AUTO_CREATE);
                break;
            case R.id.unbind_service:
                unbindService(conn);
                break;
            case R.id.get_service_state:
                Toast.makeText(MainActivity.this, "Service的count值为:" + binder.getCount(), Toast.LENGTH_SHORT).show();//②
                break;
        }
    }
}

上面①号代码用于在Activity与Service连接成功时获取Service的onBind()方法返回的MyBinder对象,②号代码可以通过MyBinder对象访问Service的运行状态。

点击绑定Service按钮,在Logcat面板可以看到如下输出:

点击获取Service状态按钮,可以看到如下图所示的输出:

点击解绑Service按钮,在Logcat面板可以看到如下输出:

如上图所示,当程序调用unbindService()方法解除对某个Service的绑定时,系统会先调用该Service的onUnbind()方法,然后再回调onDestroy()方法。

与多次调用startService()方法启动不同的是,多次调用bindService()方法并不会执行重复绑定。

Service的生命周期


随着应用程序启动Service方式不同,Service的生命周期也略有差异,如下图:

如果应用程序通过startService()方法来启动Service,Service的生命周期如上图左半部分所示。如果应用程序通过bindService()方法来启动Service,Service的生命周期如上图右半部分所示。

当Activity调用bindService()绑定一个已通过startService()启动的Service时,系统只是把Service内部IBinder对象传给Activity,并不会把该Service生命周期完全绑定到该Activity,因而当Activity调用UNBindService()方法取消与该Service的绑定时,也只是切断该Activity与Service之间的关联,并不能停止该Service组件。要停止该Service组件,还需调用stopService()方法。

IntentService


首先看一下Service本身存在的两个问题。

  • Service不会专门启动一个单独的进程,Service与它所在应用位于同一个进程中。
  • Service不是一个新的线程,因此不应该在Service中直接处理耗时的任务。

IntentService正好弥补了Service的不足:IntentService会使用队列来管理请求Intent,每当客户端代码通过Intent请求启动IntentService时,IntentService会将该Intent加入队列,然后开启一条新的worker线程来处理该Intent。对于异步的startService()请求,IntentService会按次序依次处理队列中的Intent,该线程保证同一时刻只处理一个Intent。由于IntentService使用新的worker线程处理Intent请求,因此IntentService不会阻塞主线程,所以IntentService自己就可以处理耗时任务。

IntentService的特征:

  • IntentService会创建单独的worker线程来处理所有的Intent请求。
  • IntentService会创建单独的worker线程来处理onHandleIntent()方法实现的代码,因此开发者无须处理多线程问题。
  • 当所有请求处理完成后,IntentService会自动停止,因此开发者无须调用stopSelf()方法来停止该Service。
  • 为Service的onBind()方法提供了默认实现,默认实现的onBind()方法返回null。
  • 为Service的onStartCommand()方法提供了默认实现,该实现会将请求Intent添加到队列中。

扩展IntentService实现Service无须重写onBind()、onStartCommand()方法,只要重写onHandleIntent()方法即可。

定时更换壁纸


通过AlarmManager周期性调用某个Service,从而让系统实现定时更换壁纸的功能。

该程序界面有两个按钮,一个用于启动定时更换壁纸,一个用于关闭定时更换壁纸,代码如下:

public class MainActivity extends AppCompatActivity {

    private Button mStart;
    private Button mStop;

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

        mStart = (Button) findViewById(R.id.start);
        mStop = (Button) findViewById(R.id.stop);
        //指定启动ChangeService组件
        Intent intent = new Intent(MainActivity.this, ChangeService.class);
        //创建PendingIntent对象
        final PendingIntent pi = PendingIntent.getService(MainActivity.this, 0, intent, 0);

        mStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取AlarmManager对象
                AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
                //设置每隔2秒执行pi所代表的组件一次
                alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, 2000, pi);
                mStart.setEnabled(false);
                mStop.setEnabled(true);
                Toast.makeText(MainActivity.this, "壁纸定时更换启动成功啦", Toast.LENGTH_SHORT).show();
            }
        });

        mStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mStart.setEnabled(true);
                mStop.setEnabled(false);
                //获取AlarmManager对象
                AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
                //取消对pi的调度
                alarmManager.cancel(pi);
            }
        });
    }
}

上面程序代码指定程序每2秒执行一次pi所代表的组件。程序中pi代表了ChangeService组件,ChangeService代码如下:

public class ChangeService extends Service {

    //定义定时更换的壁纸资源
    int[] wallpapers = new int[]{
            R.drawable.author,
            R.drawable.girl,
            R.drawable.life
    };

    //定义系统的壁纸管理服务
    WallpaperManager wallpaperManager;
    //定义当前所显示的壁纸
    int current = 0;

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化WallPaperManager
        wallpaperManager = WallpaperManager.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //如果到了最后一张,系统重新开始
        if (current >= 3) {
            current = 0;
        }
        try {
            //改变壁纸
            wallpaperManager.setResource(wallpapers[current++]);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return START_STICKY;
    }

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

重写Service的onStartCommand()方法,也就是每次启动该Service时都会执行onStartCommand()方法中的代码,更换壁纸的代码就放在该方法中。

为了允许该程序改变壁纸,还需在AndroidManifest.xml中添加权限:

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

运行该程序,点击开始,返回桌面即可看到系统壁纸每2秒更换一次,效果图如下:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android知识点总结

1-AIII-Service实现开屏跳转到指定Activity

21560
来自专栏KK的小酒馆

Service的跨进程开发Android开发高级进阶

Service的跨进程通信主要由两种Android提供的方法进行,一个是AIDL,通过创建一个AIDL文件来完成,另一个是利用Messenger,发送Messa...

13620
来自专栏Android干货

安卓开发_实现截图功能

31360
来自专栏编程思想之路

Android中应用调用系统权限

现在设备的安全性越来越受到重视,随之而来的便是开发中的各种不便,比如有普通权限,运行时权限,系统权限之分。对于运行时权限的添加可以参考 对于Android中各个...

30360
来自专栏刘望舒

Android网络编程(六)OkHttp3用法全解析

相关文章 Android网络编程(一)HTTP协议原理 Android网络编程(二)HttpClient与HttpURLConnection Androi...

251100
来自专栏Android知识点总结

1-AII--BroadcastReceiver广播的静态注册与动态注册

14910
来自专栏三流程序员的挣扎

Navigation 详解三

在 BottomNavigationActivity 中添加 Toolbar,修改主题为 NoActionBar 的。

13320
来自专栏mukekeheart的iOS之旅

Android基础总结(8)——服务

服务(Service)是Android中实现程序后台运行的解决方案,它非常适合用于去执行哪些不需要和用户交互而且还要长期运行的任务。服务的运行不依赖任何用户界...

40580
来自专栏QQ音乐技术团队的专栏

[Android] Toast问题深度剖析(一)

伴随着我们开发的深入,Toast 的问题也逐渐暴露出来。本文章就将解释 Toast 这些问题产生的具体原因。

2.2K150
来自专栏懒人开发

BottomNavigationView简单使用

之前见过类似这个库, 是带ripple效果的 不记得具体地址了,和这个类似 https://github.com/Ashok-Varma/BottomNav...

10530

扫码关注云+社区

领取腾讯云代金券