参考书籍:Android第一行代码(第二版).郭霖著
1、通知
Notification是Android中较有特色的功能,当应用程序希望向用户发出提示信息但不在前台运行时可使用(手机顶部显示通知图标,下拉后看到详细内容)。
(1)基本用法
可在活动(较少,一般进入后台才需要)、广播接收器、服务里创建。
步骤:
a、获取NotificationManager实例:
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//调用Context的getSystemService()方法,参数用于确定获取系统哪个服务
b、使用Builder构造器创建Notification对象。由于Android每个版本API不稳定,使用support库中的兼容API(support-4v中提供NotificationCompat类),保证程序在所有Android系统版本上正常工作:
Notification notification = new NotificationCompat.Builder(context).setContentTitle("This is content title").setContentText("This is content text").setWhen(System.currentTimeMillis()).setSmallIcon(R.drawable.small_icon).setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.large_icon)).build();//可在build()方法之前连缀任意多设置方法来创建丰富的Notification对象
上述调用的五个设置方法:指定通知的标题内容(下拉时可见)、指定通知的正文内容(下拉时可见)、指定通知被创建的时间(毫秒,下拉时显示在通知上)、设置通知小图标(只能用纯alpha甑图片,显示在系统状态栏上)、设置通知大图标(下拉时显示)。
c、调用NotificationManager的notify()方法显示通知。
manager.notify(1, notification);//第一个参数为id(保证为每个通知指定的都不同)
例:新建NotificationTest项目,修改布局文件,添加一个用于发送通知的按钮即可。修改主程序:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button sendNotice = (Button) findViewById(R.id.send_notice);
sendNotice.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
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.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.build();
manager.notify(1, notification);
break;
default:
break;
}
}
}
通知现在点击无反应。需使用PendingIntent(与Intent(更倾向于立即执行某个动作)类似,但更倾向于在某个合适的时机去执行某个动作)。它主要提供几个静态方法用于获取PendingIntent实例:getActivity()/getBroadcast()/getService()(参数相同:第一个是Context, 第二个一般用不到(传入0即可),第三个是Intent对象(可通过其构建PendingIntent的“意图”),第四个用于确定PendingIntent的行为(FLAG_ONE_SHOT/FLAG_NO_CREATE/FLAG_CANCLE_CURRENT/FLAG_UPDATE_CURRENT四种值可选,通常传入0))。
NotificationCompat.Builder还可再连缀一个SetContent()方法(参数正是PendingIntent对象)。
优化NotificationTest项目。需准备另一个活动NotificationActivity,布局文件notification_layout,修改其中代码:
<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>
修改MainActivity:
...
public void onClick(View v) {
switch (v.getId()){
case R.id.send_notice:
**Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);**
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())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
**.setContentIntent(pi)**
.build();
manager.notify(1, notification);
break;
default:
break;
}
}
...
重新运行程序,点击按钮,点击通知,就会看到NotificationActivity活动界面。
此时发现系统状态上的通知图标没消失(代码中没取消)。两种方法可解决:NotificationCompat.Builder连缀setAutoCancel()方法(设置为true),显式调用NotificationManager的cancel()方法。第二种方法如下,在NotificationActivity中修改:
public class NotificationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.notification_layout);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(1);//参数为通知的id
}
}
(2)进阶技巧
NotificationCompat.Builder中提供了丰富的API。常用的:
setSound():在通知发出时播放一段音频。每个手机的/system/media/audio/ringtones目录下都有很多音频文件。例:
Notification notification = new NotificationCompat.Builder(this)
...
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
.build();
setVibrate:让手机振动,接收长整型数组(设置手机静止和振动时长,毫秒,下标为0的值表静止时长,下标为1的表振动时长,下标为2表静止,依次交替)。例:
Notification notification = new NotificationCompat.Builder(this)
...
.setVibrate(new long[]{0, 1000, 1000, 1000})//通知来到时立即振动1秒,后静止1秒,再振动1秒
.build();
控制振动需声明权限:
<uses-permission android:name="android.permission.VIBRATE"/>
setLights:控制手机前置的LED灯。接收三个参数:指定LED灯颜色,指定亮起时长(毫秒),指定暗去时长(毫秒)。例:
Notification notification = new NotificationCompat.Builder(this)
...
.setLights(Color.GREEN, 1000, 1000)
.build();
以上方法具体参数需根据手机做适当修改。
也可直接使用通知的默认效果setDefaults。例:
Notification notification = new NotificationCompat.Builder(this)
...
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();
(3)高级功能
NotificationCompat.Builder中更强大的API方法。
setStyle():允许构建富文本的通知内容(除文字和图标外,还有更多元素,如长文字、图片等)。接收NotificationCompat.Style()参数,例:
Notification notification = new NotificationCompat.Builder(this)
...
.seSyle(new NotificationCompat.BigTextStyle().bigTest("Learn how to buid notifications, send and sync data, and use voice actions.Get the official Android IDE and developer tools to build apps for Android."))
.build();
还可显示一张大图:
Notification notification = new NotificationCompat.Builder(this)
...
.seSyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.big_image)))
.build();
setPriority():设置通知的重要程度。5个常量值可选:PRIORITY_DEFAULT(默认,跟不设置一样)、PRIORITY_MIN(最低,可能只会在特定场景显示,如下拉状态栏时)、PRIORITY_LOW(较低,可能会缩小或改变显示顺序在更重要通知之后)、PRIORITY_HIGH(较高,能能会放大或排在比较靠前位置)、PRIORITY_MAX(最高,必须让用户立即看到并做出响应)。例:
Notification notification = new NotificationCompat.Builder(this)
...
.sePriority(NotificationCompat.PRIORITY_MAX)
.build();
运行效果如下:
2、摄像头和相册
(1)调用摄像头拍照
在应用程序里调用手机摄像头进行拍照。新建CameraAlbumTest项目,修改布局文件:
<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="@+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>
一个用于拍照,一个用于显示。修改MainActivity:
public class MainActivity extends AppCompatActivity {
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//创建File对象,用于存储拍照后的图片,
// 存放在手机SD卡的应用关联缓存目录(SD卡中专门用于存放当前应用缓存数据的位置)下,getExternalCacheDir()方法可得到
//具体路径是/sdcard/Android/data//cache
//Android6.0开始,读写SD卡被列为危险权限,如果存放在SD卡其他目录需进行运行时权限处理,使用此目录则不用
File outputImage = new File(getExternalCacheDir(), "output_image.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT >= 24) {//如果运行设备的系统版本等于或高于Android7.0
imageUri = FileProvider.getUriForFile(MainActivity.this, "com.example.jojo.cameraalbumtest.fileprovider", outputImage);
//将File对象转换成一个封装过的Uri对象。三个参数:Context对象,任意唯一字符串,File对象
//Android7.0后,直接访问本地真实路径的Uri被认为不安全,会抛出异常,
// 而FileProvider是一种特殊的内容提供器(使用和内容提供器类似的机制保护数据),可选择性地将封装过的Uri共享给外部,提高应用安全
} else {
//如果运行设备的系统版本低于Android7.0,此Uri对象标识着output_image.jpg图片的本地真实路径
imageUri = Uri.fromFile(outputImage);
}
//启动相机程序
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//指定图片输出地址为刚刚得到的Uri对象
startActivityForResult(intent, TAKE_PHOTO);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case TAKE_PHOTO:
if (resultCode == RESULT_OK){
try {
//将照片显示出来
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
还需注册内容提供器:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.jojo.cameraalbumtest">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
activity>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<provider
android:authorities="com.example.jojo.cameraalbumtest.fileprovider"
android:name="android.support.v4.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
provider>
application>
manifest>
在xml文件夹下创建file_paths.xml文件:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path=""/>
paths>
将程序运行到手机上,点击按钮进行拍照,完成后返回。
(2)从相册中选择照片
编辑activity_main.xml文件,添加一个选择照片的按钮,修改主程序:
public class MainActivity extends AppCompatActivity {
...
public static final int CHOOSE_PHOTO = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
...
Button chooseFromAlbum = (Button)findViewById(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){
//相册中的照片都是存储在SD卡上的,需要申请运行时权限,WRITE_EXTERNAL_STORAGE是危险权限,表示同时授予程序对SD卡的读和写的能力
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_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 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
openAlbum();
}else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_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及以上系统使用此方法处理图片(此版本开始,选取相册中的图片不再返回图片的真实Uri,而是封装过的Uri)
handleImageOnKitKat(data);
}else {
//4.4以下系统使用此方法处理图片
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void handleImageOnKitKat(Intent data){
String imagePath = null;
Uri uri = data.getData();
if (DocumentsContract.isDocumentUri(this, uri)){
//如果是document类型的Uri,则通过document id处理
String docId = DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())){
//如果Uri的authority是media格式的话,id还需再进行一次解析
String id = docId.split(":")[1];//字符串分割解析出数字格式的id(字符串后半部分才是真正的数字id)
String selection = MediaStore.Images.Media._ID + "=" + id;//构建新的Uri和条件语句
imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection);//获得图片真实路径
}else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(docId));
imagePath = getImagePath(contentUri, null);
}else if ("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的Uri,则使用普通方式处理
imagePath = getImagePath(uri, null);
}else if ("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的Uri,直接获取图片路径即可
imagePath = uri.getPath();
}
displayImage(imagePath);//根据图片路径显示图片
}
}
private void handleImageBeforeKitKat(Intent data){
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
private String getImagePath(Uri uri, String selection){
String path = null;
//通过Uri和selection来获取真实的图片路径
Cursor cursor = getContentResolver().query(uri, null, selection,null,null);
if (cursor != null){
if (cursor.moveToFirst()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath){
if (imagePath != null){
Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
}else {
Toast.makeText(this, "failed to get image",Toast.LENGTH_SHORT).show();
}
}
将程序重新运行,授予权限后,随意选择一张照片回到程序界面。(手机如没有SD卡,选择图片后返回没任何图片显示,也没Toast弹出,这里是模拟器的效果)
3、播放多媒体文件
(1)音频
工作流程:创建MediaPlayer对象;调用setDataSource()设置音频文件路径;
调用prepare()进入到准备状态;调用start()开始播放音频;调用pause()暂停播放;
调用reset()停止播放。
新建PlayAudioTest项目,修改布局文件:
"http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
修改MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private MediaPlayer mediaPlayer = new MediaPlayer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button play = (Button) findViewById(R.id.play);
Button pause = (Button) findViewById(R.id.pause);
Button stop = (Button) findViewById(R.id.stop);
play.setOnClickListener(this);
pause.setOnClickListener(this);
stop.setOnClickListener(this);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initMediaPlayer();
}
}
private void initMediaPlayer(){
try {
File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");//SD下,音乐文件夹下的音乐
mediaPlayer.setDataSource(file.getPath());//指定音频文件路径
mediaPlayer.prepare();//让MediaPlayer进入准备状态
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initMediaPlayer();
}else {
Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
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;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null){
mediaPlayer.stop();
mediaPlayer.release();//释放资源
}
}
}
还需声明权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
在手机上运行程序,正常。
(2)视频
主要使用VideoView类实现,其将视频的显示和控制集于一身。常用方法如下:
新建PlayVideoTest项目,修改布局文件:
<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="@+id/play"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Play"/>
<Button
android:id="@+id/pause"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Pause"/>
<Button
android:id="@+id/replay"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Replay"/>
LinearLayout>
<VideoView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
LinearLayout>
修改MainActivity:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoView = (VideoView)findViewById(R.id.video_view);
Button play = (Button) findViewById(R.id.play);
Button pause = (Button) findViewById(R.id.pause);
Button replay = (Button) findViewById(R.id.replay);
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initVideoPath();
}
}
private void initVideoPath(){
File file = new File(Environment.getExternalStorageDirectory(), "/movie.mp4");
videoView.setVideoPath(file.getPath());//指定视频文件路径
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initVideoPath();
}else {
Toast.makeText(this, "拒绝权限将无法使用程序", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
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;
default:
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (videoView != null){
videoView.suspend();
}
}
}
同样声明权限。在手机上运行程序。
VideoView只是做了很好的封装,背后仍是使用MediaPlayer对视频文件进行控制的。但VideoView在视频格式的支持及播放效率方面有较大不足,只用于播放一些游戏片头动画或视频宣传还是够的。