Android简单音乐播放器的实现

ChassidyHar 7年前
   <h2><strong>前言</strong></h2>    <p>啦啦啦~各位好久不见啦~博主最近比较忙,而且最近一次实验也是刚刚结束~</p>    <p>好了不废话了,直接进入我们这次的内容~</p>    <p>在这篇文章里我们将学习Service(服务)的相关知识,学会使用 Service 进行后台工作, 学会使用 Service 与 Activity 进行通信,并在此知识基础上学会使用 MediaPlayer和简单的多线程编程、使用 Handle 更新 UI,并设计成功一个简单的音乐播放器。</p>    <h2><strong>基础知识</strong></h2>    <p>Service作为Android四大组件之一,在每一个应用程序中都扮演着非常重要的角色。它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务。必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持运行状态。</p>    <p>所以Service通常被称为“后台服务”,其中“后台”一词是相对于前台而言的,具体是指其本身的运行并不依赖于用户可视的UI界面,因此,从实际业务需求上来理解,Service的适用场景应该具备以下条件:</p>    <p>(1)并不依赖于用户可视的UI界面(当然,这一条其实也不是绝对的,如前台Service就是与Notification界面结合使用的);</p>    <p>(2)具有较长时间的运行特性。</p>    <h3><strong>1.Service AndroidManifest.xml 声明</strong></h3>    <p>一般而言,从Service的启动方式上,可以将Service分为Started Service和Bound Service。无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来。在使用Service时,要想系统能够找到此自定义Service,无论哪种类型,都需要在AndroidManifest.xml中声明,语法格式如下:</p>    <pre>  <code class="language-java"><service android:enabled=["true" | "false"]      android:exported=["true" | "false"]      android:icon="drawable resource"      android:isolatedProcess=["true" | "false"]      android:label="string resource"      android:name="string"      android:permission="string"      android:process="string" >      . . .  </service>  </code></pre>    <p>其中,android:name对应Service类名,android:permission是权限声明,android:process设置具体的进程名称。需要注意的是Service能否单独使用一个进程与其启动方式有关,本后下面会给出具体说明。其他的属性此处与其他组件基本相同,不再过多描述。</p>    <p>注:如果自定义Service没有在AndroidManifest.xml中声明,当具体使用时,不会像Activity那样直接崩溃报错,对于显式Intent启动的Service,此时也会给出waring信息“IllegalArgumentException: Service not registered”,有时候不容易发现忘了声明而一时定位不到问题。</p>    <h3><strong>2.Started Service</strong></h3>    <p>Started Service相对比较简单,通过context.startService(Intent serviceIntent)启动Service,context.stopService(Intent serviceIntent)停止此Service。当然,在Service内部,也可以通过stopSelf(...)方式停止其本身。</p>    <p>1)Started Service自定义</p>    <p>下面代码片段显示的是一个最基本的Started Service的自定义方式:</p>    <pre>  <code class="language-java">public class MyService extends Service {        public static final String TAG = "MyService";        @Override      public IBinder onBind(Intent intent) {          return null;      }        @Override      public void onCreate() {          super.onCreate();          Log.w(TAG, "in onCreate");      }        @Override      public int onStartCommand(Intent intent, int flags, int startId) {          Log.w(TAG, "in onStartCommand");          Log.w(TAG, "MyService:" + this);          String name = intent.getStringExtra("name");          Log.w(TAG, "name:" + name);          return START_STICKY;      }        @Override      public void onDestroy() {          super.onDestroy();          Log.w(TAG, "in onDestroy");      }  }  </code></pre>    <p>其中,onBind(...)函数是Service基类中的唯一抽象方法,子类都必须重写实现,此函数的返回值是针对Bound Service类型的Service才有用的,在Started Service类型中,此函数直接返回 null 即可。onCreate(...)、onStartCommand(...)和onDestroy()都是Started Service相应生命周期阶段的回调函数。</p>    <p>2) Started Service使用</p>    <pre>  <code class="language-java">public class MainActivity extends Activity {        public static final String TAG = "MainActivity";        private Button startServiceBtn;      private Button stopServideBtn;      private Button goBtn;        private Intent serviceIntent;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);            startServiceBtn = (Button) findViewById(R.id.start_service);          stopServideBtn = (Button) findViewById(R.id.stop_service);          goBtn = (Button) findViewById(R.id.go);            startServiceBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  serviceIntent = new Intent(MainActivity.this, MyService.class);                  startService(serviceIntent);              }          });            stopServideBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  stopService(serviceIntent);              }          });            goBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  Intent intent = new Intent(MainActivity.this, BActivity.class);                  startActivity(intent);              }          });        }        @Override      protected void onDestroy() {          super.onDestroy();          Log.w(TAG, "in onDestroy");      }  }  </code></pre>    <p>如上代码片段。</p>    <p>当Client调用startService(Intent serviceIntent)后,如果MyService是第一次启动,首先会执行 onCreate()回调,然后再执行onStartCommand(Intent intent, int flags, int startId),当Client再次调用startService(Intent serviceIntent),将只执行onStartCommand(Intent intent, int flags, int startId),因为此时Service已经创建了,无需执行onCreate()回调。无论多少次的startService,只需要一次stopService()即可将此Service终止,执行onDestroy()函数(其实很好理解,因为onDestroy()与onCreate()回调是相对的)。</p>    <p>下面重点关注下onStartCommand(Intent intent, int flags, int startId)方法。</p>    <p>其中参数flags默认情况下是0,对应的常量名为START_STICKY_COMPATIBILITY。startId是一个唯一的整型,用于表示此次Client执行startService(...)的请求请求标识,在多次startService(...)的情况下,呈现0,1,2....递增。另外,此函数具有一个int型的返回值,具体的可选值及含义如下:</p>    <p>START_NOT_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service。</p>    <p>START_STICKY:当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外。</p>    <p>START_REDELIVER_INTENT:与START_STICKY唯一不同的是,回调onStartCommand(...)方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent。</p>    <p>START_STICKY_COMPATIBILITY:compatibility version of {@link #START_STICKY} that does not guarantee that {@link #onStartCommand} will be called again after being killed。此值一般不会使用,所以注意前面三种情形就好。</p>    <p>3) Started Service生命周期及进程相关</p>    <p>1.onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional ... >> onDestroy(Client调用stopService(..))</p>    <p>注:onStartCommand(..)可以多次被调用,onDestroy()与onCreate()想匹配,当用户强制kill掉进程时,onDestroy()是不会执行的。</p>    <p>2.对于同一类型的Service,Service实例一次永远只存在一个,而不管Client是否是相同的组件,也不管Client是否处于相同的进程中。</p>    <p>3.Service通过startService(..)启动Service后,此时Service的生命周期与Client本身的什么周期是没有任何关系的,只有Client调用stopService(..)或Service本身调用stopSelf(..)才能停止此Service。当然,当用户强制kill掉Service进程或系统因内存不足也可能kill掉此Service。</p>    <p>4.Client A 通过startService(..)启动Service后,可以在其他Client(如Client B、Client C)通过调用stopService(..)结束此Service。</p>    <p>5.Client调用stopService(..)时,如果当前Service没有启动,也不会出现任何报错或问题,也就是说,stopService(..)无需做当前Service是否有效的判断。</p>    <p>6.startService(Intent serviceIntent),其中的intent既可以是显式Intent,也可以是隐式Intent,当Client与Service同处于一个App时,一般推荐使用显示Intent。当处于不同App时,只能使用隐式Intent。</p>    <p>当Service需要运行在单独的进程中,AndroidManifest.xml声明时需要通过android:process指明此进程名称,当此Service需要对其他App开放时,android:exported属性值需要设置为true(当然,在有intent-filter时默认值就是true)。</p>    <pre>  <code class="language-java"><service      android:name=".MyService"      android:exported="true"      android:process=":MyCorn" >      <intent-filter>          <action android:name="com.example.androidtest.myservice" />      </intent-filter>  </service>  </code></pre>    <p>4)Started Service Client与Service通信相关</p>    <p>当Client调用startService(Intent serviceIntent)启动Service时,Client可以将参数通过Intent直接传递给Service。Service执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播的方式(此时,Client需要注册此广播)。</p>    <h3><strong>3.Bound Service</strong></h3>    <p>相对于Started Service,Bound Service具有更多的知识点。Bound Service的主要特性在于Service的生命周期是依附于Client的生命周期的,当Client不存在时,Bound Service将执行onDestroy,同时通过Service中的Binder对象可以较为方便进行Client-Service通信。Bound Service一般使用过程如下:</p>    <p>1.自定义Service继承基类Service,并重写onBind(Intent intent)方法,此方法中需要返回具体的Binder对象;</p>    <p>2.Client通过实现ServiceConnection接口来自定义ServiceConnection,并通过bindService (Intent service, ServiceConnection sc, int flags)方法将Service绑定到此Client上;</p>    <p>3.自定义的ServiceConnection中实现onServiceConnected(ComponentName name, IBinder binder)方法,获取Service端Binder实例;</p>    <p>4.通过获取的Binder实例进行Service端其他公共方法的调用,以完成Client-Service通信;</p>    <p>5.当Client在恰当的生命周期(如onDestroy等)时,此时需要解绑之前已经绑定的Service,通过调用函数unbindService(ServiceConnection sc)。</p>    <p>在Bound Service具体使用过程中,根据onBind(Intent intent)方法放回的Binder对象的定义方式不同,又可以将其分为以下三种方式,且每种方式具有不同的特点和适用场景:</p>    <p>1).Extending the Binder class</p>    <p>这是Bound Service中最常见的一种使用方式,也是Bound Service中最简单的一种。</p>    <p>局限:Clinet与Service必须同属于同一个进程,不能实现进程间通信(IPC)。否则则会出现类似于“android.os.BinderProxy cannot be cast to xxx”错误。</p>    <p>下面通过代码片段看下具体的使用:</p>    <pre>  <code class="language-java">public class MyBindService extends Service {        public static final String TAG = "MyBindService";        private MyBinder mBinder = new MyBinder();        public class MyBinder extends Binder {          MyBindService getService() {              return MyBindService.this;          }      }        @Override      public void onCreate() {          super.onCreate();          Log.w(TAG, "in onCreate");      }        @Override      public IBinder onBind(Intent intent) {          Log.w(TAG, "in onBind");          return mBinder;      }        @Override      public boolean onUnbind(Intent intent) {          Log.w(TAG, "in onUnbind");          return super.onUnbind(intent);      }        @Override      public void onDestroy() {          super.onDestroy();          Log.w(TAG, "in onDestroy");      }  }  </code></pre>    <pre>  <code class="language-java">public class BActivity extends Activity {        public static final String TAG = "BActivity";        private Button bindServiceBtn;      private Button unbindServiceBtn;        private Button startIntentService;        private Intent serviceIntent;        private ServiceConnection sc = new MyServiceConnection();      private MyBinder mBinder;      private MyBindService mBindService;      private boolean mBound;        private class MyServiceConnection implements ServiceConnection {            @Override          public void onServiceConnected(ComponentName name, IBinder binder) {              Log.w(TAG, "in MyServiceConnection onServiceConnected");              mBinder = (MyBinder) binder;              mBindService = mBinder.getService();                mBound = true;          }            @Override          public void onServiceDisconnected(ComponentName name) {              // This is called when the connection with the service has been              // unexpectedly disconnected -- that is, its process crashed.              Log.w(TAG, "in MyServiceConnection onServiceDisconnected");              mBound = false;          }        }        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.b);            bindServiceBtn = (Button) findViewById(R.id.bind_service);          unbindServiceBtn = (Button) findViewById(R.id.unbind_service);          startIntentService = (Button) findViewById(R.id.start_intentservice);            bindServiceBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  Intent intent = new Intent(BActivity.this, MyBindService.class);                  bindService(intent, sc, Context.BIND_AUTO_CREATE);              }          });            unbindServiceBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  excuteUnbindService();              }          });            startIntentService.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  Intent intent = new Intent(BActivity.this, MyIntentService.class);                  startService(intent);              }          });        }        private void excuteUnbindService() {          if (mBound) {              unbindService(sc);              mBound = false;          }      }        @Override      protected void onDestroy() {          super.onDestroy();          Log.w(TAG, "in onDestroy");          excuteUnbindService();      }  }  </code></pre>    <p>首次点击bindServiceBtn进行bindService(..)时,依次回调顺序如下:</p>    <p>1 MyBindService(13457): in onCreate</p>    <p>2 MyBindService(13457): in onBind</p>    <p>3 BActivity(13457): in MyServiceConnection onServiceConnected</p>    <p>再次点击bindServiceBtn按钮时,发现没有任何输出,说明MyBindService没有进行任何回调。</p>    <p>点击unbindServiceBtn进行unbindService(..)时,回调顺序为:</p>    <p>1 MyBindService(13457): in onUnbind</p>    <p>2 MyBindService(13457): in onDestroy</p>    <p>注:在四大基本组件中,需要注意的的是BroadcastReceiver不能作为Bound Service的Client,因为BroadcastReceiver的生命周期很短,当执行完onReceive(..)回调时,BroadcastReceiver生命周期完结。而Bound Service又与Client本身的生命周期相关,因此,Android中不允许BroadcastReceiver去bindService(..),当有此类需求时,可以考虑通过startService(..)替代。</p>    <h3><strong>4.Service特性</strong></h3>    <p>1.Service本身都是运行在其所在进程的主线程(如果Service与Clinet同属于一个进程,则是运行于UI线程),但Service一般都是需要进行”长期“操作,所以经常写法是在自定义Service中处理”长期“操作时需要新建线程,以免阻塞UI线程或导致ANR;</p>    <p>2.Service一旦创建,需要停止时都需要显示调用相应的方法(Started Service需要调用stopService(..)或Service本身调用stopSelf(..), Bound Service需要调用unbindService(..)),否则对于Started Service将处于一直运行状态,对于Bound Service,当Client生命周期结束时也将因此问题。也就是说,Service执行完毕后,必须人为的去停止它。</p>    <h3><strong>5.IntentService</strong></h3>    <p>IntentService是系统提供给我们的一个已经继承自Service类的特殊类,IntentService特殊性是相对于Service本身的特性而言的:</p>    <p>1.默认直接实现了onBind(..)方法,直接返回null,并定义了抽象方法onHandlerIntent(..),用户自定义子类时,需要实现此方法;</p>    <p>2.onHandlerIntent(..)主要就是用来处于相应的”长期“任务的,并且已经自动在新的线程中,用户无语自定义新线程;</p>    <p>3.当”长期“任务执行完毕后(也就是onHandlerIntent(..)执行完毕后),此IntentService将自动结束,无需人为调用方法使其结束;</p>    <p>4.IntentService处于任务时,也是按照队列的方式一个个去处理,而非真正意义上的多线程并发方式。</p>    <p>下面是一个基本的继承自IntentService的自定义Service:</p>    <pre>  <code class="language-java">public class MyIntentService extends IntentService {        public static final String TAG = "MyIntentService";        public MyIntentService() {          super(TAG);      }        public MyIntentService(String name) {          super(name);      }        @Override      protected void onHandleIntent(Intent intent) {          Log.w(TAG, "in onHandleIntent");          Log.w(TAG, "thread name:" + Thread.currentThread().getName());      }    }  </code></pre>    <h3><strong>6.Service和Activity通信</strong></h3>    <p>上面我们学习了Service的基本用法,启动Service之后,就可以在onCreate()或onStartCommand()方法里去执行一些具体的逻辑了。不过这样的话Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。那么有没有什么办法能让它们俩的关联更多一些呢?比如说在Activity中可以指定让Service去执行什么任务。当然可以,只需要让Activity和Service建立关联就好了。</p>    <p>观察MyService中的代码,你会发现一直有一个onBind()方法我们都没有使用到,这个方法其实就是用于和Activity建立关联的,修改MyService中的代码,如下所示:</p>    <pre>  <code class="language-java">public class MyService extends Service {            public static final String TAG = "MyService";            private MyBinder mBinder = new MyBinder();            @Override        public void onCreate() {            super.onCreate();            Log.d(TAG, "onCreate() executed");        }            @Override        public int onStartCommand(Intent intent, int flags, int startId) {            Log.d(TAG, "onStartCommand() executed");            return super.onStartCommand(intent, flags, startId);        }            @Override        public void onDestroy() {            super.onDestroy();            Log.d(TAG, "onDestroy() executed");        }            @Override        public IBinder onBind(Intent intent) {            return mBinder;        }            class MyBinder extends Binder {                public void startDownload() {                Log.d("TAG", "startDownload() executed");                // 执行具体的下载任务            }            }        }    </code></pre>    <h2>实验内容</h2>    <p>实现一个简单的播放器,要求功能有:</p>    <p>1. 播放、暂停,停止,退出功能;</p>    <p>2. 后台播放功能;</p>    <p>3. 进度条显示播放进度、拖动进度条改变进度功能;</p>    <p>4. 播放时图片旋转,显示当前播放时间功能。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2dc527c304de8dc0071b0831cc49ee25.png"> <img src="https://simg.open-open.com/show/e147f9367100e5008cb9f48414d5adda.png"> <img src="https://simg.open-open.com/show/aac9bf686f941e015a82c23f02cee6cb.png"> <img src="https://simg.open-open.com/show/caaf5f58c87992711ad3ba3dba0dc12b.png"></p>    <p> </p>    <p>打开程序主页面                       开始播放                                   暂停                                          停止</p>    <h2><strong>实验内容相关知识  </strong></h2>    <h3><strong>1. MediaPlayer 介绍</strong></h3>    <p>常用方法</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f73be6713a71787a5a9ede3672a2529b.png"></p>    <h3><strong>2. 向虚拟机添加文件</strong></h3>    <p>首先打开 Android Device Monitor,如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/19142346ea54d330d3c3f9d5fc900f97.png"></p>    <p>使用自己手机进行调试时,注意下把文件拷到内置 SD 卡而不是外置 SD 卡会比较方 便。要使用外置的 SD 卡时,注意下文件路径的获取。</p>    <h3><strong>3.使用 MediaPlayer</strong></h3>    <p>创建对象初始化:</p>    <p>注意下获取的文件路径,若是使用模拟器的如下,若是使用自己手机的内置 SD 卡则使 用:Environment.getExternalStorageDirectory()  +   "/data/music.mp3</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/80c419b452a2ad8b95a334e5f189bec0.png"></p>    <p>播放/暂停:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/27e1fe780fc02f9da015f00dbbd4701d.png"></p>    <p>停止:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/05f8cb77eecfee11f3980fd7e0d639b1.png"></p>    <h3><strong>4.Service 的使用</strong></h3>    <p>创建 service 类,实现 MediaPlayer 的功能。 注意在 AndroidManifest.xml 文件里注册 Service:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0e7d0f8f0e3b68afe6bba67d21786021.png"></p>    <p>通过 Binder 来保持 Activity 和 Service 的通信(写在 service 类):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b176c9b9aff1a2f62d333112b5845413.png"></p>    <p>在 Activity 中调用 bindService 保持与 Service 的通信(写在 activity 类): Activity 启动时绑定 Service:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0723ecbe8fbe9f307d2f727978ae0d3c.png"></p>    <p>bindService 成功后回调 onServiceConnected 函数,通过 IBinder 获取 Service 对 象,实现 Activity 与 Service 的绑定:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/73ce9dad70697eefaee6b1609cb63baf.png"></p>    <p>停止服务时,必须解除绑定,写入退出按钮中:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/298ec20c8eb593e8908b85e7a6f4b488.png"></p>    <p>此时,在 Activity 的 onCreate 方法中执行上述与 Service 通信的方法后,即可实现 后台播放。点击退出按钮,程序会退出,音乐停止;返回桌面,音乐继续播放。</p>    <h3><strong>5.Handler的使用</strong></h3>    <p>Handler 与 UI 是同一线程,这里可以通过 Handler 更新 UI 上的组件状态,Handler 有很多方法,这里使用比较简便的 post 和 postDelayed 方法。</p>    <p>使用 Seekbar 显示播放进度,设置当前值与最大值:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ab21c6d405c014eac5a90913845bff1a.png"></p>    <p>定义 Handler:run 函数中进行更新 seekbar 的进度在类中定义简单日期格式,用来显 示播放的时间,用 time.format 来格式所需要的数据,用来监听进度条的滑动变化:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8f64cf26e6ef176201884242682e5d14.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b5e2eec49a10c78ea1e0a5acef1e8f3d.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2b720581d8ca791a6f9643939b975b59.png"></p>    <h2><strong>实验过程</strong></h2>    <p>本次实验主要是实现一个音乐播放器,首先打开Android Device Monitor, 向虚拟机添加音乐文件。</p>    <p>创建 service 类,使用 MediaPlayer,创建对象,设置一个布尔代数变量作为我们判断音乐此时状态的tag,使用setDataSource方法调用虚拟设备中的音乐文件,并对其进行初始化:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e61d809133d55e60235f2f732b13bd51.png"></p>    <p>在 AndroidManifest.xml 文件里注册 Service:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c0746e185ae5cb00c3099477f154e3a3.png"></p>    <p>并在其中声明SD卡的读写权限:</p>    <p><img src="https://simg.open-open.com/show/2cdb5bb2d52c965180155a38fbc18dc8.png"></p>    <p>通过 Binder 来保持 Activity 和 Service 的通信(写在 service 类):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/352de1a8c6c9bbeb314ec9c3703aea61.png"></p>    <p>在Activity中调用 bindService 保持与 Service 的通信:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/117cb8cb049508ed24bce6f57e322eeb.png"></p>    <p>Activity 启动时绑定 Service。</p>    <p>bindService成功后回调onServiceConnected 函数,通过IBinder 获取 Service对象,实现Activity与 Service的绑定:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/9bfbf77bef18ddecbba4046708df00c8.png"></p>    <p>在Activity中设置按钮点击事件时我们需要调用MediaPlayer中的某些方法,这里我们先在service类中实现MediaPlayer的功能:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f3682d74d1040cf5cc0abbfb8307e929.png"></p>    <p>此时,在 Activity 的 onCreate方法中执行上述与 Service 通信的方法后,即可实现后台播放。点击退出按钮,程序会退出,音乐停止;返回桌面,音乐继续播放。</p>    <p>Handler 与 UI 是同一线程,这里可以通过 Handler 更新 UI 上的组件状态,通过Handler可以统一进行对UI的管理,因为Handler采用消息处理的机制。简单理解就是另一个线程发送一个编号给消息所在的线程,那么该线程的消息处理程序就会收到该消息并进行处理,而消息采用int类型,所以int能够表示多少种数字就以为着有多少消息可以给你使用(因为存在系统的消息,所以可能有一部分被系统的消息占有,而不能使用)。Handler有很多方法,这里使用比较简便的 post和postDelayed 方法。</p>    <p>定义 Handler:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/939db5bb1e8f0f9a6a128f81846134ee.png"></p>    <p>run 函数中进行更新 seekbar 的进度,在类中定义简单日期格式,用来显示播放的时间,用 time.format 来格式所需要的数据,用来监听进度条的滑动变化(使用 Seekbar 显示播放进度,设置当前值与最大值,具体事件在onCreate方法中实现):</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b45b09ad52116b34aa2920c501d31198.png"></p>    <p>接下来,我们需要完成各个按钮的点击事件。在点击事件中,为了完成图片旋转以及其实时更新,我们使用animator属性.我们首先实例化了一个ObjectAnimator,然后设置所需的参数:imageView:需要更改的View,动画类型,动画范围,并在对应的点击事件中引用animator中的一些事件,实现动画的点击动作:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3fdc0a40c8d7e200390be130261d82c8.png"></p>    <p>在点击事件中,主要是实现按钮文本的变换以及调用service中定义好的MediaPlayer事件,并实现动画的变换:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/80a74ecec0a37974ef26f489f8721bd7.png"></p>    <p>这里需要注意的是,在btnPlayOrPause.setOnClickListener中,为了正确实现动画事件,我们需要设置相应的tag,由tag的变换来控制事件的调用:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/99f21d43ad7fa6eee8900595598c2322.png"></p>    <p>而且停止服务时,必须解除绑定,写入btnQuit按钮中:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/60708f0e310bf4efeb3d561052f136a0.png"></p>    <p>最后,在onCreate函数中,我们将定义的各个函数一一实现,即可完成实验的功能。</p>    <p>除此之外,为了保证返回后台(按了返回键后)仍能继续播放,并且打开应用后Activity中的内容要与音乐当前的状态对应,我们还需要获取并设置返回键的点击事件:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3d89b06ca2468f66a9e8c1c61db785a1.png"></p>    <p> </p>    <h2><strong>注意事项</strong></h2>    <p>在设置mediaPlayer时出现了 android mediaPlayer error (-38,0) 的报错。</p>    <p>在查询了一些资料后,解决了这个问题:</p>    <p>android mediaPlayer error (-38,0) 出现这个错误发现在mediaPlayer.reset()后调用了mediaPlayer.getDuration()在没有给mediaPlayer对象设置数据源之前,是不能使用getDuration等这些方法的</p>    <p>这时,需要检查一下在设置MediaPlayer的数据源时,使用的是那种方式:</p>    <p>1.在初始化MediaPlayer时,通过create方法设置数据源。则不能写MediaPlayer.prepare()方法,这时,会报错。</p>    <p>2.如果是使用MediaPlayer构造函数初始化MediaPlayer,然后通过setDataSource方法设置数据源时,就需要在start()之前,使用MediaPlayer.prepare()方法,对数据源进行一次编译。能够避免出现(-38,0)这种错误。</p>    <h2><strong>实验截图</strong></h2>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0d8a4465c105b0bd929553b39344b739.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4307a51126bd76fe69edce1eaeba8493.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8fa95d411c33decedc9e4fb5f34490a9.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5ff6aede36f03e1cb4b580b61176725f.png"></p>    <h2><strong>注</strong></h2>    <p>1、本实验实验环境:</p>    <p>操作系统 Windows 10 </p>    <p>实验软件 Android Studio 2.2.1</p>    <p>虚拟设备:Nexus_5X</p>    <p>API:23</p>    <p>2、贴代码的时候由于插入代码框的大小问题,代码格式不太严整,望见谅~</p>    <p> </p>    <p>来自:http://www.cnblogs.com/yanglh6-jyx/p/Android_Service_MediaPlayer.html</p>    <p> </p>