第008天:多媒体功能的调用

        在过去,手机的功能都比较单调,仅仅就是用来打电话和发短信的。而如今,手机在我们的 生活中正扮演着越来越重要的角色,各种娱乐方式都可以在手机上进行。上班的路上太无聊,可 以戴着耳机听音乐。外出旅行的时候,可以在手机上看电影。无论走到哪里,遇到喜欢的事物都 可以随手拍下来。

        众多的娱乐方式少不了强大的多媒体功能的支持,而Android在这方面也做得非常出色。它提供了一系列的API,使得我们可以在程序中调用很多手机的多媒体资源,从而编写出更加丰富多彩的应用程序,本章我们就将对Android中一些常用的多媒体功能的使用技巧进行学习。

        前面的7章内容,我们一直都是使用模拟器来运行程序的,不过本章涉及的一些功能必须要在真正的Android手机上运行才看得到效果。因此,首先我们就来学习一下,如何使用Android手机来运行程序。

8.1将程序运行到手机上

        不必我多说,首先你需要拥有一部Android手机。现在Android手机早就不是什么稀罕物, 几乎已经是人手一部了,如果你还没有的话,赶紧去购买吧。

        想要将程序运行到手机上,我们需要先通过数据线把手机连接到电脑上。然后进入到设置-> 开发者选项界面,并在这个界面中勾选中USB调试选项,如图8.1所示。

         注意从Android 4.2系统开始,开发者选项默认是隐藏的,你需要先进入到“关于手机”界 面,然后对着最下面的版本号那一栏连续点击,就会让开发者选项显示出来。

        然后如果你使用的是Windows操作系统,还需要在电脑上安装手机的驱动。一般借助360 手机助手或豌豆荚等工具都可以快速地进行安装,安装完成后就可以看到手机已经连接到电脑上 了,如图8.2所示。

        现在观察Android Monitor,你会发现当前是有两个设备在线的,一个是我们一直使用的模拟 器,另外一个则是刚刚连接上的手机了,如图8.3所示。

        然后运行一下当前项目,这时不会直接将程序运行到模拟器或者手机上,而是会弹出一个对 话框让你进行选择,如图8.4所示。

         选中下面的LGE Nexus 5后点击0K,就会将程序运行到手机上了。

8.2使用通知

        通知(Notification)Android系统中比较有特色的一个功能,当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知来实现。发出一条通知后, 手机最上方的状态栏中会显示一个通知的图标,下拉状态栏后可以看到通知的详细内容。Android 的通知功能获得了大量用户的认可和喜爱,就连iOS系统也在5.0版本之后加入了类似的功能。

8.2.1通知的基本用法

        了解了通知的基本概念,下面我们就来看一下通知的使用方法吧。通知的用法还是比较灵活 的,既可以在活动里创建,也可以在广播接收器里创建,当然还可以在下一章中我们即将学习的 服务里创建。相比于广播接收器和服务,在活动里创建通知的场景还是比较少的,因为一般只有 当程序进入到后台的时候我们才需要使用通知。

        不过,无论是在哪里创建通知,整体的步骤都是相同的,下面我们就来学习一下创建通知的 详细步骤。首先需要一个NotificationManager来对通知进行管理,可以调用ContextgetSystem- Service()方法获取到。getSystemServicef)方法接收一个字符串参数用于确定获取系统的哪 个服务,这里我们传入 Context. NOTIFICATION_SERVICE 即可。因此,获取 NotificationManager 的实例就可以写成"

NotificationManager manager = (NotificationManager) 
getSystemService(Context.NOTIFICATION_SERVICE);

        接下来需要使用一个Builder构造器来创建Notification对象,但问题在于,几乎Android 系统的每一个版本都会对通知这部分功能进行或多或少的修改,API不稳定性问题在通知上面突 显得尤其严重。那么该如何解决这个问题呢?其实解决方案我们之前已经见过好几回了,就是使 用support库中提供的兼容APIsupport*库中提供了一个NotificationCompat类,使用这个 类的构造器来创建Notification对象,就可以保证我们的程序在所有Android系统版本上都能 正常工作了,代码如下所示:

Notification notification = new NotificationCompat.Builder(context).build();

        当然,上述代码只是创建了一个空的Notification对象,并没有什么实际作用,我们可以 在最终的build ()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,先来 看一些最基本的设置:

Notification notification = new NotificationCompat.Builder(context)

.setContentTitle("This is content title")

.setContentText("This is content text")

.setWhen(System.currentTimeMillis())

