第七章 后台服务(远程服务)

参考资料:
《Android应用程序开发》ISBN 9787302283164
参考软件:
Android Studio、Eclipse+ADT、Android SDK、JDK
远程服务
一、什么是远程服务
简单来说就是:从一个进程调用另一个进程中的服务程序。
二、Android实现原理
如图1所示:

图1 android远程服务示意图
(1) 客户端一般是自定义的Activity,通过bindService()发起服务绑定
(2) 服务端,通过onBind()响应绑定请求,并返回共接口的Binder
(3) 客户端将接收到的Binder转换成接口对象ISecondary,好比获得了远程对象通信接口的应用
(4) 由于客户端与服务端定义了相同的接口(AIDL文档相同),所以可以通过(3)中的ISecondary对象调用接口中的方法,如此一来访问远程服务的方法如同访问本地对象的方法一般
(5) 通过接口实现代码将请求传给服务对象
(6) 服务代码调用自己的方法
一、 进程间的通讯IPC
q 在Android系统中,每个应用程序在各自的进程中运行,而且出于安全原因的考虑,这些进程之间彼此是隔离的,进程之间传递数据和对象,需要使用Android支持的进程间通信(Inter-Process Communication,IPC)机制
q 在Unix/Linux系统中,传统的IPC机制包括共享内存、管道、消息队列和socket等等,这些IPC机制虽然被广泛使用,但仍然存在着固有的缺陷,如容易产生错误、难于维护等等
q 在Android系统中,没有使用传统的IPC机制,而是采用Intent和远程服务的方式实现IPC,使应用程序具有更好的独立性和鲁棒性
q Android系统允许应用程序使用Intent启动Activity和Service,同时Intent可以传递数据,是一种简单、高效、易于使用的IPC机制
q Android系统的另一种IPC机制就是远程服务,服务和调用者在不同的两个进程中,调用过程需要跨越进程才能实现
q 在Android系统中使用远程服务,一般按照以下三个步骤实现
n 使用AIDL语言定义远程服务的接口
n 根据AIDL语言定义的接口,在具体的Service类中实现接口中定义的方法和属性
n 在需要调用远程服务的组件中,通过相同的AIDL接口文件,调用远程服务
二、服务创建与调用
q 在Android系统中,进程之间不能直接访问相互的内存控件,因此为了使数据能够在不同进程间传递,数据必须转换成能够穿越进程边界的系统级原语,同时,在数据完成进程边界穿越后,还需要转换回原有的格式
q AIDL(Android Interface Definition Language)是Android系统自定义的接口描述语言,可以简化进程间数据格式转换和数据交换的代码,通过定义Service内部的公共方法,允许在不同进程间的调用者和Service之间相互传递数据
q AIDL的IPC机制、COM和Corba都是基于接口的轻量级进程通信机制

q AIDL语言的语法与Java语言的接口定义非常相似,唯一不同之处在于,AIDL允许定义函数参数的传递方向
q AIDL支持三种方向:in、out和inout
n 标识为in的参数将从调用者传递到远程服务中
n 标识为out的参数将从远程服务传递到调用者中
n 标识为inout的参数将先从调用者传递到远程服务中,再从远程服务返回给调用者
q 如果不标识参数的传递方向,默认所有函数的传递方向为in
q 出于性能方面的考虑,不要在参数中标识不需要的传递方向
q 远程服务的创建和调用需要使用AIDL语言,一般分为以下几个过程
n 使用AIDL语言定义远程服务的接口
n 通过继承Service类实现远程服务
n 绑定和使用远程服务

三、服务端的建立
(1)建立一个android应用
向导完成
(2)使用AIDL语言定义远程服务的接口

package com.example.serverdemo;
publicinterface IMathService {
long add(long a,long b);
}
重命名
直接到项目文件夹中改名
然后在项目中刷新



n IMathService.java文件根据IMathService.aidl的定义,生成了一个内部静态抽象类Stub,Stub继承了Binder类,并实现IMathService接口
n 在Stub类中,还包含一个重要的静态类Proxy。可以认为Stub类用来实现本地服务调用,Proxy类用来实现远程服务调用,将Proxy作为Stub的内部类完全是出于使用方便的目的

(3)通过继承Service类实现远程服务

