2、android Service 详细用法

定义一个服务

在项目中定义一个服务,新建一个ServiceTest项目,然后在这个项目中新增一个名为MyService的类,并让它继承自Service,完成后的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.jack.servicetest;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
 
public class MyService extends Service {
 
    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
 
}

目前MyService中只用一个onBind()方法,这个方法是Service中唯一的一个抽象方法,所以必须要在子类里实现。既然定义了一个服务,自然应该在服务中去处理一些事情,那处理事情的逻辑应该写在哪里?这时我们就可以重写Service中的另外一些方法了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.jack.servicetest;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
 
public class MyService extends Service {
 
    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
    }
 
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        return super.onStartCommand(intent, flags, startId);
    }
     
     
 
}

可以看到,这里我们又重写了onCreate(),onDestroy()和onStartCommand(Intent intent, int flags, int startId)这三个方法,它们是每个服务中最常用到的三个方法。其中onCreate方法会在服务创建的时候调用,onStartCommand方法会在每次服务启动的时候调用。onDestroy()方法会在服务销毁的时候调用。

通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand方法里。而当服务销毁时,我们又应该在onDestroy()方法中去回收那些不在使用的资源。

另外需要注意的是,没一个服务都需要在AndroidManifest.xml文件中进行注册才能生效,android四大组件都需要进行注册。于是我们修改AndroidManifest.xml文件,代码如下所示:

1
2
<manifest android:versioncode="1" android:versionname="1.0" package="com.jack.servicetest" xmlns:android="http://schemas.android.com/apk/res/android"><uses-sdk android:minsdkversion="13" android:targetsdkversion="17">
</uses-sdk></manifest>

这样的话,就已经将一个服务定义好了。

启动和停止服务

定义好了服务后,接下来就应该考虑如何启动以及停止这个服务。启动服务和停止服务主要借助Intent来实现,下面我们在ServiceTest项目中尝试去启动已经停止MyService这个服务。

首先修改activity_main.xml中的代码,如下所示:

1
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"><button android:id="@+id/start_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="start" service=""></button><button android:id="@+id/stop_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="stop" service=""></button></linearlayout>

上面的布局主要加入了2个按钮,用来启动和停止服务。

然后修改MainActivity中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.jack.servicetest;
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
 
public class MainActivity extends Activity implements OnClickListener{
 
    private Button startService;
    private Button stopService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService=(Button) findViewById(R.id.start_service);
        stopService=(Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch(v.getId()){
        case R.id.start_service:
            Intent startIntent =new Intent(this,MyService.class);
            startService(startIntent);//启动服务
            break;
        case R.id.stop_service:
            Intent stopIntent =new Intent(this,MyService.class);
            stopService(stopIntent);//停止服务
            break;
        default:
            break;
        }
    }
 
}

上面我们在onCreate方法中分别获取到start service按钮和stop service按钮的实例,并给它们注册了点击
事件。然后在start service按钮的点击事件里面,我们构建出了一个Intent对象,并调用startService()
方法来启动MyService这个服务。在stop service按钮的点击事件里,我们同样构建出了一个Intent对象,并调用
stopService()方法来停止MyService这个服务。startService()和stopService()方法都是定义在Context
类中的,所以我们在活动里可以直接调用这两个方法。注意,这里完全是由活动来决定服务何时停止的,如果没有点击stop service
按钮,服务就会一直处于运行状态。那服务有什么办法让自己停下来了?只需要在MyService的任何一个位置调用shopSelf()
方法就能让服务停止下来了。

接下来我们要考虑,如何才能证明服务已经成功启动或者停止了?最简单的方法就是在MyService的几个方法中加入打印日志,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.jack.servicetest;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    @Override
    public IBinder onBind(Intent arg0) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        Log.d(MyService, onCreate());
    }
 
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyService, onDestroy());
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
         
        Log.d(MyService, onStartCommand);
         
        return super.onStartCommand(intent, flags, startId);
         
    }
     
     
 
}

