[Android 基础系列]Service、IntentService和习以为常的误解

前言:

       也许是低门槛原因,最初接触Android的人写了很多书、博文,创造了一个邪论:Activity就是弄界面的,Service就是弄后台的,进而将“播放音乐”这种演变为“耗时操作”,进而演绎成:“耗时的、长时间运行的都需要使用service”。只想说:MDZZ!
       原意是想全文自己写,但看了一眼API文档,整理的实在是太好了,所以本文会摘录API的内容并结合重点写一点内容。

正文:

Service:

API文档中的概述如下:
A Service is an application component representing either an application's desire to perform a longer-running operation while not interacting with the user or to supply functionality for other applications to use. Each service class must have a corresponding <service> declaration in its package's AndroidManifest.xml. Services can be started with Context.startService() andContext.bindService().
Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Processes and Threads. The IntentService class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.

给大家蹩脚的翻译一下:
Service是一个应用组件,代表着:“在不和用户交互的情况下,进行‘持续’运行的应用需求”或者“为其他应用提供功能性服务” 。每个Service类(当然是你要用的)都需要在Manifest文件中声明(利用<service>节点),Service有两种方式启动:Context.startService() 和 Context.bindService() 函数。
注意注意注意!service和其他应用组件一样,运行在宿主进程的主线程中,这意味着如果你的service需要做 频繁的CPU计算操作(例如播放MP3)或者阻塞的操作(例如访问网络),那应该创建独立的线程(就是让你规避主线程)。
然后提到了IntentService是一个规范的实现,它在子线程中处理工作(言下之意你不用手动开辟子线程)。

请注意我上面用了一个词:“持续”运行,这并不准确,但我不想使用长时间运行这个词,总会被误解为“阻塞”。

OK,再看看Service是个什么鬼,API说你所疑惑的可以通过排除法解决:
•	A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
•	A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).
Service不是一个分隔独立的进程
Service不是线程,不代表它脱离主线程工作

把两点合一起:service是运行在启动它的应用程序进程中的,应用程序这个进程有主线程(俗称UI线程),service不脱离主线程工作,所以你不能阻塞service的主线程,否则这会带来ANR。

我们先不看生命周期(其实大家对此是比较明白的)
我们看API中提到的Local Service使用,顺便引出一个疑惑。

写一个Service:
public class LocalService extends Service {
    private NotificationManager mNM;

    // Unique Identification Number for the Notification.
    // We use it on Notification start, and to cancel it.
    private int NOTIFICATION = R.string.local_service_started;

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }

    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

        // Display a notification about us starting.  We put an icon in the status bar.
        showNotification();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("LocalService", "Received start id " + startId + ": " + intent);
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancel(NOTIFICATION);

        // Tell the user we stopped.
        Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
    }

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

    // This is the object that receives interactions from clients.  See
    // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();

    /**
     * Show a notification while this service is running.
     */
    private void showNotification() {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = getText(R.string.local_service_started);

        // Set the icon, scrolling text and timestamp
        Notification notification = new Notification(R.drawable.stat_sample, text,
                System.currentTimeMillis());

        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, LocalServiceActivities.Controller.class), 0);

        // Set the info for the views that show in the notification panel.
        notification.setLatestEventInfo(this, getText(R.string.local_service_label),
                       text, contentIntent);

        // Send the notification.
        mNM.notify(NOTIFICATION, notification);
    }
}

省略声明

演示绑定/启动
private LocalService mBoundService;

private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        // This is called when the connection with the service has been
        // established, giving us the service object we can use to
        // interact with the service.  Because we have bound to a explicit
        // service that we know is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        mBoundService = ((LocalService.LocalBinder)service).getService();

        // Tell the user about this for our demo.
        Toast.makeText(Binding.this, R.string.local_service_connected,
                Toast.LENGTH_SHORT).show();
    }

    public void onServiceDisconnected(ComponentName className) {
        // This is called when the connection with the service has been
        // unexpectedly disconnected -- that is, its process crashed.
        // Because it is running in our same process, we should never
        // see this happen.
        mBoundService = null;
        Toast.makeText(Binding.this, R.string.local_service_disconnected,
                Toast.LENGTH_SHORT).show();
    }
};