package com.example.serverdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.widget.Toast;
public class MathService extends Service {
private final IMathService.Stub mBinder = new IMathService.Stub(){
@Override
public long add(long a, long b) throws RemoteException {
// TODO Auto-generated method stub
return a + b;
}
};
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(this, "远程绑定:MathService",
Toast.LENGTH_SHORT).show();
return mBinder;
}
@Override
public boolean onUnbind (Intent intent){
Toast.makeText(this, "取消远程绑定:MathService",
Toast.LENGTH_SHORT).show();
return false;
}
}
(4)注册并隐式启动服务
四、客户端的建立
(1)建立android项目

(2)建立和服务端一样的AIDL
从服务端copy一份,改下包名
package com.example.clientdemo;
interface IMathService {
long add(long a,long b);
}
(3)绑定和使用远程服务
五、自定义数据类型传递
http://wenku.baidu.com/link?url=Ib7a77WsZpgrcPN3JQ4GUqO4K9v2Yzxi7lYPseV5rk-66DEQWL4J4fPs6x0MdEuRsxr7-cgeja8NlgCh2nDGPKEG6qSmI7swgHb6DgPZiUO
q 在Android系统中,进程间传递的数据包括:
n Java语言支持的基本数据类型
n 用户自定义的数据类型
q 为了使数据能够穿越进程边界,所有数据都必须是“可打包”的
q 对于Java语言的基本数据类型,打包过程是自动完成的
q 但对于自定义的数据类型,用户则需要实现Parcelable接口,使自定义的数据类型能够转换为系统级原语保存在Parcel对象中,穿越进程边界后可再转换为初始格式
实例



(1)服务器端建立

A、建立Role对象用来被传递

包--àNew-àclass



package com.lsu.aidlserverdemo;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
publicclass Role implements Parcelable {
private String name; //名字
private String vocation; //职业
private String profession; //专业
private String weapon; //武器
private String GS; //GS
private String credit; //当前声望
private Bitmap image; //头像
public String getName() {
returnname;
}
publicvoid setName(String name) {
this.name = name;
}
public String getVocation() {
returnvocation;
}
publicvoid setVocation(String vocation) {
this.vocation = vocation;
}
public String getProfession() {
returnprofession;
}
publicvoid setProfession(String profession) {
this.profession = profession;
}
public String getWeapon() {
returnweapon;
}
publicvoid setWeapon(String weapon) {
this.weapon = weapon;
}
public String getGS() {
returnGS;
}
publicvoid setGS(String gS) {
GS = gS;
}
public String getCredit() {
returncredit;
}
publicvoid setCredit(String credit) {
this.credit = credit;
}
public Bitmap getImage() {
returnimage;
}
publicvoid setImage(Bitmap image) {
this.image = image;
}
@Override
publicint describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
publicvoid writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(vocation);
dest.writeString(profession);
dest.writeString(weapon);
dest.writeString(GS);
dest.writeString(credit);
image.writeToParcel(dest, 0);
}
publicstaticfinal Parcelable.Creator<Role> CREATOR = new Creator<Role>(){
@Override
public Role createFromParcel(Parcel source) {
Role role = new Role();
role.name = source.readString();
role.vocation = source.readString();
role.profession = source.readString();
role.GS = source.readString();
role.credit = source.readString();
role.weapon = source.readString();
role.image = Bitmap.CREATOR.createFromParcel(source);
return role;
}
@Override
public Role[] newArray(int size) {
// TODO Auto-generated method stub
returnnew Role[size];
}
} ;
}
B、建立Role.aidl

C、首先建立MyAidl.aidl
File---ànew---àinterface


到源代码文件夹改名

在项目中刷新

代码 1
package com.lsu.aidlserverdemo;
import com.lsu.aidlserverdemo.Role;
interface MyAidl {
List getRoleList(); //得到所有的游戏角色
Role getRole(String name);
String getName();
}
先看主方法,里面定义了3个方法,分别为getRoleList()、getRole(String name)、getName()。返回值分别为List、Role、String。
需要特别注意,不是基本数据类型,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置,每个Aidl文件中只能定义一个Interface。
其中Role是我们自定义的实体类,Aidl并不能找到这个类,所以需要倒包,但是这个包是Role.java和Role.aidl所在的包。Role.aidl是对Role.java的一个指向
D、建立Service

