目錄
- 寫在前面的話
- 一、概述
- 二、遇到的問題
- 三、步驟展示
- 四、結果展示
- 五、補充
寫在前面的話
1、參考自:https://b23.tv/0mHcF5
2、内容如果有不對的,希望可以指出或補充。
3、由于這部分卡了很久,也算是個完整的練習,是以單獨提出來了。
4、新知識。
一、概述
通信方式:
1、本地服務通信:是指
應用程式内部
的通信,需要使用IBinder對象進行本地服務。
2、遠端服務通信:是指
兩個應用程式間
的通信,遠端服務通信是通過AIDL(Android Interface Definition Language,Android接口定義語言,定義了用戶端與服務端的一個标準)實作的。
這裡測試的是本地服務通信。
二、遇到的問題
以下都是在Android Studio4.1.1裡進行的。
報錯資訊:
最終換成以下這種虛拟機(成功解決了問題):
三、步驟展示
1、準備
檔案:
導入音頻檔案:
聲明:
2、布局
activity_play.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/bg"
android:gravity="center_vertical"
>
<!--拖動條-->
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:max="100"/>
<!--按鈕-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp">
<Button
android:id="@+id/play_or_pause_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放" />
<Button
android:id="@+id/close_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止" />
</LinearLayout>
</LinearLayout>
3、接口
activity與服務間通過接口連接配接。
4、服務
PlayerService.java
package com.example.testservice.services;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import com.example.testservice.presenter.PlayerPresenter;
public class PlayerService extends Service {
private static final String TAG = "PlayerService";
private PlayerPresenter mplayerPresenter;
@Override
public void onCreate(){
Log.d(TAG,"onCreate...");
super.onCreate();
if (mplayerPresenter == null) {
mplayerPresenter = new PlayerPresenter();
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind...");
return mplayerPresenter;
}
@Override
public void onDestroy() {
Log.d(TAG,"onDestroy...");
super.onDestroy();
mplayerPresenter = null;
}
}
5、功能實作部分
PlayerPresenter.java
package com.example.testservice.presenter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.util.Log;
import com.example.testservice.interfaces.IPlayerControl;
import com.example.testservice.interfaces.IPlayerViewControl;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
//實作邏輯層的服務
public class PlayerPresenter extends Binder implements IPlayerControl {
private static final String TAG = "PlayerPresenter";
private IPlayerViewControl mViewController;
private int mCurrentState = PLAY_STATE_STOP;//記錄目前的播放狀态 預設停止
private MediaPlayer mMediaPlayer;
private Timer mTimer;
private SeekTimeTask mTimeTask;
@Override
public void registerViewController(IPlayerViewControl viewControl) {
this.mViewController = viewControl;
}
@Override
public void unRegisterViewController() {
mViewController = null;
}
@Override
public void playOrPause() {
Log.i(TAG,"playOrPause...");
if (mCurrentState==PLAY_STATE_STOP) {
//建立播放器
initPlayer();
//設定資料源
try {
mMediaPlayer.setDataSource("/mnt/sdcard/testsong.mp3");
mMediaPlayer.prepare();
mMediaPlayer.start();
mCurrentState = PLAY_STATE_PLAY;
startTimer();//拖動條開始動
}catch (IOException e){
e.printStackTrace();
}
}else if(mCurrentState == PLAY_STATE_PLAY){
//如果目前狀态為播放則暫停
if (mMediaPlayer != null) {
mMediaPlayer.pause();
mCurrentState = PLAY_STATE_PAUSE;
stopTimer();//拖動條停止
}
}else if(mCurrentState == PLAY_STATE_PAUSE){
//如果目前狀态為暫停就繼續播放
if (mMediaPlayer != null) {
mMediaPlayer.start();
mCurrentState = PLAY_STATE_PLAY;
startTimer();
}
}
//通知ui更新界面按鈕文字
if (mViewController != null) {
mViewController.onPlayStateChange(mCurrentState);
}
}
//初始化播放器
private void initPlayer() {
if (mMediaPlayer == null) {
//音頻播放器
mMediaPlayer = new MediaPlayer();
//播放類型 因為播放的mp3
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
}
// 停止過後再播放就是從頭開始
@Override
public void stopPlay() {
Log.i(TAG,"stopPlay...");
if (mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mMediaPlayer.stop();
mCurrentState = PLAY_STATE_STOP;
stopTimer();
if (mViewController != null) {
mViewController.onPlayStateChange(mCurrentState);//更新播放狀态
}
mMediaPlayer.release();
mMediaPlayer = null;
}
}
@Override
public void seekTo(int seek) {
Log.i(TAG,"seekTo..."+seek);
//seek是0~100
//轉換 得到seek百分比
if (mMediaPlayer != null) {
//.getDuration()取到的是音頻的真實“長度”
int tarSeek = (int) (seek * 1.0f / 100 * mMediaPlayer.getDuration());
mMediaPlayer.seekTo(tarSeek);
}
}
//開啟一個timerTask
private void startTimer(){
if (mTimer == null) {
mTimer = new Timer();
}
if (mTimeTask == null) {
mTimeTask = new SeekTimeTask();
}
mTimer.schedule(mTimeTask,0,500);//500毫秒
}
//停止
private void stopTimer(){
if (mTimeTask != null) {
mTimeTask.cancel();
mTimeTask = null;//置空
}
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}
private class SeekTimeTask extends TimerTask{
@Override
public void run() {
if (mMediaPlayer != null && mViewController != null) {
//擷取目前的播放進度
int currentPosition = mMediaPlayer.getCurrentPosition();
//Log.d(TAG,"current play position..."+currentPosition);
//目前的播放進度/總的播放進度
int curPosition = (int) (currentPosition * 1.0f / mMediaPlayer.getDuration() *100);
//改變ui界面拖動條
mViewController.onSeekChange(curPosition);
}
}
}
}
6、活動視窗
PlayerActivity.java
package com.example.testservice;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import androidx.annotation.Nullable;
import com.example.testservice.interfaces.IPlayerControl;
import com.example.testservice.interfaces.IPlayerViewControl;
import com.example.testservice.services.PlayerService;
import static com.example.testservice.interfaces.IPlayerControl.PLAY_STATE_PAUSE;
import static com.example.testservice.interfaces.IPlayerControl.PLAY_STATE_PLAY;
import static com.example.testservice.interfaces.IPlayerControl.PLAY_STATE_STOP;
//ui的展示
//activity與服務間通過接口連接配接
public class PlayerActivity extends Activity {
private static final String TAG = "PlayerActivity";
private SeekBar mSeekBar;
private Button mPlayOrPause,mClose;
private PlayerConnection mPlayerConnection;
private IPlayerControl mIPlayerControl;
private boolean isUserTouchProgressBar = false;//預設使用者沒觸摸到拖動條
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_play);
initView();
//設定相關事件
initEvent();
//另需開啟服務 這樣服務才能長期運作
initService();
//綁定服務
initBindService();
}
//開啟播放的服務
private void initService() {
Log.d(TAG,"initService");
startService(new Intent(this,PlayerService.class));
}
//綁定服務
private void initBindService() {
Log.d(TAG,"initBindService");
Intent intent = new Intent(this, PlayerService.class);
if(mPlayerConnection == null) {
mPlayerConnection = new PlayerConnection(); //ctrl+alt+f抽取成員變量
}
bindService(intent,mPlayerConnection,BIND_AUTO_CREATE);
}
private class PlayerConnection implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"onServiceConnected------>"+service);
mIPlayerControl = (IPlayerControl) service;//這樣才能進行下面的控件事件
//按鈕功能的接口連接配接
mIPlayerControl.registerViewController(mIPlayerViewControl);
}
@Override
//斷開連接配接(綁定
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG,"onServiceDisconnected");
mIPlayerControl = null;
}
}
//初始化控件事件
private void initEvent() {
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//進度條發生改變
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//拖動進度條
isUserTouchProgressBar = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int touchProgress = seekBar.getProgress();
Log.d(TAG,"touchProgress------>"+touchProgress);
//停止拖動
if (mIPlayerControl != null) {
mIPlayerControl.seekTo(touchProgress);
}
isUserTouchProgressBar = false;//沒用觸摸到拖動條
}
});
mPlayOrPause.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//播放或暫停
if(mIPlayerControl != null){
mIPlayerControl.playOrPause();
}
}
});
mClose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//點選關閉按鈕
//需要判空,否則容易崩掉
if(mIPlayerControl != null) {
mIPlayerControl.stopPlay();
}
}
});
}
//初始化各控件
private void initView() {
mSeekBar = (SeekBar) this.findViewById(R.id.seek_bar);
mPlayOrPause = (Button) this.findViewById(R.id.play_or_pause_btn);
mClose = (Button) this.findViewById(R.id.close_btn);
}
//釋放服務
@Override
protected void onDestroy() {
super.onDestroy();
if(mPlayerConnection != null){
mIPlayerControl.unRegisterViewController();//釋放資源
Log.d(TAG,"unbind service------>onDestroy");
unbindService(mPlayerConnection);
}
}
//ui狀态
private IPlayerViewControl mIPlayerViewControl = new IPlayerViewControl() {
@Override
public void onPlayStateChange(int state) {
//根據播放狀态來修改ui(按鈕上顯示的文字)
switch (state){
case PLAY_STATE_PLAY:
mPlayOrPause.setText("暫停");
break;
case PLAY_STATE_PAUSE:
case PLAY_STATE_STOP:
mPlayOrPause.setText("播放");
break;
}
}
@Override
public void onSeekChange(final int seek) {
//在Android中progressBar、surfaceView控件可以用子線程更新(不是主線程但也沒問題原因)
Log.d(TAG,"current thread-----"+Thread.currentThread().getName());//從結果得出不是主線程
//是以進行如下操作:
runOnUiThread(new Runnable() {
@Override
public void run() {
//改變播放進度(拖動條)
if (!isUserTouchProgressBar) {
mSeekBar.setProgress(seek);
}
}
});
}
};
}
四、結果展示
可在背景運作。
五、補充
1、MediaPlayer播放音頻與視訊
2、seekBar屬性詳解