内容简介:在此之前希望你已经阅读了:在此之前希望你已经阅读了:1).Service的简单
至美不过回首,往事重重,顾来时,荆棘漫漫,血浸途中。 今铜衣铁靴,再行来路,任荆棘漫漫,唯落绿叶残枝。 ----张风捷特烈 复制代码
零、前言
在此之前希望你已经阅读了: Android点将台:颜值担当[-Activity-]
在此之前希望你已经阅读了: Android点将台:外交官[-Intent-]
1.本文的知识点
1).Service的简单 介绍及使用
2).Service的 绑定服务
实现 音乐播放器(条)
3).使用 aidl
实现其他app访问该Service,播放音乐
2.Service总览
类名:Service 父类:ContextWrapper 修饰:public abstract 实现的接口:[ComponentCallbacks2] 包名:android.app 依赖类个数:16 内部类/接口个数:0 源码行数:790 源码行数(除注释):171 属性个数:3 方法个数:21 public方法个数:20 复制代码
一、Service初步认识
1.简述
Service和Activity同属一家,一暗一明,Android作为颜值担当,Service做后台工作(如图)
他不见天日,却要忠诚地执行任务,Service这个类的本身非常小,裸码171行
是什么让它成为"新手的噩梦",一个单词: Binder
,曾经让多少人闻风丧胆的 首席杀手
2.Service的开启与关闭
2.1:Service测试类
/** * 作者:张风捷特烈<br></br> * 时间:2019/1/17/017:21:30<br></br> * 邮箱:1981462002@qq.com<br></br> * 说明:Service测试 */ class MusicService : Service() { /** * 绑定Service * @param intent 意图 * @return IBinder对象 */ override fun onBind(intent: Intent): IBinder? { Log.e(TAG, "onBind: ") return null } /** * 创建Service */ override fun onCreate() { super.onCreate() Log.e(TAG, "onCreate: ") } /** * 开始执行命令 * @param intent 意图 * @param flags 启动命令的额外数据 * @param startId id * @return */ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { Log.e(TAG, "onStartCommand: ") Toast.makeText(this, "onStartCommand", Toast.LENGTH_SHORT).show() return super.onStartCommand(intent, flags, startId) } /** * 解绑服务 * @param intent 意图 * @return */ override fun onUnbind(intent: Intent): Boolean { Log.e(TAG, "onUnbind: 成功解绑") return super.onUnbind(intent) } /** * 销毁服务 */ override fun onDestroy() { super.onDestroy() Log.e(TAG, "onDestroy: 销毁服务") } companion object { private val TAG = "MusicService" } } 复制代码
2.2:ToastSActivity测试类
就两个按钮,点一下
//开启服务 id_btn_start.setOnClickListener { toastIntent = Intent(this, MusicService::class.java) startService(toastIntent) } //销毁服务 id_btn_kill.setOnClickListener { stopService(toastIntent) } 复制代码
2.3:测试类结果
点一下开启会执行 onCreate
和 onStartCommand
方法
多次点击开启, onCreate
只会执行一次, onStartCommand
方法每次都会执行
点击开启与销毁
3.Activity与Service的数据传递
onStartCommand中有Intent,和BroadcastReciver的套路有点像
---->[ToastSActivity#onCreate]---------------------- id_btn_start.setOnClickListener { toastIntent = Intent(this, MusicService::class.java) toastIntent?.putExtra("toast_data", id_et_msg.text.toString()) startService(toastIntent) } ---->[MusicService#onStartCommand]---------------------- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int Log.e(TAG, "onStartCommand: ") val data = intent.getStringExtra("toast_data") //data?:"NO MSG"表示如果data是空,就取"NO MSG" Toast.makeText(this, data?:"NO MSG", Toast.LENGTH_SHORT).show() return super.onStartCommand(intent, flags, startId) } 复制代码
4.在另一个App中使用其他app的Service
创建另一个App,进行测试 Activity
、 BroadcastReciver
、 Service
是四大组件的三棵顶梁柱
Intent可以根据组件包名及类名开启组件, Activity
、 BroadcastReciver
可以, Service
自然也可以,
局限性:
1.需要添加android:exported="true",否则会崩 <service android:name=".service.service.ToastService" android:exported="true"/> 2.大概一分钟后会自动销毁,自动销毁后再用就会崩...所以约等于无用 复制代码
4.关于隐式调用Service
Android5.0+ 明确指出不能隐式调用:ContextImpl的 validateServiceIntent
方法中
---->[ContextImpl#validateServiceIntent]--------------------------- private void validateServiceIntent(Intent service) { //包名、类名为空,即隐式调用,跑异常 if (service.getComponent() == null && service.getPackage() == null) { //从LOLLIPOP(即5.0开始) if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { IllegalArgumentException ex = new IllegalArgumentException( "Service Intent must be explicit: " + service); throw ex; } else { Log.w(TAG, "Implicit intents with startService are not safe: " + service + " " + Debug.getCallers(2, 3)); } } } 复制代码
二、绑定服务
前面的都是组件的日常,接下来才是Service的要点
为了不让本文看起来太low,写个布局吧(效果摆出来了,可以仿着做。不嫌丑的话用button也可以)
1.实现的效果
为了方便管理,这里写了一个IPlayer接口规定一下MusicPlayer的几个主要方法
暂时都是无返回值,无入参的方法,以后有需要再逐步完善
2.播放接口
/** * 作者:张风捷特烈<br></br> * 时间:2018/10/31 0031:23:32<br></br> * 邮箱:1981462002@qq.com<br></br> * 说明:播放接口 */ interface IPlayer { fun create()// 诞生 fun start()// 开始 fun resume()// 复苏 fun stop()// 停止 fun pause()// 暂停 fun release()//死亡 } 复制代码
3.播放的核心类
/** * 作者:张风捷特烈<br></br> * 时间:2019/1/17/017:21:57<br></br> * 邮箱:1981462002@qq.com<br></br> * 说明:播放核心类 */ class MusicPlayer(private val mContext: Context) : Binder(), IPlayer { override fun create() { Toast.makeText(mContext, "诞生", Toast.LENGTH_SHORT).show() } override fun start() { Toast.makeText(mContext, "开始播放", Toast.LENGTH_SHORT).show() } override fun resume() { Toast.makeText(mContext, "恢复播放", Toast.LENGTH_SHORT).show() } override fun stop() { Toast.makeText(mContext, "停止播放", Toast.LENGTH_SHORT).show() } override fun pause() { Toast.makeText(mContext, "暂停播放", Toast.LENGTH_SHORT).show() } override fun release() { Toast.makeText(mContext, "销毁", Toast.LENGTH_SHORT).show() } } 复制代码
4.播放的服务
/** * 作者:张风捷特烈<br></br> * 时间:2019/1/17/017:21:30<br></br> * 邮箱:1981462002@qq.com<br></br> * 说明:播放Service测试 */ class MusicService : Service() { override fun onBind(intent: Intent): IBinder? { Log.e(TAG, "onBind: ") Toast.makeText(this, "Bind OK", Toast.LENGTH_SHORT).show() return MusicPlayer(this) } override fun onCreate() { super.onCreate() Log.e(TAG, "onCreate: ") } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { Log.e(TAG, "onStartCommand: ") return super.onStartCommand(intent, flags, startId) } override fun onUnbind(intent: Intent): Boolean { Toast.makeText(this, "onUnbind: 成功解绑", Toast.LENGTH_SHORT).show() Log.e(TAG, "onUnbind: 成功解绑") return super.onUnbind(intent) } override fun onDestroy() { super.onDestroy() Log.e(TAG, "onDestroy: 销毁服务") } companion object { private val TAG = "MusicService" } } 复制代码
5.Activity中的使用
/** * 绑定服务 */ private fun bindMusicService() { musicIntent = Intent(this, MusicService::class.java) mConn = object : ServiceConnection { // 当连接成功时候调用 override fun onServiceConnected(name: ComponentName, service: IBinder) { mMusicPlayer = service as MusicPlayer } // 当连接断开时候调用 override fun onServiceDisconnected(name: ComponentName) { } } //[2]绑定服务启动 bindService(musicIntent, mConn, BIND_AUTO_CREATE); } 复制代码
三、音乐播放条的简单实现
接下来实现一个播放条,麻雀虽小,五脏俱全,完善了一下UI,如下
1.歌曲准备和修改接口
这里为了简洁些,直接用四个路径,判断存在什么的自己完善(非本文重点)
关于 MediaPlayer的相关知识详见这篇 ,这里就直接上代码了
在create时传入播放的列表路径字符串
/** * 作者:张风捷特烈<br></br> * 时间:2018/10/31 0031:23:32<br></br> * 邮箱:1981462002@qq.com<br></br> * 说明:播放接口 */ interface IPlayer { fun create(musicList: ArrayList<String>)// 诞生 fun start()// 开始 fun stop()// 停止 fun pause()// 暂停 fun release()//死亡 fun next()//下一曲 fun prev()//上一曲 fun isPlaying(): Boolean 是否播放 fun seek(pre_100: Int)//拖动进度 } 复制代码
2.create方法和start方法的实现
MusicActivity中通过 ServiceConnection
的 onServiceConnected方法
回调 IBinder
对象
将 MusicPlayer
对象传入MusicActivity中,对应的UI点击调用对应的方法即可
---->[MusicPlayer]-------------- private lateinit var mPlayer: MediaPlayer private var isInitialized = false//是否已初始化 private var mCurrentPos = 0//当前播放第几个音乐 private lateinit var mMusicList: ArrayList<String>//当前播放第几个音乐 ---->[MusicPlayer#create]-------------- override fun create(musicList: ArrayList<String>) { mMusicList = musicList val file = File(musicList[mCurrentPos]) val uri = Uri.fromFile(file) mPlayer = MediaPlayer.create(mContext, uri) isInitialized = true Log.e(TAG, "诞生") } ---->[MusicPlayer#start]-------------- override fun start() { if (!isInitialized && mPlayer.isPlaying) { return } mPlayer.start(); Log.e(TAG, "开始播放") } 复制代码
这样歌曲就能播放了
3.上一曲和下一曲的实现及自动播放下一曲
---->[MusicPlayer]-------------- override fun next() { mCurrentPos++ judgePos()//如果越界则置0 changMusicByPos(mCurrentPos) } override fun prev() { mCurrentPos-- judgePos()//如果越界则置0 changMusicByPos(mCurrentPos) } /** * 越界处理 */ private fun judgePos() { if (mCurrentPos >= mMusicList.size) { mCurrentPos = 0 } if (mCurrentPos < 0) { mCurrentPos = mMusicList.size - 1 } } /** * 根据位置切歌 * @param pos 当前歌曲id */ private fun changMusicByPos(pos: Int) { mPlayer.reset()//重置 mPlayer.setDataSource(mMusicList[pos])//设置当前歌曲 mPlayer.prepare()//准备 start() Log.e(TAG, "当前播放歌曲pos:$pos:,路径:${mMusicList[pos]}" ) } ---->[MusicPlayer#create]-------------- mPlayer.setOnCompletionListener { next()//播放完成,进入下一曲 } 复制代码
4.进度拖拽和监听处理
这里每隔一秒更新一下进度,通过Timer实现,当然实现方式有很多
---->[MusicPlayer]-------------- override fun seek(pre_100: Int) { pause() mPlayer.seekTo((pre_100 * mPlayer.duration / 100)) start() } ---->[MusicPlayer#create]-------------- mTimer = Timer()//创建Timer mHandler = Handler()//创建Handler mTimer.schedule(timerTask { if (isPlaying()) { val pos = mPlayer.currentPosition; val duration = mPlayer.duration; mHandler.post { if (mOnSeekListener != null) { mOnSeekListener.onSeek((pos.toFloat() / duration * 100).toInt()); } } } }, 0, 1000) //------------设置进度监听----------- interface OnSeekListener { fun onSeek(per_100: Int); } private lateinit var mOnSeekListener: OnSeekListener fun setOnSeekListener(onSeekListener: OnSeekListener) { mOnSeekListener = onSeekListener; } 复制代码
5.绑定服务的意义何在?
估计很多新手都有一个疑问,我直接在Activity中new 一个MediaPlayer多好
为什么非要通过Service来绕一圈得到MediaPlayer对象呢?
比如:一台服务器S上运行着一个游戏业务,一个客户端C连接到服务器便能够玩游戏 没有人会想把服务器上的业务移植到客户端,如果这样就真的一人一区了 Service相当于提供服务,此时Activity相当于客户端,通过conn连接服务 MediaPlayer(Binder对象)相当于核心业务,通过绑定获取服务,是典型的client-server模式 client-server模式的特点是一个Service可以为多个客户端服务 client可以通过IBinder接口获取服务业务的实例这里是MediaPlayer(Binder对象) 从而实现在client端直接调用服务业务(MediaPlayer)中的方法以实现灵活交互 但是现在只能在一个app里玩,如何让其他app也可以连接服务,这就要说到aidl了 还有很重要的一点:Service存活力强,记得上次在Activity中new MediaPlayer 来播放音乐 切切应用一会就停了。今天在Service里,玩了半天音乐也没停 复制代码
四、安卓接口定义语言 aidl
在Service中的使用
这个服务端有点弱,现在想办法让外部也能用它
不知道下图你里看出了什么,我看的挺兴奋,前几天看framework源码,感觉挺相似
你可以看一下 ActivityManagerNative
的源码和这里AS自动生成的,你会有所感触
1.aidl文件的书写
还记得上面的IPlayer的接口吧,aidl内容就是这个接口的方法
只不过书写的语法稍稍不同,下面是IMusicPlayerService的aidl
写完后记得点小锤子,他会使用 sdk\build-tools\28.0.3\aidl.exe
生成代码
// IMusicPlayerService.aidl package com.toly1994.tolyservice; // Declare any non-default types here with import statements interface IMusicPlayerService { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ void stop(); void pause(); void start(); void prev(); void next(); void release(); boolean isPlaying(); void seek(int pre_100); //加in void create(in List<String> filePaths); } 复制代码
2.自动生成的代码使用
IMusicPlayerService
刚才我们是自定义
MusicPlayer
继承
Binder
并实现
IPlayer
现在有个现成的IMusicPlayerService.Stub,我们继承它就行了,为避免看起来乱
新建了一个 MusicPlayerService
和 MusicPlayerStub
,可以上面的方式图对比一下
---->[IMusicPlayerService$Stub]------------ public interface IMusicPlayerService extends android.os.IInterface{ /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.toly1994.tolyservice.IMusicPlayerService 复制代码
3.MusicPlayerStub的实现(Binder对象)
实现上和上面的 MusicPlayer
一模一样,这里用 java 实现
/** * 作者:张风捷特烈<br/> * 时间:2019/1/23/023:17:11<br/> * 邮箱:1981462002@qq.com<br/> * 说明:MusicPlayerStub--Binder对象 */ public class MusicPlayerStub extends IMusicPlayerService.Stub { private MediaPlayer mPlayer; private boolean isInitialized = false;//是否已初始化 private int mCurrentPos = 0;//当前播放第几个音乐 private List<String> mMusicList;//音乐列表 private Context mContext; private Timer mTimer; private Handler mHandler; public MusicPlayerStub(Context mContext) { this.mContext = mContext; } @Override public void create(List<String> filePaths) throws RemoteException { mMusicList = filePaths; File file = new File(mMusicList.get(mCurrentPos)); Uri uri = Uri.fromFile(file); mPlayer = MediaPlayer.create(mContext, uri); isInitialized = true; //构造函数中 mTimer = new Timer();//创建Timer mHandler = new Handler();//创建Handler //开始方法中 mTimer.schedule(new TimerTask() { @Override public void run() { if (mPlayer.isPlaying()) { int pos = mPlayer.getCurrentPosition(); int duration = mPlayer.getDuration(); mHandler.post(() -> { if (mOnSeekListener != null) { mOnSeekListener.onSeek((int) (pos * 1.f / duration * 100)); } }); } } }, 0, 1000); mPlayer.setOnCompletionListener(mp -> { try { next();//播放完成,进入下一曲 } catch (RemoteException e) { e.printStackTrace(); } }); } @Override public void start() throws RemoteException { if (!isInitialized && mPlayer.isPlaying()) { return; } mPlayer.start(); } @Override public void stop() throws RemoteException { } @Override public void pause() throws RemoteException { if (mPlayer.isPlaying()) { mPlayer.pause(); } } @Override public void prev() throws RemoteException { mCurrentPos--; judgePos();//如果越界则置0 changMusicByPos(mCurrentPos); } @Override public void next() throws RemoteException { mCurrentPos++; judgePos();//如果越界则置0 changMusicByPos(mCurrentPos); } @Override public void release() throws RemoteException { } @Override public boolean isPlaying() throws RemoteException { return mPlayer.isPlaying(); } @Override public void seek(int pre_100) throws RemoteException { pause(); mPlayer.seekTo((pre_100 * mPlayer.getDuration() / 100)); start(); } /** * 越界处理 */ private void judgePos() { if (mCurrentPos >= mMusicList.size()) { mCurrentPos = 0; } if (mCurrentPos < 0) { mCurrentPos = mMusicList.size() - 1; } } /** * 根据位置切歌 * * @param pos 当前歌曲id */ private void changMusicByPos(int pos) { mPlayer.reset();//重置 try { mPlayer.setDataSource(mMusicList.get(pos));//设置当前歌曲 mPlayer.prepare();//准备 start(); } catch (IOException | RemoteException e) { e.printStackTrace(); } } //------------设置进度监听----------- public interface OnSeekListener { void onSeek(int per_100); } private OnSeekListener mOnSeekListener; public void setOnSeekListener(OnSeekListener onSeekListener) { mOnSeekListener = onSeekListener; } } 复制代码
4. MusicPlayerService
中返回MusicPlayerStub对象
一般都把MusicPlayerStub作为MusicPlayerService的一个内部类
本质没有区别,为了和上面对应,看起来舒服些,我把MusicPlayerStub提到了外面
/** * 作者:张风捷特烈<br/> * 时间:2019/1/23/023:16:32<br/> * 邮箱:1981462002@qq.com<br/> * 说明:音乐播放服务idal版 */ public class MusicPlayerService extends Service { private MusicPlayerStub musicPlayerStub; @Override public void onCreate() { super.onCreate(); ArrayList<String> musicList = new ArrayList<>(); musicList.add("/sdcard/toly/此生不换_青鸟飞鱼.aac"); musicList.add("/sdcard/toly/勇气-梁静茹-1772728608-1.mp3"); musicList.add("/sdcard/toly/草戒指_魏新雨.aac"); musicList.add("/sdcard/toly/郭静 - 下一个天亮 [mqms2].flac"); musicPlayerStub = new MusicPlayerStub(this); try { musicPlayerStub.create(musicList); } catch (RemoteException e) { e.printStackTrace(); } } @Nullable @Override public IBinder onBind(Intent intent) { return musicPlayerStub; } } 复制代码
5.在本项目中的使用
如果只在本项目中用,将两个类换下名字就行了和刚才没本质区别
/** * 绑定服务 */ private fun bindMusicService() { musicIntent = Intent(this, MusicPlayerService::class.java) mConn = object : ServiceConnection { // 当连接成功时候调用 override fun onServiceConnected(name: ComponentName, service: IBinder) { mMusicPlayer = service as MusicPlayerStub mMusicPlayer.setOnSeekListener { per_100 -> id_pv_pre.setProgress(per_100) } } // 当连接断开时候调用 override fun onServiceDisconnected(name: ComponentName) { } } //[2]绑定服务启动 bindService(musicIntent, mConn, BIND_AUTO_CREATE); } 复制代码
话说回来,搞了一大圈,aidl的优势在哪里?现在貌似还没看出来哪里厉害,接着看
在此之前先配置一下服务 app/src/main/AndroidManifest.xml
<service android:name=".service.service.MusicPlayerService"> <intent-filter> <action android:name="www.toly1994.com.music.player"></action> </intent-filter> </service> 复制代码
五、基于 aidl
在另一个项目中使用别的项目Service
这就是aidl的牛掰的地方,跨进程间通信,以及Android的系统级Service都基于此
下面进入另一个app里: anotherapp
,核心点就是获取IMusicPlayerService对象
注意一点: 常识问题,在客户端连接服务端时,服务端要先打开...
class ServiceTestActivity : AppCompatActivity() { private var mConn: ServiceConnection? = null private lateinit var mMusicPlayer: IMusicPlayerService override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.ac_br) title="另一个App" bindMusicService() id_btn_send.text="播放音乐" id_btn_send.setOnClickListener { mMusicPlayer.start() } } /** * 绑定服务 */ private fun bindMusicService() { val intent = Intent() //坑点:5.0以后要加 服务包名,不然报错 intent.setPackage("com.toly1994.tolyservice") intent.action = "www.toly1994.com.music.player" mConn = object : ServiceConnection { // 当连接成功时候调用 override fun onServiceConnected(name: ComponentName, service: IBinder) { //核心点获取IMusicPlayerService对象 mMusicPlayer = IMusicPlayerService.Stub.asInterface(service) } // 当连接断开时候调用 override fun onServiceDisconnected(name: ComponentName) { } } //[2]绑定服务启动 bindService(intent, mConn, BIND_AUTO_CREATE); } } 复制代码
当点击时音乐响起,一切就通了,如果你了解client-server模式,你应该明白这有多重要
framework的众多service就是这个原理,所以不明白aidl,framework的代码看起来会很吃力
下一篇将会结合framework,详细讨论aidl以及Binder的机制的第一层。
后记:捷文规范
1.本文成长记录及勘误表
项目源码 | 日期 | 附录 |
---|---|---|
V0.1--无 | 2018-1-23 | 无 |
发布名: Android点将台:绝命暗杀官[-Service-]
捷文链接: juejin.im/post/5c4a7e…
2.更多关于我
笔名 | 微信 | |
---|---|---|
张风捷特烈 | 1981462002 | zdl1994328 |
我的github: github.com/toly1994328
我的简书: www.jianshu.com/u/e4e52c116…
我的简书: www.jianshu.com/u/e4e52c116…
个人网站:www.toly1994.com
3.声明
1----本文由张风捷特烈原创,转载请注明
2----欢迎广大编程爱好者共同交流
3----个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
4----看到这里,我在此感谢你的喜欢与支持
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Android点将台:烽火狼烟[-Handler-]
- Android点将台:济世儒侠[-ContentProvider-]
- Android点将台:颜值担当[-Activity-]
- Android点将台:金科玉律[-AIDL-]
- Android点将台:传令官[-BroadcastReciver-](使用级)
- Android 点将台:撒豆成兵[- Fragment -]
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。