代码 2
package com.lsu.aidlserverdemo;
import java.util.ArrayList;
import java.util.List;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.os.RemoteException;
public class DataProvider extends Service {
public static final String TAG = "aidlService";
ArrayList<Role> list;
@Override
public void onCreate() {
super.onCreate();
list = new ArrayList<Role>();
Role warrior = new Role();
warrior.setName("雨过天晴");
warrior.setVocation("战士");
warrior.setProfession("采矿");
warrior.setWeapon("巨剑");
warrior.setGS("5880");
warrior.setCredit("aaaaa");
warrior.setImage(BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher));
list.add(warrior);
}
@Override
public IBinder onBind(Intent intent) {
return new AidlImpl();
}
class AidlImpl extends MyAidl.Stub{
@Override
public List getRoleList() throws RemoteException {
// TODO Auto-generated method stub
return list;
}
@Override
public Role getRole(String name) throws RemoteException {
for(int i = 0 ; i<list.size();i++){
if(list.get(i).getName().equalsIgnoreCase(name)){
return list.get(i);
}
}
return null;
}
@Override
public String getName() throws RemoteException {
// TODO Auto-generated method stub
return "123";
}
}
}
E、注册服务

代码 3
<serviceandroid:name="com.lsu.aidlserverdemo.DataProvider">
<intent-filter>
<action android:name="com.lsu.edu.role"></action>
</intent-filter>
</service>
F、界面启动服务