由此证明MyService服务确实已经成功停止下来了。

onCreate方法和onStartCommand方法的区别:onCreate方法是在服务第一次创建的时候调用的,而onStartCommand方法则在每次启动服务的时候都会调用,由于刚才我们是第一次点击start service按钮,服务此时还未创建过,所以两个方法都会执行,之后如果你在连续多点击几次start service按钮,你就会发现只有onStartCommand方法可以得到执行了。

活动和服务进行通信

目前我们希望在MyService里提供一个下载的功能,然后再活动中可以决定何时开始下载,以及随时查看下载进。实现这个功能的思路是创建一个专门的Binder对象来对下载功能进行管理,修改MyService中的代码:如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.jack.servicetest;
 
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    private DownloadBinder mBinder=new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d(MyService, startdownload executed);
        }
         
        public int getProgress(){
            Log.d(MyService, getProgress executed);
            return 0;
        }
         
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return mBinder;
    }
 
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        Log.d(MyService, onCreate());
    }
 
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyService, onDestroy());
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
         
        Log.d(MyService, onStartCommand);
         
        return super.onStartCommand(intent, flags, startId);
         
    }
     
     
 
}

这里我们新建了一个DownloadBinder类,并让它继承自Binder,然后再它的内部提供开始下载以及查看下载进度的方法。当然这只是两个模拟的方法,并没有实现真正的功能,我们在这两个方法中分别打印了一行日志。

接着,在MyService中创建了DownloadBinder的实例,然后再onBind()方法里返回了这个实例,这样MyService中的工作就全部完成了。

下面我们需要在活动中调用服务里的方法,首先需要在布局文件中新曾两个按钮,修改activity_main.xml中的代码,如下所示:

1
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"><button android:id="@+id/start_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="start" service=""></button><button android:id="@+id/stop_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="stop" service=""></button><button android:id="@+id/bind_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="bind" service=""></button><button android:id="@+id/unbind_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="unbind" service=""></button></linearlayout>

这两个活动用于在活动中进行绑定和取消绑定服务,当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了,修改MainActivity中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package com.jack.servicetest;
 
import com.jack.servicetest.MyService.DownloadBinder;
 
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
 
