Android点将台:绝命暗杀官[

编程入门 行业动态 更新时间:2024-10-11 09:22:24

Android<a href=https://www.elefans.com/category/jswz/34/1748592.html style=点将台:绝命暗杀官["/>

Android点将台:绝命暗杀官[

个人所有文章整理在此篇,将陆续更新收录:知无涯,行者之路莫言终(我的编程之路)


零、前言
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<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:测试类结果

点一下开启会执行onCreateonStartCommand方法

多次点击开启,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): IntLog.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,进行测试 ActivityBroadcastReciverService是四大组件的三棵顶梁柱
Intent可以根据组件包名及类名开启组件,ActivityBroadcastReciver可以,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<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<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<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<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中通过ServiceConnectiononServiceConnected方法回调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 = musicListval file = File(musicList[mCurrentPos])val uri = Uri.fromFile(file)mPlayer = MediaPlayer.create(mContext, uri)isInitialized = trueLog.e(TAG, "诞生")
}---->[MusicPlayer#start]--------------
override fun start() {if (!isInitialized && mPlayer.isPlaying) {return}mPlayer.start();Log.e(TAG, "开始播放")
}
复制代码

这样歌曲就能播放了


3.上一曲和下一曲的实现及自动播放下一曲
---->[MusicPlayer]--------------override fun next() {mCurrentPos++judgePos()//如果越界则置0changMusicByPos(mCurrentPos)
}override fun prev() {mCurrentPos--judgePos()//如果越界则置0changMusicByPos(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 statementsinterface 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);//加invoid create(in List<String> filePaths);
}
复制代码

2.自动生成的代码使用

本文只是说一下生成的IMusicPlayerService如何使用,下一篇将详细分析它 可以看出IMusicPlayerService中有一个内部类Stub继承自Binder还实现了IMusicPlayerService
刚才我们是自定义MusicPlayer继承Binder并实现IPlayer
现在有个现成的IMusicPlayerService.Stub,我们继承它就行了,为避免看起来乱
新建了一个MusicPlayerServiceMusicPlayerStub,可以上面的方式图对比一下

---->[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<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;}@Overridepublic 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();//创建TimermHandler = new Handler();//创建Handler//开始方法中mTimer.schedule(new TimerTask() {@Overridepublic 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();}});}@Overridepublic void start() throws RemoteException {if (!isInitialized && mPlayer.isPlaying()) {return;}mPlayer.start();}@Overridepublic void stop() throws RemoteException {}@Overridepublic void pause() throws RemoteException {if (mPlayer.isPlaying()) {mPlayer.pause();}}@Overridepublic void prev() throws RemoteException {mCurrentPos--;judgePos();//如果越界则置0changMusicByPos(mCurrentPos);}@Overridepublic void next() throws RemoteException {mCurrentPos++;judgePos();//如果越界则置0changMusicByPos(mCurrentPos);}@Overridepublic void release() throws RemoteException {}@Overridepublic boolean isPlaying() throws RemoteException {return mPlayer.isPlaying();}@Overridepublic 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<br/>* 说明:音乐播放服务idal版*/
public class MusicPlayerService extends Service {private MusicPlayerStub musicPlayerStub;@Overridepublic 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@Overridepublic 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 MusicPlayerStubmMusicPlayer.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.music.player"></action></intent-filter>
</service>
复制代码

五、基于aidl在另一个项目中使用别的项目Service

这就是aidl的牛掰的地方,跨进程间通信,以及Android的系统级Service都基于此
下面进入另一个app里:anotherapp,核心点就是获取IMusicPlayerService对象
注意一点:常识问题,在客户端连接服务端时,服务端要先打开...

class ServiceTestActivity : AppCompatActivity() {private var mConn: ServiceConnection? = nullprivate lateinit var mMusicPlayer: IMusicPlayerServiceoverride 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.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.本文的知识点
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<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:测试类结果

点一下开启会执行onCreateonStartCommand方法

多次点击开启,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): IntLog.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,进行测试 ActivityBroadcastReciverService是四大组件的三棵顶梁柱
Intent可以根据组件包名及类名开启组件,ActivityBroadcastReciver可以,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<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<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<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<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中通过ServiceConnectiononServiceConnected方法回调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 = musicListval file = File(musicList[mCurrentPos])val uri = Uri.fromFile(file)mPlayer = MediaPlayer.create(mContext, uri)isInitialized = trueLog.e(TAG, "诞生")
}---->[MusicPlayer#start]--------------
override fun start() {if (!isInitialized && mPlayer.isPlaying) {return}mPlayer.start();Log.e(TAG, "开始播放")
}
复制代码

这样歌曲就能播放了


3.上一曲和下一曲的实现及自动播放下一曲
---->[MusicPlayer]--------------override fun next() {mCurrentPos++judgePos()//如果越界则置0changMusicByPos(mCurrentPos)
}override fun prev() {mCurrentPos--judgePos()//如果越界则置0changMusicByPos(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 statementsinterface 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);//加invoid create(in List<String> filePaths);
}
复制代码

2.自动生成的代码使用

本文只是说一下生成的IMusicPlayerService如何使用,下一篇将详细分析它 可以看出IMusicPlayerService中有一个内部类Stub继承自Binder还实现了IMusicPlayerService
刚才我们是自定义MusicPlayer继承Binder并实现IPlayer
现在有个现成的IMusicPlayerService.Stub,我们继承它就行了,为避免看起来乱
新建了一个MusicPlayerServiceMusicPlayerStub,可以上面的方式图对比一下

---->[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<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;}@Overridepublic 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();//创建TimermHandler = new Handler();//创建Handler//开始方法中mTimer.schedule(new TimerTask() {@Overridepublic 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();}});}@Overridepublic void start() throws RemoteException {if (!isInitialized && mPlayer.isPlaying()) {return;}mPlayer.start();}@Overridepublic void stop() throws RemoteException {}@Overridepublic void pause() throws RemoteException {if (mPlayer.isPlaying()) {mPlayer.pause();}}@Overridepublic void prev() throws RemoteException {mCurrentPos--;judgePos();//如果越界则置0changMusicByPos(mCurrentPos);}@Overridepublic void next() throws RemoteException {mCurrentPos++;judgePos();//如果越界则置0changMusicByPos(mCurrentPos);}@Overridepublic void release() throws RemoteException {}@Overridepublic boolean isPlaying() throws RemoteException {return mPlayer.isPlaying();}@Overridepublic 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<br/>* 说明:音乐播放服务idal版*/
public class MusicPlayerService extends Service {private MusicPlayerStub musicPlayerStub;@Overridepublic 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@Overridepublic 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 MusicPlayerStubmMusicPlayer.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.music.player"></action></intent-filter>
</service>
复制代码

五、基于aidl在另一个项目中使用别的项目Service

这就是aidl的牛掰的地方,跨进程间通信,以及Android的系统级Service都基于此
下面进入另一个app里:anotherapp,核心点就是获取IMusicPlayerService对象
注意一点:常识问题,在客户端连接服务端时,服务端要先打开...

class ServiceTestActivity : AppCompatActivity() {private var mConn: ServiceConnection? = nullprivate lateinit var mMusicPlayer: IMusicPlayerServiceoverride 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.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的机制的第一层。

更多推荐

Android点将台:绝命暗杀官[

本文发布于:2024-03-09 19:01:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1725825.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:点将台   Android

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!