我的全部文章整理在此篇,將陸續更新收錄:知無涯,行者之路莫言終(個人編程之路)java
1).Service的簡單`介紹及使用`
2).Service的`綁定服務`實現`音樂播放器(條)`
3).使用`aidl`實現其餘app訪問該Service,播放音樂
複製代碼
類名:Service 父類:ContextWrapper 修飾:public abstract
實現的接口:[ComponentCallbacks2]
包名:android.app 依賴類個數:16
內部類/接口個數:0
源碼行數:790 源碼行數(除註釋):171
屬性個數:3 方法個數:21 public方法個數:20
複製代碼
Service和Activity同屬一家,一暗一明,Android做爲顏值擔當,Service作後臺工做(如圖)
他不見天日,卻要忠誠地執行任務,Service這個類的自己很是小,裸碼171行
是什麼讓它成爲"新手的噩夢",一個單詞:Binder
,曾經讓多少人聞風喪膽的首席殺手
android
/**
* 做者:張風捷特烈<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"
}
}
複製代碼
就兩個按鈕,點一下編程
//開啓服務
id_btn_start.setOnClickListener {
toastIntent = Intent(this, MusicService::class.java)
startService(toastIntent)
}
//銷燬服務
id_btn_kill.setOnClickListener {
stopService(toastIntent)
}
複製代碼
點一下開啓會執行
onCreate
和onStartCommand
方法bash
屢次點擊開啓,
onCreate
只會執行一次,onStartCommand
方法每次都會執行服務器
點擊開啓與銷燬app
onStartCommand中有Intent,和BroadcastReciver的套路有點像ide
---->[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)
}
複製代碼
建立另外一個App,進行測試
Activity
、BroadcastReciver
、Service
是四大組件的三棵頂樑柱
Intent能夠根據組件包名及類名開啓組件,Activity
、BroadcastReciver
能夠,Service
天然也能夠,函數
侷限性:佈局
1.須要添加android:exported="true",不然會崩
<service android:name=".service.service.ToastService" android:exported="true"/>
2.大概一分鐘後會自動銷燬,自動銷燬後再用就會崩...因此約等於無用
複製代碼
Android5.0+ 明確指出不能隱式調用:ContextImpl的
validateServiceIntent
方法中post
---->[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也能夠)
爲了方便管理,這裏寫了一個IPlayer接口規定一下MusicPlayer的幾個主要方法
暫時都是無返回值,無入參的方法,之後有須要再逐步完善
/**
* 做者:張風捷特烈<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()//死亡
}
複製代碼
/**
* 做者:張風捷特烈<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()
}
}
複製代碼
/**
* 做者:張風捷特烈<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"
}
}
複製代碼
/**
* 綁定服務
*/
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,以下
這裏爲了簡潔些,直接用四個路徑,判斷存在什麼的本身完善(非本文重點)
關於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)//拖動進度
}
複製代碼
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, "開始播放")
}
複製代碼
這樣歌曲就能播放了
---->[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()//播放完成,進入下一曲
}
複製代碼
這裏每隔一秒更新一下進度,經過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;
}
複製代碼
估計不少新手都有一個疑問,我直接在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自動生成的,你會有所感觸
還記得上面的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);
}
複製代碼
本文只是說一下生成的IMusicPlayerService如何使用,下一篇將詳細分析它 能夠看出IMusicPlayerService中有一個內部類Stub繼承自Binder還實現了
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
複製代碼
實現上和上面的
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;
}
}
複製代碼
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;
}
}
複製代碼
若是隻在本項目中用,將兩個類換下名字就好了和剛纔沒本質區別
/**
* 綁定服務
*/
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).Service的簡單`介紹及使用`
2).Service的`綁定服務`實現`音樂播放器(條)`
3).使用`aidl`實現其餘app訪問該Service,播放音樂
複製代碼
類名:Service 父類:ContextWrapper 修飾:public abstract
實現的接口:[ComponentCallbacks2]
包名:android.app 依賴類個數:16
內部類/接口個數:0
源碼行數:790 源碼行數(除註釋):171
屬性個數:3 方法個數:21 public方法個數:20
複製代碼
Service和Activity同屬一家,一暗一明,Android做爲顏值擔當,Service作後臺工做(如圖)
他不見天日,卻要忠誠地執行任務,Service這個類的自己很是小,裸碼171行
是什麼讓它成爲"新手的噩夢",一個單詞:Binder
,曾經讓多少人聞風喪膽的首席殺手
/**
* 做者:張風捷特烈<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"
}
}
複製代碼
就兩個按鈕,點一下
//開啓服務
id_btn_start.setOnClickListener {
toastIntent = Intent(this, MusicService::class.java)
startService(toastIntent)
}
//銷燬服務
id_btn_kill.setOnClickListener {
stopService(toastIntent)
}
複製代碼
點一下開啓會執行
onCreate
和onStartCommand
方法
屢次點擊開啓,
onCreate
只會執行一次,onStartCommand
方法每次都會執行
點擊開啓與銷燬
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)
}
複製代碼
建立另外一個App,進行測試
Activity
、BroadcastReciver
、Service
是四大組件的三棵頂樑柱
Intent能夠根據組件包名及類名開啓組件,Activity
、BroadcastReciver
能夠,Service
天然也能夠,
侷限性:
1.須要添加android:exported="true",不然會崩
<service android:name=".service.service.ToastService" android:exported="true"/>
2.大概一分鐘後會自動銷燬,自動銷燬後再用就會崩...因此約等於無用
複製代碼
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也能夠)
爲了方便管理,這裏寫了一個IPlayer接口規定一下MusicPlayer的幾個主要方法
暫時都是無返回值,無入參的方法,之後有須要再逐步完善
/**
* 做者:張風捷特烈<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()//死亡
}
複製代碼
/**
* 做者:張風捷特烈<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()
}
}
複製代碼
/**
* 做者:張風捷特烈<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"
}
}
複製代碼
/**
* 綁定服務
*/
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,以下
這裏爲了簡潔些,直接用四個路徑,判斷存在什麼的本身完善(非本文重點)
關於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)//拖動進度
}
複製代碼
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, "開始播放")
}
複製代碼
這樣歌曲就能播放了
---->[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()//播放完成,進入下一曲
}
複製代碼
這裏每隔一秒更新一下進度,經過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;
}
複製代碼
估計不少新手都有一個疑問,我直接在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自動生成的,你會有所感觸
還記得上面的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);
}
複製代碼
本文只是說一下生成的IMusicPlayerService如何使用,下一篇將詳細分析它 能夠看出IMusicPlayerService中有一個內部類Stub繼承自Binder還實現了
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
複製代碼
實現上和上面的
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;
}
}
複製代碼
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;
}
}
複製代碼
若是隻在本項目中用,將兩個類換下名字就好了和剛纔沒本質區別
/**
* 綁定服務
*/
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的機制的第一層。