void doBindService() {
    // Establish a connection with the service.  We use an explicit
    // class name because we want a specific service implementation that
    // we know will be running in our own process (and thus won't be
    // supporting component replacement by other applications).
    bindService(new Intent(Binding.this, 
            LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
    mIsBound = true;
}

void doUnbindService() {
    if (mIsBound) {
        // Detach our existing connection.
        unbindService(mConnection);
        mIsBound = false;
    }
}

@Override
protected void onDestroy() {
    super.onDestroy();
    doUnbindService();
}

我们看到了使用bindService 绑定并启动service,并在Activity销毁时解除绑定。所以有人疑惑了:我并不想我的service这时候被结束掉,但不解除绑定是不规范的、甚至担心是否会影响到Activity的销毁、回收。

OK,我们必须再回忆一下Service的介绍,没有一句话说它是为Activity提供服务的,它本身是一个服务没错,它也只是一个服务,而不是为Activity提供服务的服务。

前面提到了两种“启动”service的方法 bindService和startService,严格的说:startService是启动,而且是Context中定义的方法,从来没有说是Activity中定义的;bindService是绑定service,绑定时如果没有启动会帮助你启动,但本质是绑定。
存在这样的差异:
启动:我只是把你弄活了,然后没有半毛钱关系。
绑定:你要跟着我混!我不要你了你就可以走了。

Context类中对unbindService的描述是:
Disconnect from an application service. You will no longer receive calls as the service is restarted, and the service is now allowed to stop at any time.
断开应用服务连接,你不会再收到service重启的消息(回调),这个service也可以在任意时间被系统终止了。

这里我们做一个简单的测试,让两个Activity绑定同一个Service,看解除绑定对service的影响:
package com.example.testservice;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;

public class MainActivity extends Activity {
	
	private final static String TAG = "MainActivity";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		Log.d(TAG, "onCreate");
		doBindService();
		
		findViewById(R.id.button).setOnClickListener(new OnClickListener() {
			
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				startActivity(new Intent(MainActivity.this,SecondActivity.class));
			}
		});
	}
	
	private LocalService mBoundService;

	private ServiceConnection mConnection = new ServiceConnection() {
	    public void onServiceConnected(ComponentName className, IBinder service) {
	        mBoundService = ((LocalService.LocalBinder)service).getService();

	        // Tell the user about this for our demo.
	        Log.d(TAG, "service bind");
	    }

	    public void onServiceDisconnected(ComponentName className) {
	        // This is called when the connection with the service has been
	        // unexpectedly disconnected -- that is, its process crashed.
	        // Because it is running in our same process, we should never
	        // see this happen.
	        mBoundService = null;
	        Log.d(TAG, "service unbind");
	    }
	};

	void doBindService() {
	    // Establish a connection with the service.  We use an explicit
	    // class name because we want a specific service implementation that
	    // we know will be running in our own process (and thus won't be
	    // supporting component replacement by other applications).
		Log.d(TAG, "do service bind");
	    bindService(new Intent(this, 
	            LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
	    mIsBound = true;
	}
	
	private boolean mIsBound  = false;

	void doUnbindService() {
	    if (mIsBound) {
	        // Detach our existing connection.
	    	Log.d(TAG, "do service unbind");
	        unbindService(mConnection);
	        mIsBound = false;
	    } else {
	    	Log.d(TAG, "no necessary to unbind,it is not binding");
	    }
	}

	@Override
	protected void onDestroy() {
		Log.d(TAG, "onDestroy");
	    super.onDestroy();
	    doUnbindService();
	}
}
package com.example.testservice;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class SecondActivity extends Activity {
	
	private final static String TAG = "SecondActivity";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_second);
		doBindService();
	}
	
	private LocalService mBoundService;

	private ServiceConnection mConnection = new ServiceConnection() {
	    public void onServiceConnected(ComponentName className, IBinder service) {
	        mBoundService = ((LocalService.LocalBinder)service).getService();

	        // Tell the user about this for our demo.
	        Log.d(TAG, "service bind");
	    }

	    public void onServiceDisconnected(ComponentName className) {
	        // This is called when the connection with the service has been
	        // unexpectedly disconnected -- that is, its process crashed.
	        // Because it is running in our same process, we should never
	        // see this happen.
	        mBoundService = null;
	        Log.d(TAG, "service unbind");
	    }
	};

	void doBindService() {
	    // Establish a connection with the service.  We use an explicit
	    // class name because we want a specific service implementation that
	    // we know will be running in our own process (and thus won't be
	    // supporting component replacement by other applications).
		Log.d(TAG, "do service bind");
	    bindService(new Intent(this, 
	            LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
	    mIsBound = true;
	}
	
	private boolean mIsBound  = false;

	void doUnbindService() {
	    if (mIsBound) {
	        // Detach our existing connection.
	    	Log.d(TAG, "do service unbind");
	        unbindService(mConnection);
	        mIsBound = false;
	    } else {
	    	Log.d(TAG, "no necessary to unbind,it is not binding");
	    }
	}

	@Override
	protected void onDestroy() {
		Log.d(TAG, "onDestroy");
	    super.onDestroy();
	    doUnbindService();
	}
}
package com.example.testservice;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/** 
 * @ClassName: LocalService 
 * @Description: TODO
 * @date 2016年4月27日 下午3:43:57
 *  
 * @author leobert.lan
 * @version 1.0
 */
public class LocalService extends Service {
	
	private final static String TAG = "LocalService";

    /**
     * Class for clients to access.  Because we know this service always
     * runs in the same process as its clients, we don't need to deal with
     * IPC.
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            return LocalService.this;
        }
    }

    @Override
    public void onCreate() {
    	Log.i(TAG, "service onCreate");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Received start id " + startId + ": " + intent);
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
    	Log.i(TAG, "service destroy");
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
    	Log.i(TAG, "service onBind");
        return mBinder;
    }

    // This is the object that receives interactions from clients.  See
    // RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();

}
我截取了一下logcat,但是eclipse的背景。。。




我们看到,不是解除绑定就关闭service的,是service不再被绑定的时候,它会被干掉。

正如API提供的demo中注释所说的一样:
// This is called when the connection with the service has been
        // established, giving us the service object we can use to
        // interact with the service.  Because we have bound to a explicit
        // service that we know is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
bindService可以获得service对象,并且和它进行交互(说白了就是想调用他的方法)。
我们还有另外一个有逼格的东西:IntentService。

IntentService:

文档这样描述它:
IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This "work queue processor" pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time.
IntentService是service的子类,设计用来处理异步请求,通过startService函数的调用来发送请求,吧啦吧啦一堆。

要使用它的话需要实现onHandlerIntent方法,这个方法的描述:
This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().
Intent是有序到达、处理的,先来的先处理,耗时的话后面的等着,不会阻塞主线程之类的,干完活自己干死自己,所以你不要调用stopSelf().

然而我们经常觉得没啥卵用。

多线程:

这里话分两头,前面我们知道service不是thread,耗时操作应该规避主线程的,所以要开辟子线程做,IntentService做了一定的封装,帮助开辟了子线程。
在Android使用多线程要知道一件事情:Dalvik毕竟能提供的资源有限,意味着回收很重要,开辟线程会提高资源使用程度,但也有被系统干死回收的可能,依托Activity开辟子线程(初衷一定是为了耗时的code、阻塞的code)有Activity面临关闭而子线程操作没有完成的可能,例如日记本应用。依托Service的生命力顽强一点。

不过这个没法演示。

后记:

网上的文章有好有坏,大家还是要多看API文档靠谱,国人的转载风格大家都懂的。我写东西一般本着负责的心,没法保证都是对的,如果有错误的内容还请大家指出。








  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值