首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android点将台:绝命暗杀官[-Service-]

Android点将台:绝命暗杀官[-Service-]

作者头像
张风捷特烈
发布于 2024-01-26 02:05:52
发布于 2024-01-26 02:05:52
27802
代码可运行
举报
运行总次数:2
代码可运行
零、前言
1.本文的知识点
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1).Service的简单`介绍及使用`   
2).Service的`绑定服务`实现`音乐播放器(条)`   
3).使用`aidl`实现其他app访问该Service,播放音乐

2.Service总览
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
类名: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测试类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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测试类

就两个按钮,点一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//开启服务
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的套路有点像

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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,进行测试 ActivityBroadcastReciverService是四大组件的三棵顶梁柱 Intent可以根据组件包名及类名开启组件,ActivityBroadcastReciver可以,Service自然也可以,


局限性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.需要添加android:exported="true",否则会崩
<service android:name=".service.service.ToastService" android:exported="true"/> 

2.大概一分钟后会自动销毁,自动销毁后再用就会崩...所以约等于无用

4.关于隐式调用Service

Android5.0+ 明确指出不能隐式调用:ContextImpl的validateServiceIntent方法中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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.播放接口
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.播放的核心类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.播放的服务
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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中的使用
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 绑定服务
 */
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时传入播放的列表路径字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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中通过ServiceConnectiononServiceConnected方法回调IBinder对象 将MusicPlayer对象传入MusicActivity中,对应的UI点击调用对应的方法即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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.上一曲和下一曲的实现及自动播放下一曲
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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实现,当然实现方式有很多

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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对象呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
比如:一台服务器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生成代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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如何使用,下一篇将详细分析它 可以看出IMusicPlayerService中有一个内部类Stub继承自Binder还实现了IMusicPlayerService 刚才我们是自定义MusicPlayer继承Binder并实现IPlayer 现在有个现成的IMusicPlayerService.Stub,我们继承它就行了,为避免看起来乱 新建了一个MusicPlayerServiceMusicPlayerStub,可以上面的方式图对比一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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提到了外面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.在本项目中的使用

如果只在本项目中用,将两个类换下名字就行了和刚才没本质区别

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 绑定服务
 */
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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对象 注意一点:常识问题,在客户端连接服务端时,服务端要先打开...

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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.本文的知识点
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1).Service的简单`介绍及使用`   
2).Service的`绑定服务`实现`音乐播放器(条)`   
3).使用`aidl`实现其他app访问该Service,播放音乐

2.Service总览
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
类名: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测试类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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测试类

就两个按钮,点一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//开启服务
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的套路有点像

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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,进行测试 ActivityBroadcastReciverService是四大组件的三棵顶梁柱 Intent可以根据组件包名及类名开启组件,ActivityBroadcastReciver可以,Service自然也可以,


局限性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1.需要添加android:exported="true",否则会崩
<service android:name=".service.service.ToastService" android:exported="true"/> 

2.大概一分钟后会自动销毁,自动销毁后再用就会崩...所以约等于无用

4.关于隐式调用Service