.setSmallIcon(R.drawab'le. smallicon)

.setLargeIcon(BitmapFactory.decodeResource(getResources(),

R.drawable.largeicon))

.build();

        上述代码中一共调用了 5个设置方法,下面我们来一一解析一下0setContentTitle()方法 用于指定通知的标题内容,下拉系统状态栏就可以看到这部分内容。setContentText ()方法用 于指定通知的正文内容,同样下拉系统状态栏就可以看到这部分内容。setWhen()方法用于指定 通知被创建的时间,以毫秒为单位,当下拉系统状态栏时,这里指定的时间会显示在相应的通知 ±o setSmalllconO方法用于设置通知的小图标,注意只能使用纯alpha图层的图片进行设置, 小图标会显示在系统状态栏上。setLargeicon ()方法用于设置通知的大图标,当下拉系统状态 栏时,就可以看到设置的大图标了。

        以上工作都完成之后,只需要调用NotificationManagernotify()方法就可以让通知显示 出来了。notify()方法接收两个参数,第一个参数是id,要保证为每个通知所指定的id都是 不同的。第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification对 象传入即可。因此,显示一个通知就可以写成:

manager.notify(l, notification);

        到这里就已经把创建通知的每一个步骤都分析完了,下面就让我们通过一个具体的例子来看 一看通知到底是长什么样的。

        新建一个NotificationTest项目,并修改activity main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent" android:layout_height="match_parent">

    <Button

    android: id=,,(a+id/send_notice"

    android: layout_width=,,wrap_content" android:tayout_height="wrap_content"         
    android:text="Send notice" />

</LinearLayout>

        布局文件非常简单,里面只有一个Send notice按钮,用于发岀一条通知。接下来修改 MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

(QOverride

protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activity_main); Button sendNotice = (Button) findViewById(R.id.sendnotice); sendNotice.setOnClickListener(this);

}

@Override

public void onClick(View v) {

switch (v.getld()) {

case R.id.send notice:

NotificationManager manager = (NotificationManager) getSystemService (NOTIFICATION_SERVICE);

Notification notification = new NotificationCompat.Builder(this)

.setContentTitle("This is content title")

.setContentText("This is content text")

.setWhen(System.cu rrentTimeMillis())

.setSmallIcon(R.mipmap.iclauncher)

・ setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.iclauncher))

.buildO; ~

manager.notify(l, notification); break;

default:

break;

}

}

}

        可以看到,我们在Sendnotice按钮的点击事件里面完成了通知的创建工作,创建的过程正如 前面所描述的一样。不过这里简单起见,我将通知栏的大小图都直接设置成了 ic launcher这张图,

        这样就不用再去专门准备图标了,而在实际项目中千万不要这样偷懒。现在可以来运行一下程序了,点击Send notice按钮,你会在系统状态栏的最左边看到一个小 图标,如图8.5所示。

        下拉系统状态栏可以看到该通知的详细信息,如图8.6所示。

         如果你使用过Android手机,此时应该会下意识地认为这条通知是可以点击的。但是当你去 点击它的时候,你会发现没有任何效果。不对啊,好像每条通知点击之后都应该会有反应的呀? 其实要想实现通知的点击效果,我们还需要在代码中进行相应的设置,这就涉及了一个新的概念: Pendingintent 。Pendingintent从名字上看起来就和Intent有些类似,它们之间也确实存在着不少共同点。比 如它们都可以去指明某一个“意图”,都可以用于启动活动、启动服务以及发送广播等。不同的 是,Intent更加倾向于去立即执行某个动作,而Pendingintent更加倾向于在某个合适的时机去执 行某个动作。所以,也可以把Pendingintent简单地理解为延迟执行的Intent

        Pendingintent的用法同样很简单,它主要提供了几个静态方法用于获取Pendingintent的实例, 可以根据需求来选择是使用getActivity()方法、getBroadcast ()方法,还是getServiceO 方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context,不用多做解释。第二 个参数一般用不到,通常都是传入0即可。第三个参数是一个Intent对象,我们可以通过这个 对象构建出Pendingintent“意图”。第四个参数用于确定Pendingintent的行为,有FLAG_ONE_ SHOTFLAG_NO_CREATE. FLAG_CANCEL_CURRENT FLAG_UPDATE_CURRENT 4 种值可选,每 种值的具体含义你可以查看文档,通常情况下这个参数传入0就可以了。

        对Pendingintent有了一定的了解后,我们再回过头来看一下NotificationCompat. Builder这个构造器还可以再连缀一个setContentlntent ()方法,接收的参数正是一个 Pendingintent对象。因此,这里就可以通过Pendingintent构建出一个延迟执行的“意图”,当 用户点击这条通知时就会执行相应的逻辑。

        现在我们来优化一下NotificationTest项目,给刚才的通知加上点击功能,让用户点击它的时 候可以启动另一个活动。

        首先需要准备好另一个活动,右击 com.example.notificationtest ->New~^Activity—>Empty Activity,新建 NotificationActivity,布局起名为 notification_layout。然后修改 notification layout.xml 中的代码,如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"         
    android:layout_width="match_parent" android:layout_height="match_parent" >
    <TextView
    android:layout_width="wrap_content"
    android :layout_height=,,wrap_content"
    android:layout_centerInParent="true"
    android:textSize="24sp"
    android:text="This is notification layout"
    />
</RelativeLayout>

        这样就把NotificationActivity这个活动准备好了,下面我们修改MainActivity中的代码,给 通知加入点击功能,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

^Override

public void onClick(View v) {

switch (v.getld()) {

case R.id.sendnotice:

Intent intent = new Intent(thisf NotificationActivity.class); Pendingintent pi = Pendingintent.getActivity(thisf 6y intent, 6); NotificationManager manager = (NotificationManager) getSystemService (NOTIFICATION_SERVICE);

Notification notification = new NotificationCompat.Builder(this)

.setContentTitle("This is content title")

.setContentText("This is content text")

.setWhen(System.currentTimeMillis())

.setSmalHcon(R.mipmap. iclauncher)

.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.iclauncher))

.setcontentintent(pi)

.build();

manager.notify(1, notification);

break;

default:

break;

        这里先是使用Intent表达出我们想要启动NotificationActivity“意图”,然后将 构建好的Intent对象传入到PendingintentgetActivity()方法里,以得到Pendingintent的实 例,接着在 NotificationCompat.Builder 中调用 setcontentintent()方法,把它作为参数传入即可。

        现在重新运行一下程序,并点击Send notice按钮,依旧会发出一条通知。然后下拉系统状态 栏,点击一下该通知,就会看到NotificationActivity这个活动的界面了,如图8.7所示。

        

        咦?怎么系统状态上的通知图标还没有消失呢?是这样的,如果我们没有在代码中对该通知进 行取消,它就会一直显示在系统的状态栏上。解决的方法有两种,一种是在NotificationCompat. Builder中再连缀一个set/kutoCancel()方法,一种是显式地调用NotificationManagercancel()方法将它取消,两种方法我们都学习一下。

        第一种方法写法如下:

Notification notification = new NotificationCompat.Builder(this)

.setAutoCancel(true)

.buildO;

        可以看到,setAutoCancel()方法传入true,就表示当点击了这个通知的时候,通知会自 动取消掉。

        第二种方法写法如下:

public class NotificationActivity extends AppCompatActivity {

(QOverride

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState); setContentView(R.layout.notificationlayout);

NotificationManager manager = (NotificationManager) getSystemService

(NOTIFICATION_SERVICE); manager.cancel(1);

}

}

        这里我们在cancel ()方法中传入了 1,这个1是什么意思呢?还记得在创建通知的时候给 每条通知指定的id吗?当时我们给这条通知设置的id就是1。因此,如果你想取消哪条通知, 在cancel ()方法中传入该通知的id就行了。

