学习android也有几个月了,到现在四大组件也只用过activity,实在惭愧,现在开始学习一下Service的使用。网上已经有很多很好地教程了,我这里整理一下,算是对自己上阶段学习的总结。
Service
我们都用过activity,activity是运行在前台界面的,有自己的生命周期,而service是运行在后台的代码,它没有界面,不过也有自己的生命周期。启动activity有两种方式,一种是调用startService(),另一种是bindService()。从下面的图可以看出来两种启动的方式是不一样的。
1.用startService()启动,首先会调用onCreate()方法,然后是onStartCommend(),这样service就启动了,你可以在onStartComment()里执行需要执行的代码。用这种方法启动service后这个service就会一直运行下去,即使启动这个service的组件被销毁了也对它没什么影响,直到调用stopService()方法或者stopSelf()才会被销毁。
2.用bindService()启动,也是先调用onCreate()方法,然后是onBind(),onBind()这个方法刚创建service的时候就会重写,使用的startService()的时候没有调用这个方法,可以return null.这样服务就与创建它的组件绑定了,一个service可以和多个组件绑定,所有跟它绑定的组件都被销毁,组件就会被销毁。
使用service也和activity一样要现在AndroidManifest里面先注册。
<service android:name="lee.org.servicedemo.MyService" > </service>
创建Service类
public class MyService2 extends Service { @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { super.onDestroy(); } }
然后就可以在里面实现逻辑了。
音乐播放器是最常见的例子,比如酷狗播放器,播放音乐后及时程序退到后台甚至关掉程序,还是可以继续播放音乐。
下面我们就来实现一个非常简单的可以在后台播放音乐的demo。
界面布局非常简单,就是放几个按钮,我这里就不贴代码了。
public class MainActivity extends Activity implements View.OnClickListener { private final static String TAG = "MainActivity"; // private ImageButton btn1;上一首 private ImageButton btn2;//播放 // private ImageButton btn3;//下一首 private MediaPlayer player;//用于播放音乐 // private static final String path = "/kgmusic/download/sky.mp3"; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { MyService.MyBind myBind = (MyService.MyBind)service;//用于activity和绑定的service交互 myBind.play(); } @Override public void onServiceDisconnected(ComponentName name) { Log.v(TAG,"解除绑定"); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //用于显示右上角的溢出菜单 默认是不显示的 try { ViewConfiguration mconfig = ViewConfiguration.get(this); Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); if(menuKeyField != null) { menuKeyField.setAccessible(true); menuKeyField.setBoolean(mconfig, false); } } catch (Exception ex) { } init(); } //初始化控件 private void init() { /* String filePath = Environment.getExternalStorageDirectory().getPath() + path;//这里我就放了一首歌 把地址写死了 Log.v(TAG, filePath); player = new MediaPlayer(); player.setLooping(true);//设置单曲循环 try { player.setDataSource(filePath); player.prepare();//准备 不调用这个方法下面调用play()的时候会出错 } catch (IOException e) { e.printStackTrace(); } */ //btn1 = (ImageButton) findViewById(R.id.btn_previous); btn2 = (ImageButton) findViewById(R.id.btn_play); //btn3 = (ImageButton) findViewById(R.id.btn_next); //btn1.setOnClickListener(this); btn2.setOnClickListener(this); //btn3.setOnClickListener(this); } @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,MyService.class); switch(v.getId()){ case R.id.btn_play: play(intent);//用service启动 //play(); break; case R.id.btn_previous: previous(); break; case R.id.btn_next: next(); break; } } void play(Intent intent){ startService(intent); } //play()方法我们放在service中实现 如果在这里实现,我们退出之后就不能继续播放音乐。 /* void play(){ if(player.isPlaying()){ btn2.setImageResource(R.drawable.btn_play_style); pause(); } else { Log.v(TAG, "start"); player.start(); btn2.setImageResource(R.drawable.btn_pause); } } void pause(){ Log.v(TAG,"pause"); player.pause(); } void next(){ Log.v(TAG,"next"); } void previous(){ Log.v(TAG,"previous"); } */ @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main,menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()){ case R.id.item1: Intent intent = new Intent(this,MainActivity2.class); startActivity(intent); finish(); break; case R.id.item2: Intent intent2 = new Intent(this,MyService.class); stopService(intent2);//停止服务 break; case R.id.item3: Intent intent3 = new Intent(this,MyService.class); bindService(intent3,conn,BIND_AUTO_CREATE);//用bindservice方式启动service break; case R.id.item4: unbindService(conn);//解除绑定 break; } return true; } }
Service实现
public class MyService extends Service{ private final static String TAG = "MyService"; private MediaPlayer player;//用于播放音乐 private static final String path = "/kgmusic/download/sky.mp3"; private MyBind myBind = new MyBind(); @Override public IBinder onBind(Intent intent) { Log.v(TAG,"bind"); return myBind; } @Override public void onCreate() { super.onCreate(); Log.v(TAG,"onCreate"); String filePath = Environment.getExternalStorageDirectory().getPath() + path; Log.v(TAG, filePath); player = new MediaPlayer(); player.setLooping(true);//设置单曲循环 try { player.setDataSource(filePath); player.prepare(); //准备 不调用这个方法下面调用play()的时候会出错 } catch (IOException e) { e.printStackTrace(); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.v(TAG,"onStartCommand"); play(); return super.onStartCommand(intent, flags, startId); } void play(){ if(player.isPlaying()){ pause(); } else { player.start(); } } void pause(){ Log.v(TAG,"pause"); player.pause(); } @Override public void onDestroy() { player.stop(); Log.v(TAG,"Destroy"); } class MyBind extends Binder{ public void play(){ if(player.isPlaying()){ pause(); } else { player.start(); } } } }
这样一个非常简单的能在后台播放音乐的音乐播放器就完成了。当我们按home键退出的时候,音乐还是会继续播放,甚至我们把程序退出,音乐还是会继续播放。
但是,这样也会出现一些问题,在后台播放音乐的时候经常会自己停掉,过段时间又自己响起来。这是怎么回事呢?因为我测试用的手机比较差,内存很小,多开几个应用内存就不够用了,android系统会在系统内存不足的时候把那些优先级低的进程杀掉,在设置里面查看运行情况,发现内存不足的时候这个service的资源就会被释放掉,过段时间又会重启,所以造成音乐播放的断断续续,但是我手机上也装了酷狗音乐,但它就不会出现这种情况,凭什么啊?大家都是在后台运行的service,难道它运气就好点,每次杀进程的时候都杀掉别人?后来在万能的Google的帮助下,找到了一种可能性。酷狗音乐一打开的时候就会在通知栏显示一个通知。
难道在通知栏显示就不会被杀掉了???
下面就先来了解一下通知notification的基本情况吧。
Notification支持文字内容显示、震动、三色灯、铃声等多种提示形式,在默认情况下,Notification仅显示消息标题、消息内容、送达时间这3项内容。
关于notification的一些基本情况下面那两篇博客讲的非常详细,我也就不班门弄斧 了。
http://blog.csdn.net/vipzjyno1/article/details/25248021
http://blog.csdn.net/vipzjyno1/article/details/25248021
有些地方要注意一下,网上很多代码都是用了
Notification notification = new Notification(R.drawable.ic_launcher, "有通知到来", System.currentTimeMillis()); notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容",pendingIntent);
这样错是没错,不过这些方法都已经被弃用了,现在Google推荐的是使用builder来代替,高版本的可以直接用
Notification.Builder builder = new Notification.Builder(this);
低版本的在引入v4包后可以使用
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
下面我们就来创建一个简单的notification吧。
NotificationCompat.Builder builder = new NotificationCompat.Builder(this); builder.setSmallIcon(R.drawable.ic_launcher);//设置图标 builder.setContentText("contentText");//设置内容文本 builder.setContentTitle("title");//设置标题文本 builder.setWhen(System.currentTimeMillis()); builder.setTicker("you get a new ticker");//设置消息来的时候提示的文本 builder.setAutoCancel(true); builder.setOngoing(true);//设置为true的时候在通知栏上不能通过滑动来使其消失,也就是说会一直停在通知栏上,金山那个通知大概就是这样做的 builder.setDefaults(Notification.DEFAULT_ALL);//通知的类型 有铃声 震动 和三色光,震动和灯光使用的时候要先添加permission Intent notificationIntent = new Intent(this, MainActivity2.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0); builder.setFullScreenIntent(pendingIntent,true);//全屏通知 NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); manager.notify(1,builder.build());//启动通知 startForeground(1, builder.build());//置为前台
这样一个简单的通知栏就搞定了,这里只是列举部分属性,更多的属性可以参考以上两篇博客。
然后把这段代码加到刚才的service的代码中,这样进程被杀掉的可能性就大大降低, 至少我还没有碰到过资源被释放的情况。
为什么加个通知栏就行了呢?关键在最后一段代码,startForeground() 方法让service运行在前台,提高了优先级,系统会优先释放掉后台运行的进程来保持前台的进程,这样就能极大避免了进程资源被释放掉。不过这样似乎还不能完全避免service被杀掉,不过一般情况下是不会被销毁的。
好了,差不多就这样了,有什么讲的不对的地方欢迎大家批评指正,也欢迎大家跟我交流。