Android5.0+ 明确指出不能隐式调用:ContextImpl的validateServiceIntent方法中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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.播放接口
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.播放的核心类
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.播放的服务
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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中的使用
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 绑定服务
 */
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时传入播放的列表路径字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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中通过ServiceConnectiononServiceConnected方法回调IBinder对象 将MusicPlayer对象传入MusicActivity中,对应的UI点击调用对应的方法即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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.上一曲和下一曲的实现及自动播放下一曲
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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实现,当然实现方式有很多

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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对象呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
比如:一台服务器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生成代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 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如何使用,下一篇将详细分析它 可以看出IMusicPlayerService中有一个内部类Stub继承自Binder还实现了IMusicPlayerService 刚才我们是自定义MusicPlayer继承Binder并实现IPlayer 现在有个现成的IMusicPlayerService.Stub,我们继承它就行了,为避免看起来乱 新建了一个MusicPlayerServiceMusicPlayerStub,可以上面的方式图对比一下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
---->[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实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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提到了外面

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 作者:张风捷特烈<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.在本项目中的使用

如果只在本项目中用,将两个类换下名字就行了和刚才没本质区别

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 绑定服务
 */
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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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对象 注意一点:常识问题,在客户端连接服务端时,服务端要先打开...

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的机制的第一层。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
STM32与传感器技术结合打造智能行李箱 | 自动跟随与报警系统【免费开源】
完整项目已打包,开源免费:https://blog.csdn.net/weixin_52908342/article/details/150453749
一键难忘
2025/08/16
1300
4.9 51单片机-(HC-SR04)超声波测距模块
HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测距精度可达高到 3mm;模块包括超声波发射器、 接收器与控制电路。
DS小龙哥
2022/01/12
2.3K0
4.9 51单片机-(HC-SR04)超声波测距模块
基于STM32的HC-SR04超声波测距模块实验
超声波测距是借助于超声脉冲回波渡越时间法来实现的。设超声波脉冲由传感器发出到接收所经历的时间为t,超声波在空气中的传播速度为c,则从传感器到目标物体的距离D可用下式求出:D = ct/2,图 2是相应的系统框图。
用户8913398
2021/08/16
8K0
基于STM32的HC-SR04超声波测距模块实验
超声波测距灯
介绍 硬件准备 本篇文章专门介绍用Arduino制作超声波测距灯,需要的材料是:
跋扈洋
2021/02/02
8500
超声波测距灯
基于STM32和HC-SR04模块实现超声波测距功能
本文用的单片机是STM32F103C8T6,超声波测距模块是HC-SR04,显示测距结果用的是0.96寸OLED屏模块。
zeruns
2022/11/11
2.6K0
基于STM32和HC-SR04模块实现超声波测距功能
毕设_基于单片机的倒车雷达/超声波测距(HC-SR04+1602显示屏)
本课题以AT89C51单片机为核心设计一种超声波倒车雷达系统,同时兼顾车内温度测量。
蒋宇智
2024/04/10
4380
毕设_基于单片机的倒车雷达/超声波测距(HC-SR04+1602显示屏)
超声波测距模块
硬件介绍 1.使用场景 超声波测距模块在平时做电子产品、机器人、智能设备中的应用里还是非常常用的,使用非常简单,但是代码的编写和理解其实并不容易,在这里想和大家交流一下。有不同的意见和建议可以给我留言或者私信我。 2. 工作原理
跋扈洋
2021/01/28
1.2K0
Arduino 入门项目系列 (3) - 超声波距离检测警报器
这周主要学习的是蜂鸣器和超声波传感器的使用,超声波传感器在智能小车上会有很多的用处。后来结合了 LCD 的使用,搭建了简易的距离检测警报器。
caoqi95
2019/03/28
4K0
Arduino 入门项目系列 (3) - 超声波距离检测警报器
stm32f103+HC-SR04+ssd1306实现超声波测距
如果你经常做一些嵌入式设备,HC-SR04应该不陌生,一款便宜简单的超声波测距装置,可以应用在智能小车测距壁障,航模飞行器定高等。这篇文章简单讲解,通过一个示例来揉和。
秋名山码神
2023/11/07
5470
stm32f103+HC-SR04+ssd1306实现超声波测距
LabVIEW控制Arduino实现超声波测距(进阶篇—5)
超声波测距是一种传统而实用的非接触测量方法,与激光、涡流和无线电测距方法相比,具有不受外界光及电磁场等因素影响的优点,在比较恶劣的环境中也具有一定的适应能力,且结构简单、成本低,因此在工业控制、建筑测量、机器人定位方面有广泛的应用。
不脱发的程序猿
2022/06/12
1.8K0
LabVIEW控制Arduino实现超声波测距(进阶篇—5)
雷达测距和超声波测距_超声波测距的原理是什么
本实验是基于MSP430利用HC-SR04超声波传感器进行测距,测距范围是3-65cm,讲得到的数据显示在LCD 1602液晶屏上。
全栈程序员站长
2022/10/03
9020
雷达测距和超声波测距_超声波测距的原理是什么
C51 单片机开发认识超声波测距传感器
闲话:数学功底好的人,对于编程来说是真的好。高精尖的东西都涉及深厚的数学知识,算法的优化也涉及各种数学知识、……编译器对于除法的优化,数学不好都搞不明白,只能记个结论算啦!如果有大把的时间用来学习的程序员,比如还在学校当学生的准程序员,那么花时间研究数学是太值得了。
码农UP2U
2024/07/04
4060
C51 单片机开发认识超声波测距传感器
基于单片机设计的激光测距仪(采用XKC-Kl200模块)
随着科技的不断进步和应用需求的增加,测距仪成为了许多领域必备的工具之一。传统的测距仪价格昂贵、体积庞大,使用起来不够方便。本项目采用STC89C52单片机作为主控芯片,结合XKC-KL200激光测距模块和LCD1602显示器,实现了一个简易且高效的激光测距仪。这个测距仪可以帮助用户快速准确地测量目标与测距仪之间的距离,并将结果通过LCD1602显示器直观地展示出来。
DS小龙哥
2023/12/01
6700
基于单片机设计的激光测距仪(采用XKC-Kl200模块)
超声波传感器模块
超声波传感器有很多种类的型号:HC-SR04、UC-025、UC-026、UC-015、US-100等等,但是他们都大同小异。他们的主要区别是工作参数有点不一样,像是工作的电压或者温度,探测距离或精度有点差别。引脚是一样的,都是4个引脚(us-100多了一个GND引脚),引脚的工作和作用也是一样的。
破晓的历程
2025/05/14
2670
超声波传感器模块
七行代码实现一个超声波测距仪
shineblink core 开发板(简称Core)的库函数支持US-015超声波测距传感器,所以只需要调用两个API,即可实现超声波测距功能。
shineblink
2020/11/16
3830
基于STC89C51/2的的超声波测距(1602A显示)「建议收藏」
8. D0 ~ D7我接入的是P 0 ^ 0~P 0 ^ 7(注意不要接到VCC管脚上去),还有接入P0口要接上拉电阻,否则会烧坏的。
全栈程序员站长
2022/09/29
4740
基于STC89C51/2的的超声波测距(1602A显示)「建议收藏」
利用LCD1602显示超声波测距
介绍 一、需要的器件 1. 51单片机:任意一款都可以,我这里使用的是STC89C52 2. LCD1602显示屏:我这里使用的是LCD1602A不带转接板的八位显示屏,是比较正常的一款。 3. 超声波测距模块:根据价钱的不同有很多可供选择,我这里推荐使用HC-SR04,因为便宜。。。 4. 杜邦线若干:在这里使用母对母杜邦线。 二、原理
跋扈洋
2021/01/29
8310
树莓派基础实验24:超声波测距传感器实验
   超声波传感器使用超声波来准确检测物体并测量距离。他发出超声波并将它们转换成电信号,主要应用于汽车的倒车雷达、机器人自动避障行走、建筑施工工地以及一些工业现场。
张国平
2020/09/27
2.4K0
基于单片机设计的电子指南针(LSM303DLH模块(三轴磁场 + 三轴加速度)
本项目是基于单片机设计的电子指南针,主要利用STC89C52作为主控芯片和LSM303DLH模块作为指南针模块。通过LCD1602液晶显示屏来展示检测到的指南针信息。
DS小龙哥
2023/11/18
5950
基于单片机设计的电子指南针(LSM303DLH模块(三轴磁场 + 三轴加速度)
基于51单片机智能小车的设计与实现转弯避障_基于单片机的智能小车设计
学习智能小车系统,有助于提高搭建系统的能力和对自动控制技术的理解。智能小车是一个较为完整的智能化系统,而智能化的研究已成为我国追赶世界科技水平的重要任务。智能小车有它特有的特点:成本低,涉及的知识面广,易于拓展[1]。整个智能小车系统作为一个完整的系统,从它的原理图的实现到实物的完成的过程,不仅需要深厚的电子方面的知识,还有对电路实现的良好掌握,对于培养学生的实践能力都有重要的意义。智能小车的竞赛在我国各大高校中都受到了重视,吸引了大批的高校学生的兴趣,而且取得了很多优异的成果,为我国推进智能化的进程做出了巨大的贡献,也为智能汽车的发展提供了理论依据[2-3]。只有当把理论和模型应用到实践中,这样的创新才用意义,我们国家这几年在智能化方面的进步越来越快,也推动了我国在国际社会上在智能化方面的话语权。智能小车是智能化的一部分,它的系统里的避障、循迹、红外遥控的技术用到了智能化,将智能化应用到传统技术上是21世纪发展的趋势。我国虽然从改革开放以来大力发展科技创新,但是在智能化的创新水平与国外较发达的国家相比还有巨大的差距,智能竞赛在高校越来越流行,也证明了我国教育在这方面很快会赶上世界上的发展水平。本次设计是以单片机为CPU,通过编程和一些外围电路的设计来实现红外遥控,避障,循迹等功能。最重要的是把模型上的研究应用到实际生活中,智能车辆便做到了这一点[4-6]。在实际应用中比如在倒车的过程中实现的红外警报系统是以智能小车为模型而研发出来的。对于电子知识的热爱与钻研有利于研发更多智能车辆,使我们的生活更加便利、智能化。
全栈程序员站长
2022/11/02
2.5K0
推荐阅读
相关推荐
STM32与传感器技术结合打造智能行李箱 | 自动跟随与报警系统【免费开源】
更多 >
目录
  • 零、前言
    • 1.本文的知识点
    • 2.Service总览
  • 一、Service初步认识
    • 1.简述
    • 2.Service的开启与关闭
      • 2.1:Service测试类
      • 2.2:ToastSActivity测试类
      • 2.3:测试类结果
    • 3.Activity与Service的数据传递
    • 4.在另一个App中使用其他app的Service
    • 4.关于隐式调用Service
  • 二、绑定服务
    • 1.实现的效果
    • 2.播放接口
    • 3.播放的核心类
    • 4.播放的服务
    • 5.Activity中的使用
  • 三、音乐播放条的简单实现
    • 1.歌曲准备和修改接口
    • 2.create方法和start方法的实现
    • 3.上一曲和下一曲的实现及自动播放下一曲
    • 4.进度拖拽和监听处理
    • 5.绑定服务的意义何在?
  • 四、安卓接口定义语言aidl在Service中的使用
    • 1.aidl文件的书写
    • 2.自动生成的代码使用
    • 3.MusicPlayerStub的实现(Binder对象)
    • 4.MusicPlayerService中返回MusicPlayerStub对象
    • 5.在本项目中的使用
  • 五、基于aidl在另一个项目中使用别的项目Service
  • 零、前言
    • 1.本文的知识点
    • 2.Service总览
  • 一、Service初步认识
    • 1.简述
    • 2.Service的开启与关闭
      • 2.1:Service测试类
      • 2.2:ToastSActivity测试类
      • 2.3:测试类结果
    • 3.Activity与Service的数据传递
    • 4.在另一个App中使用其他app的Service
    • 4.关于隐式调用Service
  • 二、绑定服务
    • 1.实现的效果
    • 2.播放接口
    • 3.播放的核心类
    • 4.播放的服务
    • 5.Activity中的使用
  • 三、音乐播放条的简单实现
    • 1.歌曲准备和修改接口
    • 2.create方法和start方法的实现
    • 3.上一曲和下一曲的实现及自动播放下一曲
    • 4.进度拖拽和监听处理
    • 5.绑定服务的意义何在?
  • 四、安卓接口定义语言aidl在Service中的使用
    • 1.aidl文件的书写
    • 2.自动生成的代码使用
    • 3.MusicPlayerStub的实现(Binder对象)
    • 4.MusicPlayerService中返回MusicPlayerStub对象
    • 5.在本项目中的使用
  • 五、基于aidl在另一个项目中使用别的项目Service
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档