public class MainActivity extends Activity implements OnClickListener{
 
    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;
    private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection=new ServiceConnection() {
        /*
         * 这里创建了一个ServiceConnection的匿名类,在这里重写了onServiceConnected方法和
         * onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。
         * 在onServiceConnected方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个
         * 实例,活动和服务之间的关系就变得非常紧密了,现在我们可以在活动中根据具体的场景来调用DownloadBinder
         * 中的任何public方法,及实现了指挥服务干什么,服务就干什么的功能,这里只做了简单的测试,在onServiceConnected
         * 中调用了DownloadBinder的startDownload(),getProgress()方法。
         * */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub
             
        }
         
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub
            downloadBinder=(MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService=(Button) findViewById(R.id.start_service);
        stopService=(Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
         
        bindService = (Button) findViewById(R.id.bind_service);
        unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
         
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch(v.getId()){
        case R.id.start_service:
            Intent startIntent =new Intent(this,MyService.class);
            startService(startIntent);//启动服务
            break;
        case R.id.stop_service:
            Intent stopIntent =new Intent(this,MyService.class);
            stopService(stopIntent);//停止服务
            break;
        case R.id.bind_service:
            /*
             *现在我们需要进行活动和服务的绑定,构建一个Intent对象,然后调用bindService()方法将
             *MainActivity()和MyService进行绑定。 bindService方法接收三个参数,第一个参数就是
             *上面创建出的Intent对象,第二个参数就是前面创建出的ServiceConnection的实例,第三个
             *参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。
             *这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
             * */
            Intent bindIntent=new Intent(this,MyService.class);
            bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务
            break;
        case R.id.unbind_service:
            /*
             * 如果我们想解除活动和服务之间的绑定,调用一下unbindService()方法就可以了。
             * */
            unbindService(connection);//解绑服务
            break;
        default:
            break;
        }
    }
 
}

可以看到,首先是MyService的onCreate()方法得到了执行,然后startDownload和getProgeress方法得到了执行,说明我们确实已经在活动力成功的调用了服务里提供的方法了。另外需要注意的,任何一个服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成之后他们都可以获取到相同的DownloadBinder实例。

服务的生命周期

服务也有自己的生命周期,前面我们使用到的onCreate(),onStartCommand(),onBind()和onDestroy()等方法
都是在服务的生命周期内可能回掉的方法。
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()。如果 这个服务之前还没创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后一直保持运行状态,
直到stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会
执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能*地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示
服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这
两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉?根据android系统的机制,一个服务只要被启动或者绑定了之后就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

使用前台服务

服务几乎都是在后台运行的,一直以来它都是默默的做着辛苦的工作。但是服务的系统优先级还是比较低的,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果你希望服务可以一直 保持运行状态,而 不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。前台服务和普通服务最大的区别就在于,它会一直有一个正在运行的系统状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

下面我们创建一个前台服务吧,修改MyService中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.jack.servicetest;
 
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    private DownloadBinder mBinder=new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d(MyService, startdownload executed);
        }
         
        public int getProgress(){
            Log.d(MyService, getProgress executed);
            return 0;
        }
         
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return mBinder;
    }
 
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        @SuppressWarnings(deprecation)
        Notification notification=new Notification(R.drawable.ic_launcher,
                Notification comes,System.currentTimeMillis());
        Intent notificationIntent=new Intent(this,MainActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
                0);
        notification.setLatestEventInfo(this, this is title, this is content,
                pendingIntent);
        startForeground(1, notification);
        /*
         可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。这就是我们前面学习的
         创建通知的方法。只不过这次在构建出Notification对象并没有使用NotificationManager来将通知显示
         出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法
         的第一个参数,第二个参数则是构建出来的Notification对象。调用startForeground()方法后就会让MyService变成
         一个前台服务,并在系统状态显示出来。
          
         */
        Log.d(MyService, onCreate());
    }
 
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyService, onDestroy());
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
         
        Log.d(MyService, onStartCommand);
         
        return super.onStartCommand(intent, flags, startId);
         
    }
     
     
 
}

现在重新运行下程序,并点击start service或bind service按钮,MyService就会以前台服务的模式开启了,并且在系统状态栏会显示一个通知图标,下拉状态栏后可以看到该通知的详细内容

使用IntentService

我们知道服务中的代码都是默认运行在主线程当中,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。

所以这个时候,就需要用到Android多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,然后再这里去处理那些耗时的逻辑。因此,一个比较标准的服务就可以写成如下形式了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.jack.servicetest;
 
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    /*private DownloadBinder mBinder=new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d(MyService, startdownload executed);
        }
         
        public int getProgress(){
            Log.d(MyService, getProgress executed);
            return 0;
        }
         
    }*/
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        //return mBinder;
        return null;
    }
 
    /*@Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        @SuppressWarnings(deprecation)
        Notification notification=new Notification(R.drawable.ic_launcher,
                Notification comes,System.currentTimeMillis());
        Intent notificationIntent=new Intent(this,MainActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
                0);
        notification.setLatestEventInfo(this, this is title, this is content,
                pendingIntent);
        startForeground(1, notification);
         
         可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。这就是我们前面学习的
         创建通知的方法。只不过这次在构建出Notification对象并没有使用NotificationManager来将通知显示
         出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法
         的第一个参数,第二个参数则是构建出来的Notification对象。调用startForeground()方法后就会让MyService变成
         一个前台服务,并在系统状态显示出来。
          
          
        Log.d(MyService, onCreate());
    }
*/
    /*@Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyService, onDestroy());
    }*/
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
         
        Log.d(MyService, onStartCommand);
        new Thread(new Runnable(){
 
            @Override
            public void run() {
                // TODO Auto-generated method stub
                //处理具体的逻辑
            }
             
        }).start();
        return super.onStartCommand(intent, flags, startId);
         
    }
     
     
 
}