8.2.2通知的进阶技巧

        现在你已经掌握了创建和取消通知的方法,并且知道了如何去响应通知的点击事件。不过通 知的用法并不仅仅是这些呢,下面我们就来探究一下通知的更多技巧。

        上一小节中创建的通知属于最基本的通知,实际上,NotificationCompat.Builder中提 供了非常丰富的API来让我们创建出更加多样的通知效果。当然,每一个API都详细地讲一遍不 太可能,我们只能从中选一些比较常用的API来进行学习。先来看看setSound()方法吧,它可 以在通知发出的时候播放一段音频,这样就能够更好地告知用户有通知到来。setSound ()方法 接收一个参数,所以在指定音频文件的时候还需要先获取到音频文件对应的URI比如说, 每个手机的/system/media/audio/ringtones目录下都有很多的音频文件,我们可以从中随便选一个 音频文件,那么在代码中就可以这样指定:

Notification notification = new NotificationCompat.Builder(this)

,setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna・ogg11))) .buildO;

        除了允许播放音频外,我们还可以在通知到来的时候让手机进行振动,使用的是vibrate 这个属性。它是一个长整型的数组,用于设置手机静止和振动的时长,以毫秒为单位。下标为0 的值表示手机静止的时长,下标为1的值表示手机振动的时长,下标为2的值又表示手机静止的 时长,以此类推。所以,如果想要让手机在通知到来的时候立刻振动1秒,然后静止1秒,再振 动1秒,代码就可以写成:

Notification notification = new NotificationCompat.Builder(this)

.setVibrate(new long[] {0, 1000, 1000, 1006 ))

.buildO;

        不过,想要控制手机振动还需要声明权限。因此,我们还得编辑AndroidManifest.xml文件, 加入如下声明:

​
<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.example.notificationtest"

    android :ve^sionCode="l,,

    android:versionName="1.0" >

    <uses-peemission android:name='*android.permission.VIBRATE" />

</manifest>

​

        学会了控制通知的声音和振动,下面我们来看一下如何在通知到来时控制手机LED灯的显。现在的手机基本上都会前置一个LED灯,当有未接电话或未读短信,而此时手机又处于锁 屏状态时,LED灯就会不停地闪烁,提醒用户去查看。我们可以使用setLightsO方法来实现 这种效果,setLightsO方法接收3个参数,第一个参数用于指定LED灯的颜色,第二个参数 用于指定LED灯亮起的时长,以毫秒为单位,第三个参数用于指定LED灯暗去的时长,也是以 毫秒为单位。所以,当通知到来时,如果想要实现LED灯以绿色的灯光一闪一闪的效果,就可 以写成:

Notification notification = new NotificationCompat.Builder(this)

.setLights(Color.GREEN, 1000, 1000)

.build();

        当然,如果你不想进行那么多繁杂的设置,也可以直接使用通知的默认效果,它会根据当前 手机的环境来决定播放什么铃声,以及如何振动,写法如下:

Notification notification = new NotificationCompat.Builder(this)

.setDefaults(NotificationCompat.DEFAULT_ALL)

.build();

        注意,以上所涉及的这些进阶技巧都要在手机上运行才能看得到效果,模拟器是无法表现出 振动以及LED灯闪烁等功能的。

8.2.3通知的高级功能

        继续观察NotificationCompat. Builder这个类,你会发现里面还有很多API是我们没有 使用过的。那么下面我们就来学习一些更加强大的API的用法,从而构建岀更加丰富的通知效果。

        先来看看setstyle()方法,这个方法允许我们构建出富文本的通知内容。也就是说通知中 不光可以有文字和图标,还可以包含更多的东西。setStyleO方法接收一个NotificationCompat. Style 参数,这个参数就是用来构建具体的富文本信息的,如长文字、图片等。

        在开始使用setStyleO方法之前,我们先来做一个试验吧,之前的通知内容都比较短,如 果设置成很长的文字会是什么效果呢?比如这样写:

Notification notification = new NotificationCompat.Builder(this)

.setContentText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android.")

.buildO;

        现在重新运行程序并触发通知,效果如图8.8所示。

%

8.8通知内容文字过长的效果

        可以看到,通知内容是无法显示完整的,多余的部分会用省略号来代替。其实这也很正常, 因为通知的内容本来就应该言简意赅,详细内容放到点击后打开的活动当中会更加合适。

        但是如果你真的非常需要在通知当中显示一段长文字,Android也是支持的,通过setStyleO 方法就可以做到,具体写法如下:

Notification notification = new NotificationCompat.Builder(this)

