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秒更换一次,效果图如下:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容