但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。所以,如果想要实现一个服务执行完毕后自动停止的功能,就可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.jack.servicetest;
 
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
 
public class MyService extends Service {
 
    /*private DownloadBinder mBinder=new DownloadBinder();
    class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d(MyService, startdownload executed);
        }
         
        public int getProgress(){
            Log.d(MyService, getProgress executed);
            return 0;
        }
         
    }*/
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        //return mBinder;
        return null;
    }
 
    /*@Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        @SuppressWarnings(deprecation)
        Notification notification=new Notification(R.drawable.ic_launcher,
                Notification comes,System.currentTimeMillis());
        Intent notificationIntent=new Intent(this,MainActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this, 0,notificationIntent,
                0);
        notification.setLatestEventInfo(this, this is title, this is content,
                pendingIntent);
        startForeground(1, notification);
         
         可以看到,这里只是修改了onCreate()方法中的代码,相信这部分代码你会非常眼熟。这就是我们前面学习的
         创建通知的方法。只不过这次在构建出Notification对象并没有使用NotificationManager来将通知显示
         出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法
         的第一个参数,第二个参数则是构建出来的Notification对象。调用startForeground()方法后就会让MyService变成
         一个前台服务,并在系统状态显示出来。
          
          
        Log.d(MyService, onCreate());
    }
*/
    /*@Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyService, onDestroy());
    }*/
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
         
        Log.d(MyService, onStartCommand);
        new Thread(new Runnable(){
 
            @Override
            public void run() {
                // TODO Auto-generated method stub
                //处理具体的逻辑
                stopSelf();
            }
             
        }).start();
        return super.onStartCommand(intent, flags, startId);
         
    }
     
     
 
}

虽然这种写法并不复杂,但是总会有一些人忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的,会自动停止的服务,android专门提供了一个IntentService类,这个类就很好的解决了前面所提到的两种尴尬,下面我们来看下它的用法。

新建一个MyIntentService类继承IntentService,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.jack.servicetest;
 
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
 
public class MyIntentService extends IntentService {
 
    /*
     这里首先是提供了一个无参的构造函数,并且必须在其内部调用父类的有参构造函数。然后要在子类中去实现
     onHandleIntent()这个抽象方法,在这个方法中可以处理一些具体的逻辑,而且不用担心ANR的问题,因为
     这个方法已经是在子线程中运行的了。这里为了证实一下,我们在onHandleIntent()方法中打印了当前线程的id。
     另外根据IntentService的特性,这个服务在运行结束后应该是会自动停止的,所以我们又重写了onDestroy()方法,在
     这里也打印l一行日志,以证实是不是停止掉了。
     */
    public MyIntentService() {
        super(MyIntentService);//调用父类的有参构造函数
        // TODO Auto-generated constructor stub
    }
 
    @Override
    protected void onHandleIntent(Intent arg0) {
        // TODO Auto-generated method stub
 
        //打印当前线程的id
        Log.d(MyIntentService, Thread id is +Thread.currentThread().getId());
    }
 
    @Override
    public void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        Log.d(MyIntentService, onDestroy() executed);
    }
 
     
}

接下来修改activity_main.xml中的代码,加入一个用于启动MyIntentService这个服务的按钮,如下所示:

1
<linearlayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"><button android:id="@+id/start_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="start" service=""></button><button android:id="@+id/stop_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="stop" service=""></button><button android:id="@+id/bind_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="bind" service=""></button><button android:id="@+id/unbind_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="unbind" service=""></button><button android:id="@+id/start_intent_service" android:layout_height="wrap_content" android:layout_width="match_parent" android:text="start" intentservice=""></button></linearlayout>