代码 4
package com.lsu.aidlserverdemo;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button)findViewById(R.id.button1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent it = new Intent(MainActivity.this,DataProvider.class);
startService(it);
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
(2)客户端的建立

A、建立和服务器端一样的包并copy文件

这个几个文件和服务器端的一模一样
B、建立界面

代码 5
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.lsu.aidlclientdemo.MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignRight="@+id/button3"
android:layout_marginBottom="78dp"
android:layout_marginRight="18dp"
android:text="reset" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/button4"
android:layout_alignLeft="@+id/button1"
android:text="取消绑定" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/button3"
android:layout_toRightOf="@+id/textView1"
android:text="获取角色" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/button1"
android:layout_centerHorizontal="true"
android:text="启动service" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView1"
android:layout_marginTop="14dp"
android:text="TextView" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView2"
android:layout_below="@+id/textView2"
android:layout_marginTop="22dp"
android:text="TextView" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView3"
android:layout_below="@+id/textView3"
android:layout_marginTop="18dp"
android:text="TextView" />
<TextView
android:id="@+id/textView5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/textView1"
android:layout_alignRight="@+id/button2"
android:text="TextView" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView2"
android:layout_alignBottom="@+id/textView2"
android:layout_alignLeft="@+id/textView5"
android:text="TextView" />
<ImageView
android:id="@+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/button1"
android:layout_below="@+id/textView6"
android:src="@android:drawable/btn_default" />
</RelativeLayout>
C、实现“启动Service”按钮
六、android studio中远程服务的实现
在Android studio 实现远程服务(Service)AIDL的方法与在eclipse中实现有些不同,Android studio中自带了AIDL创建的方式,而eclipse中需要手动创建;
下面简单介绍实现远程服务(Service)AIDL的方法;
一、 创建服务工程应用项目 RemoteDemo
2. 创建 RemoteService 服务类
3. 创建AIDL文件, PublicBusiness 接口类
在创建之前在目录build-->generated-->source-->aidl-->androidTest-->debug下面发现还没有任何文件

创建 PublicBusiness AIDL

创建后会出现如下 : 左边的main目录下出现aidl文件夹和PublicBusiness.aidl文件和包,将PublicBusiness.aidl文件中的basicTypes 方法删除

4. 在 PublicBusiness接口类中写入提供远程调用的方法,在确保PublicBusiness.aidl所在的包名与项目中默认的包名一致,如果一致,点击 Build-->Make Project(也可以直接点下图箭头指向的地方),生成相应的java文件。如果不一致,则改aidl的包名,改成一致,再点击生成,生成效果如图。

此时会发现在目录 build-->generated-->source-->aidl-->androidTest-->debug下面出现了编译的文件;
二、 创建使用远程服务中的工程应用项目 UseRemoteDemo工程:
1. 将 RemoteDemo中main目录下的aidl文件夹全部复制到 UseRemoteDemo工程 的main目录下即可;


运行后的结果:

七、MediaPlay
本节引言:
本节带来的是Android多媒体中的——MediaPlayer,我们可以通过这个API来播放音频和视频 该类是Androd多媒体框架中的一个重要组件,通过该类,我们可以以最小的步骤来获取,解码 和播放音视频。它支持三种不同的媒体来源:
本地资源
内部的URI,比如你可以通过ContentResolver来获取
外部URL(流) 对于Android所支持的的媒体格式列表
对于Android支持的媒体格式列表,可见:Supported Media Formats 文档
本节我们就来用MediaPlayer来写个简单的播放音视频的例子!
官方API文档:MediaPlayer
1.相关方法详解
1)获得MediaPlayer实例:
可以直接new或者调用create方法创建:
MediaPlayer mp = new MediaPlayer();MediaPlayer mp = MediaPlayer.create(this, R.raw.test); //无需再调用setDataSource
另外create还有这样的形式: create(Context context, Uri uri, SurfaceHolder holder) 通过Uri和指定 SurfaceHolder 【抽象类】 创建一个多媒体播放器
2)设置播放文件:
//①raw下的资源:MediaPlayer.create(this, R.raw.test);//②本地文件路径:mp.setDataSource("/sdcard/test.mp3");//③网络URL文件:mp.setDataSource("http://www.xxx.com/music/test.mp3");
另外setDataSource()方法有多个,里面有这样一个类型的参数:FileDescriptor,在使用这个 API的时候,需要把文件放到res文件夹平级的assets文件夹里,然后使用下述代码设置DataSource:
AssetFileDescriptor fileDescriptor = getAssets().openFd("rain.mp3");m_mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());
3)其他方法
getCurrentPosition( ):得到当前的播放位置
getDuration() :得到文件的时间
getVideoHeight() :得到视频高度
getVideoWidth() :得到视频宽度
isLooping():是否循环播放
isPlaying():是否正在播放
pause():暂停
prepare():准备(同步)
prepareAsync():准备(异步)
release():释放MediaPlayer对象
reset():重置MediaPlayer对象
seekTo(int msec):指定播放的位置(以毫秒为单位的时间)
setAudioStreamType(int streamtype):指定流媒体的类型
setDisplay(SurfaceHolder sh):设置用SurfaceHolder来显示多媒体
setLooping(boolean looping):设置是否循环播放
setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener): 网络流媒体的缓冲监听
setOnCompletionListener(MediaPlayer.OnCompletionListener listener): 网络流媒体播放结束监听
setOnErrorListener(MediaPlayer.OnErrorListener listener): 设置错误信息监听
setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener): 视频尺寸监听
setScreenOnWhilePlaying(boolean screenOn):设置是否使用SurfaceHolder显示
setVolume(float leftVolume, float rightVolume):设置音量
start():开始播放
stop():停止播放
2.使用代码示例
示例一:使用MediaPlayer播放音频:
运行效果图:

关键代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private Button btn_play; private Button btn_pause; private Button btn_stop; private MediaPlayer mPlayer = null; private boolean isRelease = true; //判断是否MediaPlayer是否释放的标志 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { btn_play = (Button) findViewById(R.id.btn_play); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_play.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_play: if(isRelease){ mPlayer = MediaPlayer.create(this,R.raw.fly); isRelease = false; } mPlayer.start(); //开始播放 btn_play.setEnabled(false); btn_pause.setEnabled(true); btn_stop.setEnabled(true); break; case R.id.btn_pause: mPlayer.pause(); //停止播放 btn_play.setEnabled(true); btn_pause.setEnabled(false); btn_stop.setEnabled(false); break; case R.id.btn_stop: mPlayer.reset(); //重置MediaPlayer mPlayer.release(); //释放MediaPlayer isRelease = true; btn_play.setEnabled(true); btn_pause.setEnabled(false); btn_stop.setEnabled(false); break; } }}
注意事项:
播放的是res/raw目录下的音频文件,创建MediaPlayer调用的是create方法,第一次启动播放前 不需要再调用prepare(),如果是使用构造方法构造的话,则需要调用一次prepare()方法! 另外贴下官方文档中,从其他两种途径播放音频的示例代码:
本地Uri:
Uri myUri = ....; // initialize Uri hereMediaPlayer mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(getApplicationContext(), myUri);mediaPlayer.prepare();mediaPlayer.start();
外部URL:
String url = "http://........"; // your URL hereMediaPlayer mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDataSource(url);mediaPlayer.prepare(); // might take long! (for buffering, etc)mediaPlayer.start();
Note:假如你通过一个URL以流的形式播放在线音频文件,该文件必须可以进行 渐进式下载
示例二:使用MediaPlayer播放视频
MediaPlayer主要用于播放音频,没有提供图像输出界面,所以我们需要借助其他的 组件来显示MediaPlayer播放的图像输出,我们可以使用用SurfaceView 来显示,下面我们使用SurfaceView来写个视频播放的例子:
运行效果图:

