第一行程式碼——第八章:豐富你的程式——運用手機多媒體
目錄:
知識點:
8.1 將程式執行到手機上
開啟手機開發者模式-USB 除錯 (有些手機還有通過USB安裝應用程式)
在設定中找不到開發者模式的話 需要點選系統版本 開啟開發者
若仍然在AS中找不到手機,可能驅動未安裝成功
本人解決方法:使用360手機助手 搞定的。
8.2 使用通知
8.2.1 通知的基本用法
通知的用法靈活,既可以在活動裡建立,也可在廣播接收器裡建立,還可在服務裡建立。
Intent intent = new Intent(this, OtherNoticeActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0); NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); // manager.cancel(1); 退出指定通知 //channelId 渠道id Notification notification = new NotificationCompat.Builder(this,"chat") .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)) .setAutoCancel(true) .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) //<uses-permission android:name="android.permission.VIBRATE" /> .setVibrate(new long[]{0, 1000, 1000, 1000})//設定靜止震動時長 震動1秒 靜止1秒 在震動1秒 .setLights(Color.GREEN, 1000, 1000)//燈光 // .setDefaults(NotificationCompat.DEFAULT_ALL)//根據當前情況來決定播放什麼鈴聲,以及如何震動 .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.mipmap.jelly_fish))) .setPriority(NotificationCompat.PRIORITY_HIGH)//顯示級別 橫幅 .setContentIntent(pendingIntent) // .setFullScreenIntent(pendingIntent,true)//響應緊急事件 比如來電 直接跳轉 .build(); manager.notify(1,notification);
8.2.2 通知的進階技巧
上面提到了通知的基本用法,接下來介紹一些通知的其他技巧,比如:
在通知發出時播放一段音訊,呼叫 setSound() 方法:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg"))) // 在音訊目錄下選個音訊檔案
.build();
在通知到來時讓手機振動,設定 vibrate 屬性:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setVibrate(new long[]{0,1000,1000,1000}) // 陣列下標0表靜止的時長,下標1表振動的時長,下標2表靜止的時長,以此類推
.build();
當然別忘了宣告振動許可權:
<uses-permission android:name="android.permission.VIBRATE" />
在通知到來時顯式手機 LED 燈,呼叫 setLights() 方法:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setLights(Color.GREEN,1000,1000) // 三個引數:LED 燈的顏色、燈亮時長、燈暗時長
.build();
當然也可直接使用預設效果,如下:
Notification notification = new NotificationCompat.Builder(this)
. . .
.setDefaults(NotificationCompat.DEFAULT_ALL)
.build();
8.2.3 通知的高階功能
NotificationCompat.Builder 類中的 setStyle() 方法
setStyle() 方法接收一個 NotificationCompat.style 引數,這個引數用來構造具體的富文字資訊,如長文字、圖片等。
https://segmentfault.com/a/1190000008241257
8.3 呼叫攝像頭和相簿
8.3.1 呼叫攝像頭拍照
8.3.2 從相簿中選擇照片
呼叫攝像頭和相簿 是非常常用的功能,在這裡,我們不僅實現了掉用攝像頭和相簿,同時也對於Android 7.0 系統進行了適配,主要的一點就是 獲取Uri 相關連的程式碼,在返回結果的時候也是需要我們對於Uri進行解析,請看程式碼:
package com.dak.administrator.firstcode.multi_media;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.Image;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Gallery;
import android.widget.ImageView;
import android.widget.Toast;
import com.dak.administrator.firstcode.R;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
public class CameraAlbumActivity extends AppCompatActivity {
private ImageView picture;
private static final int TAKE_PHOTO = 1;
private static final int CHOOSE_PHOTO = 2;
private Uri imageUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_album);
picture = findViewById(R.id.picture);
Button takePhoto = findViewById(R.id.take_photo);
takePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openCamera();
}
});
Button fromAblum = findViewById(R.id.choose_from_album);
fromAblum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(CameraAlbumActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(CameraAlbumActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
});
}
/**
* 開啟相機
*/
private void openCamera() {
//建立用處儲存拍照後的圖片
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) {
imageUri = FileProvider.getUriForFile(CameraAlbumActivity.this,
"com.dak.administrator.firstcode.fileprovider", outputImage);
} else {
imageUri = Uri.fromFile(outputImage);
}
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//輸出地址
startActivityForResult(intent, TAKE_PHOTO);
}
/**
* 開啟相簿
* gallery
*/
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) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openAlbum();
} else {
Toast.makeText(this, "請獲取許可權", Toast.LENGTH_SHORT).show();
}
break;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
try {
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
// 壓縮 迴圈 太慢
// ByteArrayOutputStream baos = new ByteArrayOutputStream();
// bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
// int options = 100;
// while (baos.toByteArray().length / 1024 > 100) {//迴圈判斷如果壓縮後圖片是否大於100kb,大於繼續壓縮
// baos.reset();//重置baos即清空baos
// bitmap.compress(Bitmap.CompressFormat.JPEG, options, baos);
// options -= 10;//每次都減少10
// }
// ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());//把壓縮後的資料baos存放到ByteArrayInputStream中
// Bitmap comBitmap = BitmapFactory.decodeStream(bais);
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= 19) {
//4.4及以上系統使用這個方法處理圖片
handleImageOnKitKat(data);
} else {
//4.4及以下系統使用這個方法處理圖片
handleImageBeforeKitKat(data);
}
}
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 將字串分割取出後半部分才能得到真正的數字id
String id = docId.split(":")[1];//解析出數字格式的id
String selection = MediaStore.Images.Media._ID + "=" + id;
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 String getImagePath(Uri externalContentUri, String selection) {
String path = null;
//通過Uri和selection來獲取真實圖片路徑
Cursor cursor = getContentResolver().query(externalContentUri, 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();
}
}
private void handleImageBeforeKitKat(Intent data) {
Uri uri = data.getData();
String imagePath = getImagePath(uri, null);
displayImage(imagePath);
}
}
Xml頁面:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.dak.administrator.firstcode.multi_media.CameraAlbumActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Take Photo"
android:id="@+id/take_photo"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="開啟相簿"
android:id="@+id/choose_from_album"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/picture"
/>
</LinearLayout>
在AndroidManifest.xml中配置Provider
<!-- authorities 和FileProvider.getUriForFile第二個引數一致 -->
<!-- name 使用自定義類 或者android.support.v4.content.FileProvider -->
<!-- grantUriPermissions 允許你有給其賦予臨時訪問許可權的權力 -->
<provider
android:name=".provider"
android:authorities="com.dak.administrator.firstcode.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<!-- 指定uri共享路徑 name 固定 -->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<paths>
<!--指定Uri共享 name 隨便填 path標識共享的具體路徑 空值代表整個sd卡 也可以僅共享 某張圖片路徑
path="images"
-->
<external-path
name="my_images"
path="" />
</paths>
</resources>
當然也別忘了宣告訪問SD卡的許可權:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
注意事項:在實際開發中最好根據專案的需求先對照片進行適當的壓縮,然後再載入到記憶體中。
8.4 播放多媒體檔案
在安卓中播放音訊檔案一般用 MediaPlayer 類來實現,播放視訊檔案主要用 VideoView 類來實現。
MediaPlayer 常用方法:
MediaPlayer | 構造方法 |
create | 建立一個要播放的多媒體 |
getCurrentPosition | 得到當前播放位置 |
getDuration | 得到檔案的時間 |
getVideoHeight | 得到視訊的高度 |
getVideoWidth | 得到視訊的寬度 |
isLooping | 是否迴圈播放 |
isPlaying | 是否正在播放 |
pause | 暫停 |
prepare | 準備(同步) |
prepareAsync | 準備(非同步) |
release | 釋放MediaPlayer物件相關的資源 |
reset | 重置MediaPlayer物件為剛剛建立的狀態 |
seekTo | 指定播放的位置(以毫秒為單位的時間) |
setAudioStreamType | 設定流媒體的型別 |
setDataSource | 設定多媒體資料來源(位置) |
setDisplay | 設定用SurfaceHolder來顯示多媒體 |
setLooping | 設定是否迴圈播放 |
setOnButteringUpdateListener | 網路流媒體的緩衝監聽 |
setOnErrorListener | 設定錯誤資訊監聽 |
setOnVideoSizeChangedListener | 視訊尺寸監聽 |
setScreenOnWhilePlaying | 設定是否使用SurfaceHolder來保持螢幕顯示 |
setVolume | 設定音量 |
start | 開始播放 |
stop | 停止播放 |
VideoView和MediaPlaer也比較類似,主要有以下常用方法:
setVideoPath() |
設定要播放的視訊檔案的位置。 |
start() |
開始或繼續播放視訊。 |
pause() |
暫停播放視訊。 |
resume() |
將視訊重頭開始播放。 |
seekTo() |
從指定的位置開始播放視訊。 |
isPlaying() |
判斷當前是否正在播放視訊。 |
getDuration() |
獲取載入的視訊檔案的時長。 |
8.4.1 播放音訊
package com.dak.administrator.firstcode.multi_media;
import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.dak.administrator.firstcode.R;
import java.io.File;
import java.io.IOException;
public class MediaActivity extends AppCompatActivity implements View.OnClickListener {
private MediaPlayer mediaPlayer = new MediaPlayer();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media);
Button play = findViewById(R.id.play);
Button pause = findViewById(R.id.pause);
Button stop = findViewById(R.id.stop);
play.setOnClickListener(this);
pause.setOnClickListener(this);
stop.setOnClickListener(this);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
initMediaPlayer();
}
}
private void initMediaPlayer() {
try {
File file = new File(Environment.getExternalStorageDirectory(), "music.mp3");//music.mp3不存在
mediaPlayer.setDataSource(file.getPath());
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initMediaPlayer();
}else {
Toast.makeText(this, "請獲取儲存許可權", Toast.LENGTH_SHORT).show();
finish();
}
}
@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;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
}
}
xml檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.dak.administrator.firstcode.multi_media.MediaActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/play"
android:text="play"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/pause"
android:text="pause"
/>
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="stop" />
</LinearLayout>
8.4.2 播放視訊
package com.dak.administrator.firstcode.multi_media;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.widget.VideoView;
import com.dak.administrator.firstcode.R;
import java.io.File;
public class VideoActivity extends AppCompatActivity implements View.OnClickListener {
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video);
Button play = findViewById(R.id.play);
Button pause = findViewById(R.id.pause);
Button replay = findViewById(R.id.replay);
videoView = findViewById(R.id.video_view);
play.setOnClickListener(this);
pause.setOnClickListener(this);
replay.setOnClickListener(this);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(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) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initVideoPath();
}else {
Toast.makeText(this, "請獲取儲存許可權", Toast.LENGTH_SHORT).show();
finish();
}
}
@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;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (videoView != null) {
videoView.suspend();//釋放資源
}
}
}
xml檔案:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<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:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/video_view"
/>
</LinearLayout>
8.5 小結與點評
郭霖總結:
本章我們主要對Android系統中的各種多媒體技術進行了學習,其中包括通知的使用校在模呼叫攝像頭拍照從相簿中選取照片, 以及播放香城和視訊檔案。由於所涉及的多媒體技術住快擬器上很難看得到效果,因此本章中還特意講解了在Android手機上除錯程式的方法。
又是充實飽滿的一章啊!現在多媒體方面的知識已經學得足夠多了,我希望你可以很好地將它們消化掉,尤其是與通知相關的內容,因為後面的學習當中還會用到它。目前我們所學的所有東西都僅僅是在本地上進行的,而實際上幾乎市場上的每個應用都會涉及網路互動的部分,所比下一章中我們將會學習一下Android網路程式設計方面的內容。
我的總結:
在呼叫攝像頭和相簿這一點,我也是學到了非常多的東西,平時因為用一些框架,很少回去研究這些東西,如今算是學到了這些我們必要的一些知識。
因為MediaPlayer 主要是在音樂相關的專案上用到,所以也多了一些印象,VideoView的話 他在視訊格式的支援以及播放效率方面都存在這較大的不足,所以建議我們只是用於播放一些遊戲的片頭動畫,或者某個應用的視訊宣傳等。。