然后修改MainActivity中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.jack.servicetest;
 
//import com.jack.servicetest.MyService.DownloadBinder;
 
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
 
public class MainActivity extends Activity implements OnClickListener{
 
    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;
    private Button startIntentService;
    //private MyService.DownloadBinder downloadBinder;
    private ServiceConnection connection=new ServiceConnection() {
        /*
         * 这里创建了一个ServiceConnection的匿名类,在这里重写了onServiceConnected方法和
         * onServiceDisconnected方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。
         * 在onServiceConnected方法中,我们又通过向下转型得到了DownloadBinder的实例,有了这个
         * 实例,活动和服务之间的关系就变得非常紧密了,现在我们可以在活动中根据具体的场景来调用DownloadBinder
         * 中的任何public方法,及实现了指挥服务干什么,服务就干什么的功能,这里只做了简单的测试,在onServiceConnected
         * 中调用了DownloadBinder的startDownload(),getProgress()方法。
         * */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub
             
        }
         
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub
            /*downloadBinder=(MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();*/
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService=(Button) findViewById(R.id.start_service);
        stopService=(Button) findViewById(R.id.stop_service);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
         
        bindService = (Button) findViewById(R.id.bind_service);
        unbindService = (Button) findViewById(R.id.unbind_service);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);
         
         
        startIntentService=(Button) findViewById(R.id.start_intent_service);
        startIntentService.setOnClickListener(this);
         
         
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        switch(v.getId()){
        case R.id.start_service:
            Intent startIntent =new Intent(this,MyService.class);
            startService(startIntent);//启动服务
            break;
        case R.id.stop_service:
            Intent stopIntent =new Intent(this,MyService.class);
            stopService(stopIntent);//停止服务
            break;
        case R.id.bind_service:
            /*
             *现在我们需要进行活动和服务的绑定,构建一个Intent对象,然后调用bindService()方法将
             *MainActivity()和MyService进行绑定。 bindService方法接收三个参数,第一个参数就是
             *上面创建出的Intent对象,第二个参数就是前面创建出的ServiceConnection的实例,第三个
             *参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。
             *这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
             * */
            Intent bindIntent=new Intent(this,MyService.class);
            bindService(bindIntent, connection, BIND_AUTO_CREATE);//绑定服务
            break;
        case R.id.unbind_service:
            /*
             * 如果我们想解除活动和服务之间的绑定,调用一下unbindService()方法就可以了。
             * */
            unbindService(connection);//解绑服务
            break;
        case R.id.start_intent_service:
            //打印主线程的id
            Log.d(MainActivity, Thread id is+Thread.currentThread().getId());
            Intent intentService=new Intent(this,MyIntentService.class);
            startService(intentService);
            break;
        default:
            break;
        }
    }
 
}
 
/*
 服务也有自己的生命周期,前面我们使用到的onCreate(),onStartCommand(),onBind()和onDestroy()等方法
 都是在服务的生命周期内可能回掉的方法。
    一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()。如果
    这个服务之前还没创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后一直保持运行状态,
    直到stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会
    执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()
  或stopSelf()方法,服务就会停止下来了。
  另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,
  如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里
  返回的IBinder对象的实例,这样就能*地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
   当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示
   服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这
   两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,
   这种情况下该如何才能让服务销毁掉?根据android系统的机制,一个服务只要被启动或者绑定了之后就会一直处于运行状态,必须要让以上两种条件同时
   不满足,服务才能被销毁。所以,这种情况下需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
 */

可以看到,我们在start intentservice按钮的点击事件里面去启动MyIntentService这个服务,并在这里打印了一下主线程的id,其实IntentService的用法和普通的服务没什么两样。

AndroidManifest.xml里注册,如下所示:

1
2
3
4
5
6
<manifest android:versioncode="1" android:versionname="1.0" package="com.jack.servicetest" xmlns:android="http://schemas.android.com/apk/res/android"><uses-sdk android:minsdkversion="13" android:targetsdkversion="17">
        <service android:name="com.jack.servicetest.MyService"></service>
        <service android:name="com.jack.servicetest.MyIntentService"></service>
    </application>
 
</uses-sdk></manifest>

重新运行程序,界面如下所示:

可以看到MyIntentService和MainActivity所在的线程id不一样,而且onDestory()方法也得到了执行,说明MyIntentService在运行完毕后确实自动停止了。集开启线程和自动停止于一身。

服务的最佳实践---------后台执行的定时任务

Android中实现定时任务一般有两种方式,一种是使用java api里提供的Timer类,一种是使用android的Alarm机制。
这两种方式在多数情况下都能实现类似的效果,但是Timer有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为了能让电池更加耐用,每种手机都会有自己的休眠策略,andorid手机就会在长时间不操作的情况下自动让cpu进入的到睡眠状态,这就有可能导致Timer中的定时任务无法正常运行。而Alarm机制不存在这种情况,它具有唤醒cpu的功能,即可以保证每次需要执行定时任务的时候cpu都能正常工作。需要注意,这里的唤醒cpu和唤醒屏幕完全不是同一个概念,不要弄混淆了。
我们来看看Alarm机制的用法吧,主要需要借助AlarmManager类来实现。这个类和NotificationManager有点类似,都是通过调用Context的getSystemService()方法来获取实例的,只是这里需要传入的参数是Context.ALARM_SERVICE.
因此,获取一个AlarmManager的实例就可以写成:
AlarmManager manager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
接下来调用AarmManager的set()方法就可以设置一个定时任务了,比如说想要设定一个任务在10秒后执行,就可以写成:
long triggerAtTime=SystemClock.elapsedRealtime()+10*1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pendingIntent);
第一个参数是一个整形参数,用于指定AlarmManager的工作类型,有四种值可选,分别是ELAPSED_REALTIME,ELAPSED_REALTIME_WAKEUP,
RTC 和 RTC_WAKEUP。其中ELAPSED_REALTIME表示让定时任务的触发从系统开机开始算起,但不会唤醒cpu。
ELAPSED_REALTIME_WAKEUP同样表示让定时任务的触发时间从系统开机开始算起,但会唤醒cpu。
RTC表示让定时任务的触发时间从1970年1月1日0点开始算起,但不会唤醒cpu。

RTC_WAKEUP同样表示让定时任务的触发时间从1970年1月1日0点开始算起,但会唤醒cpu。使用SystemClock.elapsedRealtime()方法
可以获取到系统开机至今所历经的毫秒数,使用System.currentTimeMillis()方法可以获取到1970年1月1日0点
至今所经历时间的毫秒数。
第二个参数就是定时任务触发的时间,以毫秒为单位。如果第一个参数使用的是ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP则这里传入开机至今的时间在加上延迟执行的时间。如果第一个参数使用的是RTC或RTC_WAKEUP,则这里传入1970年1月1日0点至今的时间再加上延迟执行的时间。
第三个参数是一个PendingIntent,对于它应该不会陌生了 吧。这里我们一般会调用getBroadcast()方法来
获取一个能够执行广播的PendingIntent。这样当定时任务被触发的时候,广播接收器的onReceive()方法就可以得到执行。
了解了 set()方法的每个参数之后,你应该能想到,设定一个任务在10秒后执行还可以写成:
long triggerAtTime=System.curentTimeMillis()+10*1000;
manager.set(AlarmManager.RTC_WAKEUP,triggerAtTime,pendingIntent);
现在已经掌握了Alarm机制的基本用法,下面我们就来创建一个可以长期在后台执行定时任务的服务。创建一个ServiceBestPractice项目,
然后新增一个LongRunningService类,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.jcak.servicebestpractice;
 