实现代码:
布局文件:activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <SurfaceView android:id="@+id/sfv_show" android:layout_width="match_parent" android:layout_height="300dp" /> <Button android:id="@+id/btn_start" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="开始" /> <Button android:id="@+id/btn_pause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="暂停 " /> <Button android:id="@+id/btn_stop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="终止" /> </LinearLayout>
MainActivity.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, SurfaceHolder.Callback { private MediaPlayer mPlayer = null; private SurfaceView sfv_show; private SurfaceHolder surfaceHolder; private Button btn_start; private Button btn_pause; private Button btn_stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { sfv_show = (SurfaceView) findViewById(R.id.sfv_show); btn_start = (Button) findViewById(R.id.btn_start); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_start.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); //初始化SurfaceHolder类,SurfaceView的控制器 surfaceHolder = sfv_show.getHolder(); surfaceHolder.addCallback(this); surfaceHolder.setFixedSize(320, 220); //显示的分辨率,不设置为视频默认 } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: mPlayer.start(); break; case R.id.btn_pause: mPlayer.pause(); break; case R.id.btn_stop: mPlayer.stop(); break; } } @Override public void surfaceCreated(SurfaceHolder holder) { mPlayer = MediaPlayer.create(MainActivity.this, R.raw.lesson); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDisplay(surfaceHolder); //设置显示视频显示在SurfaceView上 } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} @Override public void surfaceDestroyed(SurfaceHolder holder) {} @Override protected void onDestroy() { super.onDestroy(); if (mPlayer.isPlaying()) { mPlayer.stop(); } mPlayer.release(); }}
代码很简单,布局有个SurfaceView,然后调用getHolder获得一个SurfaceHolder对象, 在这里完成SurfaceView相关的设置,设置了显示的分辨率以及一个Callback接口, 重写了SurfaceView创建时,发生变化时,以及销毁时的三个方法!然后按钮控制播放 以及暂停而已~
示例三:使用VideoView播放视频
除了使用MediaPlayer + SurfaceView播放视频的方式,我们还可以使用VideoView来直接 播放视频,我们稍微改点东西就可以实现视频播放!运行效果和上面的一致,就不贴了, 直接上代码!
MainActivity.java:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private VideoView videoView; private Button btn_start; private Button btn_pause; private Button btn_stop; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bindViews(); } private void bindViews() { videoView = (VideoView) findViewById(R.id.videoView); btn_start = (Button) findViewById(R.id.btn_start); btn_pause = (Button) findViewById(R.id.btn_pause); btn_stop = (Button) findViewById(R.id.btn_stop); btn_start.setOnClickListener(this); btn_pause.setOnClickListener(this); btn_stop.setOnClickListener(this); //根据文件路径播放 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { videoView.setVideoPath(Environment.getExternalStorageDirectory() + "/lesson.mp4"); } //读取放在raw目录下的文件 //videoView.setVideoURI(Uri.parse("android.resource://com.jay.videoviewdemo/" + R.raw.lesson)); videoView.setMediaController(new MediaController(this)); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_start: videoView.start(); break; case R.id.btn_pause: videoView.pause(); break; case R.id.btn_stop: videoView.stopPlayback(); break; } }}