service的进一步学习和总结(aidl binder机制和进程间通讯)
一、服务的一些分类:
android service主要分系统服务和应用程序服务,系统服务,framwork提供的,而应用程序服务是用户自己定义的继承Service的服务。
应用程序服务又分为2类:本地服务 和 远程服务,他们的区别在于:创建的服务的客户端与所创建的服务是否在同一个进程中
本地服务:利用startService或者bindService创建出的服务,若该服务与创建服务的Activity在同一个进程中则称为本地服务。本地服务只在创建该服务的进程内部使用,该应用程序终止服务也跟着终止。
远程服务:远程服务与创建者不在同一个进程中,因此主程序终止后服务仍然可以运行。
二、本地服务的介绍:
用做个音乐播放器为例,为了让音乐在后台播放,我们一般创建一个播放音乐的服务,在activity里调用服务即可。
主要步骤:1、创建service,在里面实现音乐的播放、暂停等功能。
2、实现onbind返回的类,它继承binder类,主要是向activity抛出服务对象,供activity使用。
3、在activity里面实现ServiceConnection接口类,然后bindservice(intent,new MusicServiceConnection(), Context.BIND_AUTO_CREATE) 注意第三个参数是自动生成本地服务的标记,当待绑定的服务不存在时使用。当待绑定的服务还没运行起来,绑定前先由framwork生成服务,再进行绑定。
4、在onServiceConnected 初始化实现onbind返回的类,利用该对象可以利用getService获取绑定的服务对象来控制音乐的播放。
5、利用广播来更新点击通知栏的播放按钮后activity的控件的状态。
下面给出相应的demo:是音乐播放器的实现,在通知栏中点击播放暂停音乐与在activity中点击时候它们的控件状态都是一致的。
public class MyService extends Service { public static MediaPlayer mediaPlayer; public String path = "/data/data/com.example.user.myservicedemo/files/she-say.mp3"; private Notification notification; private MediaSession mSession; @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service.' init(); return new MyBinder(); } public static final int STATE_STOP = 0; public static final int STATE_PAUSE = 1; public static final int STATE_PLAY = 2; public static int musicPlayerState = STATE_STOP; public void init() { startNotification(); rigisterNotificationListener(); } public void startMusic() { if(musicPlayerState == STATE_STOP) { mediaPlayer = new MediaPlayer(); Uri myUri = Uri.parse(path); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); try { mediaPlayer.setDataSource(getApplicationContext(), myUri); mediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } } mediaPlayer.start(); musicPlayerState = STATE_PLAY; startNotification(); } public void pauseMusic() { if(mediaPlayer!=null&&mediaPlayer.isPlaying()) mediaPlayer.pause(); musicPlayerState = STATE_PAUSE; startNotification(); } class MyBinder extends Binder { MyService getService() { return MyService.this; } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void startNotification() { mSession = new MediaSession(getApplicationContext(), this.getClass().getName()); int play = musicPlayerState == STATE_PLAY?R.drawable.pause:R.drawable.play; notification = new Notification.Builder(this) .setContentTitle("Music") .setContentText("她说") .setSmallIcon(R.drawable.ab) .addAction(play, "",PendingIntent.getBroadcast(this, 0, new Intent().setAction("android.intent.action.paly"),0)) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.a)) .setStyle(new Notification.MediaStyle() .setShowActionsInCompactView(0) .setMediaSession(mSession.getSessionToken())) .build(); startForeground(101,notification); } public void rigisterNotificationListener() { BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case "android.intent.action.paly": if(musicPlayerState == STATE_PLAY) { pauseMusic(); startNotification(); } else { startMusic(); startNotification(); } break; default: break; } } }; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("android.intent.action.paly"); registerReceiver(broadcastReceiver ,intentFilter); } }
public class MainActivity extends AppCompatActivity { private Button button; private MyService myService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } public void init() { Intent intent = new Intent(this ,MyService.class); bindService(intent,new MusicServiceConnection(), Context.BIND_AUTO_CREATE); button = (Button) findViewById(R.id.button); rigistMusicListner(); } class MusicServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { myService = ((MyService.MyBinder) service).getService(); } @Override public void onServiceDisconnected(ComponentName name) { } } public void start(View view) { //当然这里的MyService.musicPlayerState 可以利用bind来抛出 if (MyService.musicPlayerState == MyService.STATE_STOP||MyService.musicPlayerState == MyService.STATE_PAUSE) { myService.startMusic(); } else { myService.pauseMusic(); } updateButton(button); } public void updateButton(View view) { if(view.getId() == R.id.button) { //button.getText() 是为play,但是下面用button.getText()=="play" ,第一次会返回false,==是判断2个字符串是否为同一对象,第一次的play是button初始化的时候就有这个对象,而第二次setText //是在字符串常量区。 if (button.getText().equals("play")) button.setText("pause"); else button.setText("play"); } } //点击通知栏时候收到的广播在activity里更新控件状态 public void rigistMusicListner() { BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { switch (intent.getAction()) { case "android.intent.action.paly": updateButton(button); break; default: break; } } }; IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("android.intent.action.paly"); registerReceiver(receiver,intentFilter); } }
三、远程服务的介绍:
调用远程服务,我和原来坐过的一样:
1、写远程服务并在mainfest内配置,并在该服务的进程包里添加一个aidl文件RemoteService,申明一些服务抛出的方法。(android studio 下点击project ,在main目录下)。
2、clean(或者 rebuild)下工程后(会在目录下生成相应的RemoteService.java文件(可以在服务里写Mybinder extends Stub 就会让你导包,然后选择本项目目录,然后在打开文件的地方点击邮件可找到生的java文件了),里面是java的接口RemoteService和子类Stub,通过Stub子类的asasInterface(binder)可以获取RemoteService的实例对象),在服务里写如onbind方法返回的类,该类继承RemoteServce.Stub。
3、将aidl文件拷贝到调用服务的activity的对应的project - main 目录下,aidl下的包名与远程服务所在的包名得一致。
4、在bind时候回调函数中利用asasInterface(service)获得实现接口的实例对象,利用该对象就可以调用服务里的方法对服务进行操作了。
其中遇到了个问题,在bindService时候i启动服务总是失败,经查资料发现android 5.0 后不能隐式的启动服务了,即在清单里定义intentfilter 里添加action,再利用Intent intent = new intent("") 这种形式绑定服务后就会曝出:
Service Intent must be explicit: Intent { act=com.example.user.myservicedemo.IMyAidlInterface } 的异常。
5、android 6.0中提到了新的方法,来实现进程间通讯,这个以后有时间也总结一下。
解决办法就是将Intent转化为显示的:
方法一:相对笔记麻烦,但是里面的一些方法值得学习下
在activity里面定义一个函数:
public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) { // Retrieve all services that can match the given intent PackageManager pm = context.getPackageManager(); //获得所有的应用 List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0); //所有应用中查询服务,存入resolveInfo // Make sure only one match was found if (resolveInfo == null || resolveInfo.size() != 1) { return null; } // Get component info and create ComponentName ResolveInfo serviceInfo = resolveInfo.get(0); String packageName = serviceInfo.serviceInfo.packageName; //获取包 String className = serviceInfo.serviceInfo.name; //获取类名(全路径) ComponentName component = new ComponentName(packageName, className); // Create a new intent. Use the old one for extras and such reuse Intent explicitIntent = new Intent(implicitIntent); // Set the component to be explicit explicitIntent.setComponent(component); return explicitIntent; }
然后绑定服务的时候:
final Intent intent = new Intent(); intent.setAction("com.example.user.firstapp.FIRST_SERVICE"); final Intent eintent = new Intent(createExplicitFromImplicitIntent(this,intent)); bindService(eintent,conn, Service.BIND_AUTO_CREATE);
分析了下,其实不用那么麻烦三行代码就搞定了:
方法二:
Intent intent = new Intent("android.intent.action.remote"); ComponentName componentName =new ComponentName("com.example.user.myservicedemo" , "com.example.user.myservicedemo.RemoteService"); intent.setComponent(componentName);
这里需要注意的是componentName的构造函数的2个参数一个是包名、一个是类名。必须是全路径的。