import java.util.Date;
 
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
 
public class LongRunningService extends Service {
 
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        return null;
    }
 
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // TODO Auto-generated method stub
        new Thread(new Runnable(){
 
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Log.d(LongRunningService,executed at +new Date().toString());
            }
             
        }).start();
        AlarmManager manager=(AlarmManager) getSystemService(ALARM_SERVICE);
        int anHour=10*1000;
        long triggerAtTime=SystemClock.elapsedRealtime()+anHour;
        Intent i=new Intent(this,AlarmReceiver.class);
        PendingIntent pi=PendingIntent.getBroadcast(this, 0, i, 0);
        manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                triggerAtTime, pi);
         
        return super.onStartCommand(intent, flags, startId);
    }
 
}

在onStartCommand()方法中开启了一个子线程,然后在子线程里就可以执行具体的逻辑操作了,这里简单的,只是打印了当前的时间。

创建线程之后的代码就是上面讲解的Alarm机制的用法,先是获取到了AlarmManager的实例,然后定义任务的触发时间为10秒,在使用PendingIntent指定处理定时任务的广播接收器为AlarmReceiver,最后调用set()方法完成设定。显然,AlarmReceiver不存在,我们就创建一个,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.jcak.servicebestpractice;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
 
public class AlarmReceiver extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub
 
        Intent i=new Intent(context,LongRunningService.class);
        context.startService(i);
    }
 
}

onReceiver()方法里的代码非常简单,就是构建出了一个Intent对象,然后去启动LongRunningService这个服务

。这就已经将一个长期服务在后台定时运行的服务完成了。因为一旦启动了LongRunningService,就会在onStartCommand()方法里设定一个定时任务,这样10秒后AlarmReceiver的onReceive()方法就将得到执行了,然后我们在这里再次启动LongRunningService,这样就形成了一个永久的循环,保证LongRunningService可以每隔10秒就会启动一次,这个长期在后台运行的服务就完成了。

接下来,我们需要在打开程序的时候启动一次LongRunningService,之后LongRunningService就可以一直运行了。修改MainActivity中的代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.jcak.servicebestpractice;
 
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.Menu;
 
public class MainActivity extends Activity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent=new Intent(this,LongRunningService.class);
        startService(intent);
         
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
}

最后要注册服务和广播,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--?xml version=1.0 encoding=utf-8?-->
<manifest android:versioncode="1" android:versionname="1.0" package="com.jcak.servicebestpractice" xmlns:android="http://schemas.android.com/apk/res/android">
 
    <uses-sdk android:minsdkversion="13" android:targetsdkversion="17">
 
     
         
            <intent-filter>
                 
 
                <category android:name="android.intent.category.LAUNCHER">
            </category></action></intent-filter>
        </activity>
         
         
        <service android:name="com.jcak.servicebestpractice.LongRunningService"></service>
        <receiver android:name="com.jcak.servicebestpractice.AlarmReceiver"></receiver>
    </application>
 
</uses-sdk></manifest>

现在运行一下程序,然后观察LogCat中打印的日志,

可以看到LongRunningService每隔10秒打印一条日志。

另外需要注意的是,从android4.4版开始,Alarm任务的触发时间将会变得不准确,有可能会延迟一段时间后任务才能得到执行。这并不是bug,而是系统在耗电方面进行的优化。系统会自动检测目前有多少Alarm任务存在,然后将触发时间将近的几个任务存放在一起执行,这就可以大幅度减少cpu被唤醒的次数,从而有效延长电池的使用时间。

当然,如果要求Alarm任务的执行时间必须准确无误,android仍然提供l解决方案。使用AlarmManager的setExact()方法来替代set()方法,就可以保证任务准时执行了。

 
上一篇:大组合数:Lucas定理


下一篇:AlarmManager研究