.setStyle(new NotificationCompat.BigTextStyle().bigText("Learn how to build notifications, send and sync data, and use voice actions. Get the official Android IDE and developer tools to build apps for Android.**))

.buildO;

        我们在 setStyle()方法中创建了一个 NotificationCompat.BigTextStyle 对象,这个对 象就是用于封装长文字信息的,我们调用它的bigTextO方法并将文字内容传入就可以了。

        再次重新运行程序并触发通知,效果如图8.9所示。

        除了显示长文字之外,通知里还可以显示一张大图片,具体用法也是基本相似的:

Notification notification = new NotificationCompat.Builder(this)

.setStyle(new NotificationCompat.BigPictureStyle().bigPicture

(BitmapFactory.decodeResource(getResources(), R.drawable・big_image))) ,buildf); "

        可以看到,这里仍然是调用的setstyle()方法,这次我们在参数中创建了一个 Notif icationCompat. BigPictureStyle对象,这个对象就是用于设置大图片的,然后调用它 的bigPictureO方法并将图片传入。这里我事先准备好了一张图片,通过BitmapFactorydecodeResource()方法将图片解析成Bitmap对象,再传入到bigPictureO方法中就可以了。

        现在重新运行一下程序并触发通知,效果如图8.10所示。这样我们就把setstyle ()方法中的重要内容基本都掌握了。

接下来再学习一下setPriority()方法,它可以用于设置通知的重要程度o setPriorityO

        方法接收一个整型参数用于设置这条通知的重要程度,一共有5个常量值可选:PRIORITY, DEFAULT表示默认的重要程度,和不设置效果是一样的;PRIORITY_MIN表示最低的重要程度, 系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;PRIORITY_LOW表示 较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在更重要的通知之 后;PRIORITY_HIGH表示较高的重要程度,系统可能会将这类通知放大,或改变其显示的顺序, 将其排在比较靠前的位置;PRIORITY_MAX表示最高的重要程度,这类通知消息必须要让用户立 刻看到,甚至需要用户做出响应操作。具体写法如下:

Notification notification = new NotificationCompat.Builder(this)

.setPriority(NotificationCompat.PRIORITY_MAX)

.build(); -

        这里我们将通知的重要程度设置成了最高,表示这是一条非常重要的通知,要求用户必须立 刻看到。现在重新运行一下程序,并点击Send notice按钮,效果如图8.11所示。

        可以看到,这次的通知不是在系统状态栏显示一个小图标了,而是弹出了一个横幅,并附 带了通知的详细内容,表示这是一条非常重要的通知。不管用户现在是在玩游戏还是看电影, 这条通知都会显示在最上方,以此引起用户的注意。当然,使用这类通知时一定要小心,确保你的通知内容的确是至关重要的,不然如果让用户产生反感的话,很可能会导致我们的应用程 序被卸载。

8.3调用摄像头和相册

我们平时在使用QQ或微信的时候经常要和别人分享图片,这些图片可以是用手机摄像头拍 的,也可以是从相册中选取的。类似这样的功能实在是太常见了,几乎在每一个应用程序中都会 有,那么本节我们就学习一下调用摄像头和相册方面的知识。

8.3.1调用摄像头拍照

        先来看看摄像头方面的知识,现在很多的应用都会要求用户上传一张图片来作为头像,这时 打开摄像头拍张照是最简单快捷的。下面就让我们通过一个例子来学习一下,如何才能在应用程 序里调用手机的摄像头进行拍照。

        新建一个CameraAlbumTest项目,然后修改activity main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     
    android:orientation="vertical" android :layout_width=,,match_parent"         
    android:layout_height="match_parent" >
    <Button
    android: id="(a4-id/take_photo"
    android: "layout_width="match_parent" android: layout_height=,,wrap_content"         
    android:text="Take Photo" />
    <ImageView
    android:id="@+id/picture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal" />
</LinearLayout>

        可以看到,布局文件中只有两个控件,一个Button和一个I mage View o Button是用于打开摄 像头进行拍照的,而ImageView则是用于将拍到的图片显示出来。

        然后开始编写调用摄像头的具体逻辑,修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity {

public static final int TAKE_PH0T0 = 1;

private ImageView picture;

private Uri imagedri;

(aOverride protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

Button takePhoto = (Button) findViewByld(R.id.takephoto); picture = (ImageView) findViewById(R.id.picture); takePhoto.setOnClickListener(new View.OnClickListener() { ^Override

public void onClick(View v) {

//创建File对象,用于存储拍照后的图片

File outputimage = new File(getExternalCacheDir()z "outputimage.jpg");

try (

if (outputimage.exists()) ( outputimage.delete();

}

outputimage.createNewFile();

) catch (lOException e) {

e.printStackT race();

}

if (Build.VERSION.SDKINT >= 24) {

imagedri = FileProvider.getUriForFile(MainActivity.this,

"com.example.cameraalbumtest.fileprovider", outputimage); } else {

imagedri = Uri.fromFile(outputlmage);

}

/ /启动相机程序

Intent intent = new Intent("android.media.action.IMAGECAPTURE"); intent.putExtra(MediaStore.EXTRA OUTPUT, imagedri); startActivityForResult(intent, TAKEPHOTO);

} _

});

@0verride

protected void onActivityResult(int requestcode, int resultcode, Intent data) { switch (requestcode) {

case TAKE_PH0T0:

if (resultcode == RESULT_OK) (

try {

//将拍摄的照片显示出来

Bitmap bitmap = BitmapFactory.decodeStream(getContent- Resolver().openlnputStream(imageUri));

picture.setlmageBitmap(bitmap);

} catch (FileNotFoundException e) {

e.printStackTrace();

}

}

break;

default:

break;

        上述代码稍微有点复杂,我们来仔细地分析一下。在MainActivity中要做的第一件事自然是 分另IJ获取到ButtonImageView的实例,并给Button注册上点击事件,然后在Button的点击事 件里开始处理调用摄像头的逻辑,我们重点看一下这部分代码。

        首先这里创建了一个File对象,用于存放摄像头拍下的图片,这里我们把图片命名为output. image.jpg,并将它存放在手机SD卡的应用关联缓存目录下。什么叫作应用关联缓存目录呢?就 是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()方法可以 得到这个目录,具体的路径是/sdcard/Android/data/<packagename>/cache0那么为什么要使用应用 关联缓目录来存放图片呢?因为从Android6.0系统开始,读写SD卡被列为了危险权限,如果将 图片存放在SD卡的任何其他目录,都要进行运行时权限处理才行,而使用应用关联目录则可以 跳过这一步。

        接着会进行一个判断,如果运行设备的系统版本低于Android7.0,就调用Urif romFilef) 方法将File对象转换成Uri对象,这个Uri对象标识着output_image.jpg这张图片的本地真实路 径。否则,就调用FileProvidergetUiForFile()方法将File对象转换成一个封装过的Uri 对象。getUriForFile()方法接收3个参数,第一个参数要求传入Context对象,第二个参数 可以是任意唯一的字符串,第三个参数则是我们刚刚创建的File对象。之所以要进行这样一层 转换,是因为从Android 7.0系统开始,直接使用本地真实路径的Uri被认为是不安全的,会抛出 一个FileUriExposedException异常。而FileProvider则是一种特殊的内容提供器,它使用了和内 容提供器类似的机制来对数据进行保护,可以选择性地将封装过的Uri共享给外部,从而提高了 应用的安全性。

        接下来构建出了一个Intent对象,并将这个Intentaction指定为android.media. action.IMAGE_CAPTURE,再调用IntentputExtraO方法指定图片的输出地址,这里填入 刚刚得到的Uri对象,最后调用startActivityForResult()来启动活动。由于我们使用的是一 个隐式Intent,系统会找出能够响应这个Intent的活动去启动,这样照相机程序就会被打开,拍 下的照片将会输出到output_image.jpg中。

        注意,刚才我们是使用startActivityForResult()来启动活动的,因此拍完照后会有结 果返回到onActivityResult()方法中。如果发现拍照成功,就可以调用BitmapFactorydecodeStream()方法将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到Imagelew 中显示出来。

        不过现在还没结束,刚才提到了内容提供器,那么我们自然要在AndroidManifest.xml^对内 容提供器进行注册了,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"     
    package="com.example.cameraalbumtest"> <application
    android:allowBackup="true"
    android: icon="(amipmap/ic_launcher"
    android:label="@string/app_name" android:supportsRtl="true" android :theme="    
    (astyle/AppTheme">
    <provider
    android: name=iaandroid ・ support. v4. content. FileProvider"
    android: authorities=l*com. example. cameraalbumtest. fileprovider"         
    android:exported="false" android:grantUriPermissions="true"> <meta-data
    android:name=Handroid.support.FILE_PROVIDER_PATHS“ android:resoui"ce=''@xml/file_paths”         
    />
    </provider>
    </application>
</manifest>

        其中,android:name属性的值是固定的,android:authorities属性的值必须要和刚才 FileProvider.getUriForFile()方法中的第二个参数一致。另外,这里还在<provider>标签 的内部使用vmetadata>来指定UnL的共享路径,并引用了一4"@xml/file_paths资源。当然, 这个资源现在还是不存在的,下面我们就来创建它。

        右击res目录->New—^Directory,创建一个xml目录,接着右击xml目录>New—>File,创 建一个file_paths.xml文件。然后修改file_paths.xml文件中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-path name="my_images" path="" />
</paths>

        其中,external-path就是用来指定Ui共享的,name属性的值可以随便填,path属性 的值表示共享的具体路径。这里设置空值就表示将整个SD卡进行共享,当然你也可以仅共享我 们存放output_image.jpg这张图片的路径。

        另外还有一点要注意,在Android4.4系统之前,访问SD卡的应用关联目录也是要声明权限 的,从4.4系统开始不再需要权限声明。那么我们为了能够兼容老版本系统的手机,还需要在 AndroidManifest.xml中声明一下访问SD卡的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.cameraalbumtest">

<uses-permission android:name=laandroid.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

        这样代码就都编写完了,现在将程序运行到手机上,然后点击Take Photo按钮就可以进行拍 照了,如图8.12所示。拍照完成后,点击中间按钮就会回到我们程序的界面。同时,拍摄的照片也会显示出来了,如图8.13所示。

8.3.2从相册中选择照片

        虽然调用摄像头拍照既方便又快捷,但我们并不是每次都需要去当场拍一张照片的。因为每 个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照片会比打开 相机拍一张照片更加常用。一个优秀的应用程序应该将这两种选择方式都提供给用户,由用户来 决定使用哪一种。下面我们就来看一下,如何才能实现从相册中选择照片的功能。

还是在CameraAlbumTest项目的基础上进行修改,编辑activity main.xml文件,在布局中添 加一个按钮用于从相册中选择照片,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <Button
    and roid: id=,,(a+id/take_photo"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" android:text="Take Photo" />
    <Button
        android:id="@+id/choose_from_album"
    android :layout_width="match__pa rent"
    and roid :layout_height=,,wrap_content,' android:text='*Choose From Album" />
    <ImageView
    android :id="(a+id/picture,'
    android:layout_width="wrap_content" android:layout_height="wrap_content"     android:layoutgravity="center_horizontal" />
</LinearLayout>

        然后修改MainActivity中的代码加入从相册选择照片的逻辑,代码如下所示:

public class MainActivity extends AppCompatActivity {

public static final int CHOOSE_PHOTO = 2;

^Override

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain); Button takePhoto = (Button) findViewByld(R.id.takephoto);

Button chooseFromAlbum = (Button) findViewByld(R.id.choose^from_album);

■ ・ ■

chooseFromAlbum.setOnClickListener(new View.OnClickListener() { ©Override public void onClick(View v) {

if (ContextCompat.checkSelfPermission(MainActivity. this,

Manifest.permission.WRITE_EXTERNAL__STORAGE) != PackageManager. PERMISSION.GRANTED) {

ActivityCompat.requestpermissions(MainActivity・this, new String!]( Manifest.permission. WRLTE_EXTERNAL_STORAGE }, 1); } else {

openAlbum();

});

}

private void openAlbum() {

Intent intent = new Intent("android.intent.action.GET_CONTENT"); intent.setType("image/*");

startActivityForResult (intent, CHOOSE__PHOTO); // 打开相册

} _

©Override

public void onRequestPermissionsResultfint requestcode, String!] permissions, int[] grantResults) { switch (requestcode) {

case 1:

if (grantResults.length > 6 && grantResults[0] == PackageManager. PERMISSION_GRANTED) { openAlbumO;

} else {

Toast.makeText(thisy "You denied the permission", Toast. LENGT七SHORT)・ show();

} _

break;



default:

}

}

©Override

protected void onActivityResult(int requestcode, int resultcode, Intent data) { switch (requestcode) {

case CHOOSE_PHOTO:

if (resultcode == RESULT_OK) {

//判断手机系统版本号 ~

if (Build.VERSION.SDK_INT >= 19) {

// 4.4及以上系统使甬这个方法处理图片 handlelmageOnKitKat(data);

} else {

// 4.4以下系统使用这个方法处理图片
handlelmageBeforeKitKat(data);

}

}

break;

default:

break;

@TargetApi(19)

private void handlelmageOnKitKat(Intent data) {

String imagePath = null;

Uri uri = data.getData();

if (DocumentsContract.isDocumentUri(this, uri)) {

//如果是document类型的Uri,则通过document id处理

String docld = DocumentsContract.getDocumentld(uri);

if("com.android.providers.media.documents".equals(uri・getAuthorityO)) { String id = docld.split[1]; // 解析出数字格式的 id String selection = MediaStore.Images.Media._ID + "=" + id; imagePath = getlmagePath(Mediastore.Images.Media.EXTERNAL^ CONTENT_URI, selection); ~

} else if (11 com. android. providers. downloads. documents**. equals (uri. getAuthorityO)) { Uri contentUri = ContentUris.withAppendedld(Uri.parse("content: //downloads/publ.ic_downl.oads"), Long. valueOf (docld));

imagePath = getlmagePath(contentUri, null);

}

} else if ("content".equalsIgnoreCase(uri.getScheme())) {

//如果是content类型的Uri,则使用普通方式处理

imagePath = getlmagePath(uri, null);

} else if ("file".equalsIgnoreCase(uri.getScheme())) {

//如果是file类型的U「i,直接获取图片路径即可

imagePath = uri.getPath();

} displaylmage(imagePath); //根据图片路径显示图片

private void handlelmageBeforeKitKat(Intent data) { Uri uri = data.getData();

String imagePath = getlmagePath(uri, null); displayimage(imagePath);

}

private String getlmagePath(Uri uri, String selection) {

String path = null;

//通过Uri和selection来获取真实的图片路径

Cursor cursor = getContentResolver().query(urif null, selection, null, null); if (cursor != null) { if (cursor.moveToFirst()) {

path = cursor.getString(cursor.getColumnlndex(MediaStore. Images.Media.DATA));

} cursor.close();

}

return path;

}

private void displayImage(St ring imagePath) { if (imagePath != null) {

Bitmap bitmap = BitmapFactory.decodeFile(imagePath); picture.setImageBitmap(bitmap);

} else {

Toast.makeText(this, "failed to get image"f Toast.LENGTH_SHORT).show();

} _

}

}

        可以看到,在Choose From Album按钮的点击事件里我们先是进行了一个运行时权限处理, 动态申请WRITE_EXTERNAL_STORAGE这个危险权限。为什么需要申请这个权限呢?因为相册中 的照片都是存储在SD卡上的,我们要从SD卡中读取照片就需要申请这个权限。WRITE_ EXTERNAL_STORAGE表示同时授予程序对SD卡读和写的能力。

        当用户授权了权限申请之后会调用openAlbumO方法,这里我们先是构建出了一个Intent 对象,并将它的 action 指定为 android. intent. action.GET_C0NTENTo 接着给这个 Intent 对象设置一些必要的参数,然后调用startActivityForResult()方法就可以打开相册程序选 择照片了。注意在调用startActivityForResult()方法的时候,我们给第二个参数传入的值 变成了 CH00SE_PH0T0,这样当从相册选择完图片回到onActivityResult ()方法时,就会进入 CH00SE_PH0T0case来处理图片。接下来的逻辑就比较复杂了,首先为了兼容新老版本的手 机,我们做了一个判断,如果是4.4及以上系统的手机就调用handlelmageOnKitKat ()方法来 处理鹵片,否则就调用handleImageBeforeKitKat()方法来处理图片。之所以要这样做,是因 为Android系统从4.4版本开始,选取相册中的图片不再返回图片真实的Uri 了,而是一个封装 过的Uri,因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。

        那么handlelmageOnKitKat()方法中的逻辑就基本是如何解析这个封装过的Uri 了。这里 有好几种判断情况,如果返回的Uridocument类型的话,那就取岀document id进行处理,
如果不是的话,那就使用普通的方式处理。另外,如果Uriauthoritymedia格式的话,document id还需要再进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字ido取 出的id用于构建新的Uri和条件语句,然后把这些值作为参数传入到getlmagePathO方法当中, 就可以获取到图片的真实路径了。拿到图片的路径之后,再调用displaylmageO方法将图片显 示到界面上。

        相比于 handlelmageOnKitKat ()方法,handlelmageBeforeKitKat ()方法中的逻辑就要 简单得多了,因为它的Uri是没有封装过的,不需要任何解析,直接将Uri传入到getlmagePathO 方法当中就能获取到图片的真实路径了,最后同样是调用displaylmageO方法来让图片显示到 界面上。

        现在将程序重新运行到手机上,然后点击一下Choose From Album按钮,首先会弹岀权限申 请框,如图8.14所示。

        点击允许之后就会打开手机相册,如图8.15所示。

        然后随意选择一张照片,回到我们程序的界面,选中的照片应该就会显示出来了,如图8.16 所示。

        调用摄像头拍照以及从相册中选择照片是很多Android应用都会带有的功能,现在你已经将 这两种技术都学会了,将来在工作中如果需要开发类似的功能,相信你一定能轻松完成的。不过 目前我们的实现还不算完美,因为某些照片即使经过裁剪后体积仍然很大,直接加载到内存中有 可能会导致程序崩溃。更好的做法是根据项目的需求先对照片进行适当的压缩,然后再加载到内 存中。至于如何对照片进行压缩,就要考验你查阅资料的能力了,这里就不再展开进行讲解了°

8.4播放多媒体文件

        手机上最常见的休闲方式毫无疑问就是听音乐和看电影了,随着移动设备的普及,越来越多 的人都可以随时享受优美的音乐,以及观看精彩的电影。而Android在播放音频和视频方面也是 做了相当不错的支持,它提供了一套较为完整的API,使得开发者可以很轻松地编写岀一个简易 的音频或视频播放器,下面我们就来具体地学习一下。

8.4.1播放音频

        在Android中播放音频文件一般都是使用MediaPlayer类来实现的,它对多种格式的音频 文件提供了非常全面的控制方法,从而使得播放音乐的工作变得十分简单。下表列出了 MediaPlayer类中一些较为常用的控制方法。

方法名

功能描述

setDataSource()

设置要播放的音频文件的位置

prepareO

在开始播放之前调用这个方法完成准备工作

start()

开始或继续播放音频

pauseO

暂停播放音频

resetO

MediaPlayer对象重置到刚刚创建的状态

seekToO

从指定的位置开始播放音频

stopO

停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频

release()

释放掉与MediaPlayer•对象相关的资源

isPlaying()

判断当前MediaPlayer是否正在播放音频

getDuration()

获取载入的音频文件的时长

        简单了解了上述方法后,我们再来梳理一下MediaPlayer的工作流程。首先需要创建岀一 个MediaPlayer对象,然后调用setDataSource()方法来设置音频文件的路径,再调用 prepareO方法使MediaPlayer进入到准备状态,接下来调用start()方法就可以开始播放音 频,调用pauseO方法就会暂停播放,调用resetO方法就会停止播放。

        下面就让我们通过一个具体的例子来学习一下吧,新建一个PlayAudioTest项目,然后修改 activity main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android :orientation=,,vertical" android:layout_width="match_parent"     
    android:layout_height="matchparent" >
    <Button
    android: id="(a+id/play"
    android:layout_width="match_pa rent"
    android:layout_height="wrap_content"
    android:text="Play" />
    <Button
    android:id="@+id/pause"
    android: layout_width=,,match_parent,'
    android:layout_height="wrap_content" android:text="Pause,' />
    <Button
    android:id="@+id/stop"
    android:layout_width="match_pa rent"
    android:layout_height="wrap_content" android:text="Stop" />
</LinearLayout>

        布局文件中放置了 3个按钮,分别用于对音频文件进行播放、暂停和停止操作。然后修改 MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private MediaPlayer mediaPlayer = new MediaPlayer();

(aOverride

protected void onCreate(Bundle savedlnstanceState) {

super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

Button play = (Button) findViewById(R.id.play);

Button pause = (Button) findViewById(R.id.pause);

Button stop = (Button) findViewById(R.id.stop);

play.setOndickListener(this);

pause.setOnClickListener(this);

stop.setOnClickListener(this);

if (ContextCompat.checkSelfPermission(MainActivity.thisf Manifest, permission. WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String!]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);

} else { initMediaPlayer(); // 初始化 MediaPlayer

}

private void initMediaPlayer() { try {

File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");

mediaPlayer. setDataSource(file.getPath()); // 指定音频文件的路径 mediaPlayer. prepare(); // 让 MediaPlayer 进入到准备状态

} catch (Exception e) {

e.printStackT race();

}

}

^Override

public void onRequestPermissionsResult(int requestcode, String!] permissions, int[] grantResults) {

switch (requestcode) {

case 1:

if (grantResults.length > 0 && grantResutts[0] == PackageManager. PERMISSION_GRANTED) ( initMediaPTayer();

} else {

Toast. makeText (this,"拒绝权限将无法使用程序",

Toast.LENGTH_SHORT).show();

finish();

)

break;

default:

}

}

©Override

public void onClick(View v) {

switch (v.getld()) {

case R.id.play:

if (!mediaPlayer.isPlaying()) { mediaPlayer. start(); // 开始播放

}

break;

case R.id.pause:

if (mediaPlayer.isPlaying()) { mediaPlayer. pause(); // 暂停播放

)

break;

case R.id.stop:

if (mediaPlayer.isPlaying()) { mediaPlayer. reset() ; // 停止播放 initMediaPlayer();

}

break;

default:

break;

}

}

(aOverride protected void onDestroy() ( super.onDestroy(); if (mediaPlayer != null) { mediaPlayer.stop(); mediaPlayer.release();

}

}

}

        可以看到,在类初始化的时候我们就先创建了一个MediaPlayer的实例,然后在onCreate() 方法中进行了运行时权限处理,动态申请WRITE_EXTERMAL_STORAGE权限。这是由于待会我们 会在SD卡中放置一个音频文件,程序为了播放这个音频文件必须拥有访问SD卡的权限才行。 注意,在onRequestPermissionsResult()方法中,如果用户拒绝了权限申请,那么就调用 finishO方法将程序直接关掉,因为如果没有SD卡的访问权限,我们这个程序将什么都干不了。

        用户同意授权之后就会调用initMediaPlayer()方法为MediaPlayer对象进行初始化操 作。在initMediaPlayer()方法中,首先是通过创建一个File对象来指定音频文件的路径,从 这里可以看出,我们需要事先在SD卡的根目录下放置一个名为music.mp3的音频文件。后面依 次调用了 setDataSource()方法和prepare()方法,为MediaPlayer•做好了播放前的准备。

        接下来我们看一下各个按钮的点击事件中的代码。当点击Play按钮时会进行判断,如果当 前MediaPlayer没有正在播放音频,则调用start ()方法开始播放。当点击Pause按钮时会判断, 如果当前MediaPlayer正在播放音频,则调用pause()方法暂停播放。当点击Stop按钮时会判断, 如果当前MediaPlayer正在播放音频,则调用reset ()方法将MediaPlayer重置为刚刚创建的状态, 然后重新调用一遍initMediaPlayerO方法。

        最后在onDestroy()方法中,我们还需要分别调用stop()方法和release()方法,将与 MediaPlayer相关的资源释放掉。

另外,千万不要忘记在AndroidManifest.xml文件中声明用到的权限,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.playaudiotest">
<uses-permission android:name="android.permission.WRITE_EXTERNAL__STORAGEH />
</manifest>

        这样一个简易版的音乐播放器就完成了,现在将程序运行到手机上会先弹出权限申请框,如 图8.17所示。

        同意授权之后就可以开始播放音乐了,点击一下Play按钮,优美的音乐就会响起,然后点 击Pause按钮,音乐就会停住,再次点击Play按钮,会接着暂停之前的位置继续播放。这时如果 点击一下Stop按钮,音乐也会停住,但是当再次点击Play按钮时,音乐就会从头开始播放了。

8.4.2播放视频

        播放视频文件其实并不比播放音频文件复杂,主要是使用VideoView类来实现的。这个类将 视频的显示和控制集于一身,使得我们仅仅借助它就可以完成一个简易的视频播放器。VideoView 的用法和MediaPlayer也比较类似,主要有以下常用方法:

方法名

功能描述

setVideoPath()

设置要播放的视频文件的位置

start()

开始或继续播放视频

pause()

暂停播放视频

resume()

将视频重头开始播放

seekToO

从指定的位置开始播放视频

isPlaying()

判断当前是否正在播放视频

getDuration()

获取载入的视频文件的时长

        那么我们还是通过一个实际的例子来学习一下吧,新建PlayVideoTest项目,然后修改 activity main.xml中的代码,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="match_parent"

android:layout_height=,,match_parent" >

<LinearLayout

android:layout_width="match_parent"

android:layout_height=,,wrap_content" >

<Button

android: id="(a+id/play"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="1" android:text="Play" />

<Button

android: id=,,@+id/pauseu

android:layout_width="0dp"

android: l.ayout_height=,,wrap_content"

android:layout_weight="l" android:text="Pause" />

<Button

android :id=*,(a+id/replay"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_weight="l" android:text="Replay" />

</LinearLayout>

<VideoView

android:id="@+id/video_view"

android:layout_width="match_parent" android:layout_height="wrap_content" />

</LinearLayout>

        在这个布局文件中,首先放置了 3个按钮,分别用于控制视频的播放、暂停和重新播放。然 后在按钮下面又放置了一个VideoView,稍后的视频就将在这里显示。

接下来修改MainActivity中的代码,如下所示:

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private VideoView videoView;

^Override protected void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.activitymain);

videoView = (VideoView) findViewByld(R.id.videoview); Button play = (Button) findViewById(R.id.play); Button pause = (Button) findViewByld(R.id.pause); Button replay = (Button) findViewByld(R.id.replay); play.setOnClickListener(this);

pause.setOnClickListener(this); replay.setOnClickListener(this); if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.

permission.WRITE_EXTERNAL_STORAGE) «= PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestpermissions(MainActivity.this, new St ring[]{ Manifest.permission. WRITE_EXTERNAL_STORAGE }, 1);

} else { initVideoPath(); // 初始化 MediaPlayer

}

}

private void initVideoPath() {

File file = new File(Environment.getExternalStorageDirectory(), "movie.mp4"); videoView. setVideoPath(file.getPath()); // 指定视频文件的路径

}

^Override

public void onRequestPermissionsResult(int requestcode, String!] permissions, int[] grantResults) { switch (requestcode) (

case 1

if (grantResults.length > 0 && grantResults[0] == PackageManager. PERMISSION GRANTED) {

initVideoPath();

) else {

Toast.makeTextfthis,"拒绝权限将无法使用程序,Toast.LENGTH_SHORT). show() ~

finish();

}

break;

default:

}

}

^Override

public void onClick(View v) {

switch (v.getldO) {

case R.id.play:

if (!videoView.isPlaying()) {

videoView, start () ; // 开始播放

)

break;

case R.id.pause:

if (videoView.isPlaying()) (

videoView. pause(); // 暂停播放

}

break;

case R.id.replay:

if (videoView.isPlaying()) { videoView. resume(); // 畫新播放

}

break;

}

}

(QOverride

protected void onDestroy() {

super.onDestroy();

if (videoView != null) {

videoView.suspend();

)

}

        这部分代码相信你理解起来会很轻松,因为它和前面播放音频的代码非常类似。首先在 onCreateO方法中同样进行了一个运行时权限处理,因为视频文件将会放在SD卡上。当用户同 意授权了之后就会调用initVideoPath()方法来设置视频文件的路径,这里我们需要事先在SD 卡的根目录下放置一个名为mo vie.mp4的视频文件。

        下面看一下各个按钮的点击事件中的代码。当点击Play按钮时会进行判断,如果当前并没 有正在播放视频,则调用start()方法开始播放。当点击Pause按钮时会判断,如果当前视频正 在播放,则调用pauseO方法暂停播放。当点击Replay按钮时会判断,如果当前视频正在播放, 则调用resume()方法从头播放视频。

最后在0nDestroyO方法中,我们还需要调用一下suspendO方法,将VideoView所占用 的资源释放掉。

另外,仍然始终要记得在AndroidManifest.xml文件中声明用到的权限,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.playvideotest">

<uses-permission android:name=l,android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

        现在将程序运行到手机上,会先弹出一个权限申请对话框,同意授权之后点击一下Play按 钮,就可以看到视频已经开始播放了,如图8.18所示。

<] o

8.18 Video View播放视频的效果

        点击Pause按钮可以暂停视频的播放,点击Replay按钮可以从头播放视频。

        这样的话,你就已经将VideoView的基本用法掌握得差不多了。不过,为什么它的用法和 MediaPlayer这么相似呢?其实VideoView R是帮我们做了一个很好的封装而已,它的背后仍然 是使用MediaPlayer来对视频文件进行控制的。另外需要注意,VideoView并不是一个万能的视 频播放工具类,它在视频格式的支持以及播放效率方面都存在着较大的不足。所以,如果想要仅 仅使用VideoView就编写出一个功能非常强大的视频播放器是不太现实的。但是如果只是用于播 放一些游戏的片头动画,或者某个应用的视频宣传,使用VideoView还是绰绰有余的。

        好了,关于Android多媒体方面的知识你已经学得足够多了,下面就让我们一起来总结一下 本章所学的内容吧。

8.5小结与点评

        本章我们主要对Android系统中的各种多媒体技术进行了学习,其中包括通知的使用技巧、 调用摄像头拍照、从相册中选取照片,以及播放音频和视频文件。由于所涉及的多媒体技术在模拟器上很难看得到效果,因此本章中还特意讲解了在Android手机上调试程序的方法。

        又是充实饱满的一章啊!现在多媒体方面的知识已经学得足够多了,我希望你可以很好地将 它们消化掉,尤其是与通知相关的内容,因为后面的学习当中还会用到它。目前我们所学的所有 东西都仅仅是在本地上进行的,而实际上几乎市场上的每个应用都会涉及网络交互的部分,所以 下一章中我们将会学习一下Android网络编程方面的内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值