前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android应用界面开发——Service与IntentService(实现定时更换壁纸)

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

作者头像
trampcr
发布2018-09-28 15:42:22
2.3K0
发布2018-09-28 15:42:22
举报
文章被收录于专栏:7号代码7号代码

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组件如下:

代码语言:javascript
复制
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。代码如下:

代码语言:javascript
复制
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类代码如下:

代码语言:javascript
复制
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的运行状态。

代码语言:javascript
复制
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,从而让系统实现定时更换壁纸的功能。

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

代码语言:javascript
复制
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代码如下:

代码语言:javascript
复制
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中添加权限:

代码语言:javascript
复制
<uses-permission android:name="android.permission.SET_WALLPAPER" />

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016.08.02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Service简介
  • 创建、配置Service
  • 启动和停止Service——startService()方式启动
  • 绑定本地Service并与之通信——bindService()方式启动
  • Service的生命周期
  • IntentService
  • 定时更换壁纸
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档