使用通知
通知(Notification
)是Android
系統中比較有特色的一個功能,當某個應用程序希望向用戶發出一些提示信息,而該應用程序又不在前臺運行時,就可以借助通知來實現。發出一條通知后,手機最上方的狀態欄中會顯示一個通知的圖標,下拉狀態欄后可以看到通知的詳細內容。
通知的基本用法
通知的用法還是比較靈活的,既可以在活動里創建,也可以在廣播接收器里創建,當然還可以在下一章中我們即將學習的服務里創建。相比于廣播接收器和服務,在活動里創建通知的場景還是比較少的,因為一般只有當程序進入到后臺的時候我們才需要使用通知。
首先需要一個NotificationManager來對通知進行管理,可以調用Context的getSystemService()方法獲取到。getSystemService()方法接收一個字符串參數用于確定獲取系統的哪個服務,這里我們傳入Context.NOTIFICATION_SERVICE即可。
獲取NotificationManager的實例就可以寫成:
NotificationManager manager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
接下來使用一個Builder構造器來創建Notification對象,但問題是,幾乎Android系統的每一個版本都會對通知這部分功能進行或多或少的修改,API不穩定性問題在通知上面凸顯的尤其嚴重。
support-v4庫中提供了一個NotificationCompat類,使用這個類的構造器來創建Notification對象,就可以保證我們的程序在所有Android系統版本上都能正常工作了。
Notification notification = new NotificationCompat.Builder(MainActivity.this)
.build();
上訴代碼只是創建了一個空的Notification對象,并沒有什么實際作用,我們可以在最終的build()方法之前連綴任意多的設置方法來創建一個豐富的Notification對象。
Notification notification = new NotificationCompat.Builder(MainActivity.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();
一共調用了5個設置方法
setContentTitle()方法用于指定通知的標題內容,下拉系統狀態欄就可以看到這部分內容。
setContentText()方法用于指定通知的標題內容,同樣下拉系統狀態欄就可以看到這部分內容。
setWhen()方法用于指定通知被創建的時間,以毫秒為單位,當下拉系統狀態欄時,這里指定的時間會顯示在相應的通知上。
setSmallIcon()方法用于設置通知的小圖標,注意只能使用純alpha圖層的圖片進行設置,小圖標會顯示在系統狀態欄上。
setLargeIcon()方法用于設置通知的大圖標,當下拉系統狀態欄時,就可以看到設置的大圖標了。
只需要調用NotificationManager的notify方法就可以讓通知顯示出來了。notify()方法接收兩個參數,第一個參數是id,要保證為每個通知所指定的id都是不同的。第二個參數則是Notification對象,這里直接將我們剛剛創建好的Notification對象傳入即可。
manager.notify(1,notification);
程序如下:
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.send_notice:
NotificationManager manager = (NotificationManager)
getSystemService(Context.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;
}
}
Notification的點擊
想實現通知的點擊效果,這就涉及了一個新的概念:PendingIntent
PendingIntent從名字上看起來就和Intent有些類似,它們之間也確實存在不少共同點。比如它們都可以去指明某一個意圖,都可以用于啟動活動,啟動服務以及發送廣播等。不同的是,Intent更加傾向于去立即執行某個動作,而PendingIntent更加傾向于在某個合適的時機去執行某個動作。所以,也可以把PendingIntent簡單里理解為延遲執行的Intent。
PendingIntent的用法同樣很簡單,他主要提供了幾個靜態方法用于獲取PendingIntent的實例,可以根據需求來選擇是使用getActivity()方法,getBroadcast()方法,還是getService()方法。這幾個方法接受的參數都是相同的,第一個參數依舊是Context。第二個參數一般用不到,通常傳入0即可。第三個參數是一個Intent對象,我們可以通過這個對象構建出PendingIntent的“意圖”。第四個參數用于確定PendingIntent的行為,有FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT這四種值可選,通常情況下傳入0就可以了。
NotificationCompat.Builder。這個構造器還可以在連綴一個setContentIntent()方法,接收的參數正是一個PendingIntent對象。
switch (v.getId())
{
case R.id.send_notice:
Intent intent = new Intent(this,NotificationActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
NotificationManager manager = (NotificationManager)
getSystemService(Context.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(pendingIntent)
.build();
manager.notify(1,notification);
break;
default:
break;
}
先是使用Intent表達出我們想要啟動NotificationActivity的“意圖”,然后將構建好的Intent對象傳入到PendingIntent的getActivity()方法里,以得到PendingIntent的實例,接著在NotificationCompat.Builder中調用setContentIntent()方法,把它作為參數傳入即可。
如果我們沒有在代碼中對該通知進行取消,它就會一直顯示在狀態欄上。解決的方法有兩種,一種是在NotificationCompat.Builder中在連綴一個setAutoCancel()方法,一種是顯式的調用NotificationManager的cancel()方法將它取消。
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build();
setAutoCancel()方法傳入true,就表示當點擊了這個通知的時候,通知會自動取消掉。
public class NotificationActivity extends AppCompatActivity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
NotificationManager manager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
manager.cancel(1);
}
}
如果你想取消哪條通知,在cancel()方法中傳入該通知的id就行了。
通知的進階技巧
實際上,NotificationCompat.Builder中提供了非常豐富的API來讓我們創建出更加多樣的通知效果。我們只能從中選一些比較常用的API來進行學習。先來看看setSound()方法,它可以在通知發出的時候播放一段音頻,這樣就能夠更好地告知用戶有通知到來。setSound()方法接收一個Uri參數,所以在指定音頻文件的時候還需要先獲取到音頻文件對應的URI。每個手機的/system/media/audio/ringtones目錄下都有很多的音頻文件,我們可以從中隨便選一個音頻文件。
//播放鈴聲
.setSound(Uri.fromFile(new File("/system/media/audio/ringtones/BirdLoop.ogg")))
除了允許播放音頻外,我們還可以在通知到來的時候讓手機進行震動。使用的是vibrate這個屬性。它是一個長整型的數組,用于設置手機靜止和震動的時長,以毫秒為單位。下標為0的值表示手機靜止的時長,下標為1的值表示手機震動的時長,下標為2的值又表示手機靜止的時長,以此類推。
<uses-permission android:name="android.permission.VIBRATE"/>
//震動
.setVibrate(new long[]{0,2000,1000,2000})
想要控制手機震動還需要聲明權限。
現在的手機基本上都會前置一個LED燈,當有未接電話或未讀短信,而此時手機又處于鎖屏狀態時,LED燈就會不停地閃爍,提醒用戶去查看。我們可以使用setLights()方法接收3個參數
第一個參數用于指定LED燈的顏色,第二個參數用于指定LED燈亮起的時長,以毫秒為單位,第三個參數用于指定LED燈暗去的時長,也是以毫秒為單位。
//LED燈顯示
.setLights(Color.RED,2000,1000)
如果你不想進行那么多繁雜的設置,也可以直接使用通知的默認效果,它會根據當前手機的環境來決定播放什么鈴聲,以及如何震動。
//默認的鈴聲和震動
.setDefaults(NotificationCompat.DEFAULT_ALL)
通知的高級功能
先來看看setStyle()方法,這個方法允許我們我們構建出富文本的通知內容。也就是說通知中不光可以有文字和圖標,還可以包含更多的東西。setStyle()方法接收一個NotificationCompat.Style參數,這個參數就是用來構建具體的富文本信息的,如長文字,圖片等。
.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."))
我們在setStyle()方法中創建了一個NotificationCompat.BigTextStyle對象,這個對象就是用于封裝長文字信息的,我們調用它的bigText()方法并將文字內容傳入就可以了。
除了顯示長文字之外,通知里還可以顯示一張大圖片:
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory
.decodeResource(getResources(),R.drawable.w)))
調用的setStyle()方法,這次我們在參數中創建了一個NotificationCompat.BigPictureStyle對象,這個對象就是用于放置大圖片的,然后調用它的bigPicture()方法并將圖片傳入。通過BitmapFactory的decodeResource()方法將圖片解析成Bitmap對象,再傳入到bigPicture()方法中就可以了。
再學習一下setPriority()方法,它可以用于設置通知的重要程度。setPriority()方法接收一個整形參數用于設置這條通知的重要程度。
一共有5個常量值可選:
PRIORITY_DEFAULT:表示默認的重要程度,和不設置效果是不一樣的。
PRIORITY_MIN:表示最低的的重要程度,系統可能只會在特定的場景才顯示這條通知,比如用戶下拉狀態欄的時候。
PRIORITY_LOW:表示較低的重要程度,系統可能會將這類通知縮小或改變其顯示的順序,排在更重要的通知之后。
PRIORITY_HIGH:表示較高的重要程度,系統可能會將這類通知放大,或改變其顯示的順序,將其排在比較靠前的位置。
PRIORITY_MAX:表示最高的重要程度,這類通知消息必須要讓用戶立刻看到,甚至需要用戶做出相應操作。
.setPriority(NotificationCompat.PRIORITY_MAX)
這次的通知不是在系統狀態欄顯示一個小圖標了,而是彈出了一個橫幅,并附帶了通知的詳細內容,表示這是一條非常重要的通知。不管用戶現在是在玩游戲還是看電影,這條通知都會顯示在最上方,以此引起用戶的注意。
調用攝像頭和相冊
調用攝像頭拍照
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
public static final int TAKE_PHOTO = 1;
private ImageView picture;
private Uri imageUri;
private Button takePhoto;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
public void initView()
{
takePhoto = (Button) findViewById(R.id.take_photo);
picture = (ImageView) findViewById(R.id.picture);
takePhoto.setOnClickListener(this);
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.take_photo:
// 創建File對象,用于存儲拍照后的圖片
File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
try
{
if (outputImage.exists()) // 判斷outputImage目錄是否存在
{
outputImage.delete(); // 刪除outputImage目錄
}
outputImage.createNewFile(); // 創建一個新的目錄
} catch (Exception e)
{
e.printStackTrace();
}
//判斷系統版本是否大于Android7.0
if (Build.VERSION.SDK_INT >= 24)
{
imageUri = FileProvider.getUriForFile(this,
"com.example.cameraalbumtest.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);
break;
default:
break;
}
}
@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;
}
}
}
首先這里創建了一個File對象,用于存儲攝像頭拍下的照片,這里我們把圖片命名為output_image.jpg。并把它存放在手機SD卡的應用關聯目錄下,什么叫應用關聯目錄呢?就是指SD卡中專門用于存放當前應用緩存數據的位置,調用getExternalCacheDir()方法就可以得到這個目錄,具體的路徑是/sdcard/Android/data/<package name>/cache。那么為什么要使用應用關聯緩目錄來存放圖片呢?因為從Android6.0系統開始,讀寫SD卡被列為了危險權限,如果將圖片存放在SD卡的任何其他目錄,都要進行運行時權限處理才行,而使用應用關聯目錄則可以跳過這一步。
如果運行設備的系統版本低于Android7.0,就調用Uri的fromFile()方法將File對象轉換成Uri對象,這個Uri對象標識著output_image.jpg這張圖片的本地真實路徑。否則,就調用FileProvider的getUriForFile()方法將File對象轉換成一個封裝過的Uri對象。getUriForFile()方法接收3個參數,第一個參數要求傳入Context對象,第二個參數可以是任意唯一的字符串,第三個參數則是我們剛剛創建的File對象。之所以要進行這樣一層轉換,是因為從Android7.0系統開始,直接使用本地真實路徑的Uri被認為是不安全的,會拋出一個FileUriExposedException異常。而FileProvider則是一種特殊的內容提供器,它使用了和內容提供器類似的機制來對數據進行保護,可以選擇性的將封裝過的Uri共享給外部,從而提高了應用的安全性。
構建出來了一個Intent對象,并將這個Intent的action指定為android.media.action.IMAGE_CAPTURE,再調用Intent的putExtra()方法指定圖片的輸出地址,這里填入剛剛得到的Uri對象,最后調用startActivityForResult()方法來啟動活動。我么使用的是一個隱式Intent,系統會找出能夠響應這個Intent的活動去啟動,這樣照相機程序就會被打開,拍下的照片就會輸出到output_image.jpg中。
如果發現拍照成功,就可以調用BitmapFactory的decodeStream()方法將output_image.jpg這張照片解析成Bitmap對象,然后把它設置到ImageView中顯示出來。
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.cameraalbumtest.fileprovider"
android:enabled="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
android:name屬性的值是固定的
android:authorities:屬性的值必須要和剛才FileProvider.getUriForFile()方法中的第二個參數一致。另外這里還在<provider>標簽的內部使用<meta-data>來指定Uri的共享路徑,并引用了一個@xml\file_paths資源。
右擊res目錄---New--Directory,創建一個xml目錄,接著右擊xml目錄--New--File,創建一個file_paths.xml文件。
<?xml version = "1.0" encoding= "utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_image" path=""/>
</paths>
external-path就是用來指定Uri共享的,name屬性的值可以隨便填,path屬性的值表示共享的具體路徑。這里設置空值就表示將整個SD卡進行共享。
在Android4.4系統之前,訪問SD卡的應用關聯目錄也是要聲明權限的,從4.4系統開始不再需要權限聲明。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
從相冊中選擇照片
case R.id.choose_from_album:
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
{
opemAlbum();
}
break;
//打開相冊
private void opemAlbum()
{
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)
{
opemAlbum();
}
else
{
Toast.makeText(MainActivity.this, "You denied the permission",
Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
@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;
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK)
{
//判斷手機的系統版本號
if (Build.VERSION.SDK_INT >= 19)
{
// 4.4及以上系統使用這個方法處理圖片
handleImageOnKitKat(data);
}
else
{
// 4.4以下系統使用這個方法處理圖片
handleImageBeforekitKat(data);
}
}
break;
default:
break;
}
}
@TargetApi(19)
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()))
{
//解析出數字格式的id
String id = docId.split(":")[1];
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:" +
"http://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;
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(MainActivity.this, "fail to get image", Toast.LENGTH_SHORT).show();
}
}
}
我們現實進行了一個運行時權限處理,動態申請WRITE_EXTERNAL_STORAGE這個危險權限。因為相冊中的照片都是存儲在SD卡上的,我們要從SD卡中讀取照片就需要申請這個權限。WRITE_EXTERNAL_STORAGE表示同時授予程序對SD卡讀和寫的能力。
當用戶授權了權限申請之后會調用openAlbum()方法,這里我們先是構建出了一個Intent對象,并將它的action指定為android.intent.action.GET_CONTENT。接著給這個Intent對象設置一些必要的參數,然后調用StartActivityForResult()方法就可以打開相冊程序選擇照片了。
接下來的邏輯就比較復雜了,首先為了兼容新老版本的手機,我們做了一個判斷,如果是4.4及以上的手機就調用handleImageOnKitKat()方法來處理圖片,否則就調用handleImageBeforeKitKat()方法來處理圖片。之所以要這樣做,是因為Android系統從4.4版本開始,選取相冊中的照片不再返回照片的真實Uri了,而是一個封裝過的Uri,因此如果是4.4版本以上的手機就需要對這個Uri進行解析才行。
這里有好幾種判斷情況,如果返回的Uri是document類型的話,那就取出document id 進行處理,如果不是的話,那就是用普通的方式處理。另外,如果Uri的authority是media格式的話,document id還需要再進行一次解析,要通過字符串分割的方式取出后半部分才能得到真正的數字id。取出的id用于構建新的Uri和條件語句,然后把這些值作為參數傳入到getImagePath()方法當中,就可以獲取到圖片的真實路徑了。拿到圖片的路徑之后,再調用displayImage()方法將圖片顯示到界面上。
相比于handleImageOnKitKat()方法,handleImageBeforeKitKat()方法中的邏輯就要簡單的多了,因為他的Uri是沒有封裝過的,不需要任何解析,直接將Uri傳入到getImagePath()方法當中就能獲取到圖片的真實路徑了。最后同樣是調用displayImage()方法來讓圖片顯示到界面上。
不過目前我們的實現還不算完美,因為某些照片即使經過裁剪后體積仍然很大,直接加載到內存中有可能會導致內存崩潰。更好的做法是根據項目的需求先對照片進行適當的壓縮,然后在加載到內存中。
播放多媒體文件
播放音頻
在Android中播放音頻文件一般使用都是使用MediaPlayer類來實現的,它對多種格式的音頻文件提供了非常全面的控制方法,從而使得播放音樂的工作變得十分簡單。
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
private MediaPlayer mediaPlayer = new MediaPlayer();
private Button play,pause,stop;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
play = (Button) findViewById(R.id.play);
pause = (Button) findViewById(R.id.pause);
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();
}
}
public void initMediaPlayer()
{
try
{
//獲得外部存儲的第一層目錄,即根目錄
File file = new File(Environment.getExternalStorageDirectory(),"music.mp3");
mediaPlayer.setDataSource(file.getPath()); //指定音頻文件的路徑
mediaPlayer.prepare(); //讓MediaPlayer進入到準備狀態
} catch (IOException 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(MainActivity.this, "拒絕權限將無法實現程序",
Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
@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();
}
}
}
首先需要創建出一個MediaPlayer對象,然后調用setDataSource()方法來設置音頻文件的路徑,再調用prepare()方法使MediaPlayer進入到準備狀態,接下來調用start()方法就可以開始播放音頻,調用pause()方法就會暫停播放,調用reset()方法就會停止播放。
在類初始化的時候我們就先創建了一個MediaPlayer的實例,然后在onCreate()方法中進行了運行時權限處理,動態申請WRITE_EXTERNAL_STORAGE權限。這是由于待會我們會在SD卡中放置一個音頻文件,程序為了播放這個音頻文件必須擁有訪問SD卡的權限才行。注意:在onRequestPermissionsResult()方法中,如果用戶拒絕了權限申請,那么就調用finish()方法將程序直接關掉,因為如果沒有SD卡的訪問權限,我們這個程序將什么都干不了。
用戶同意授權之后,就會調用initMediaPlayer()方法為MediaPlayer對象進行初始化操作。在initMediaPlayer()方法中,首先是通過創建一個File對象來指定音頻文件的路徑,從這里可以看出,我們需要事先在SD卡的根目錄下放置一個名為music.mp3的音頻文件。后面依次調用了setDataSource()方法和prepare()方法,為MediaPlayer做好了播放前的準備。
當點擊Play按鈕時會判斷,如果當前MediaPlayer沒有正在播放音頻,則調用start()方法開始播放。
當點擊Pause按鈕時會判斷,如果當前MediaPlayer正在播放音頻,則調用pause方法暫停播放。
當點擊Stop按鈕時會判斷,如果當前MediaPlayer正在播放音頻,則調用reset()方法將MediaPlayer重置為剛剛創建的狀態。然后重新調用一遍initMediaPlayer()方法。
最后在onDestroy()方法中,我們還需要分別調用stop()方法和release()方法,將與MediaPlayer相關的資源釋放掉。
最后千萬不要忘記加權限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
播放視頻
播放視頻文件其實并不比播放音頻文件復雜,主要是使用VideoView類來實現的。這個類將視頻的顯示和控制集于一身,使得我們僅僅借助它就可以完成一個簡易的視頻播放器。
public class MainActivity extends AppCompatActivity implements View.OnClickListener
{
private VideoView videoView;
private Button play,pause,replay;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
videoView = (VideoView) findViewById(R.id.video_view);
play = (Button) findViewById(R.id.play);
pause = (Button) findViewById(R.id.pause);
replay = (Button) findViewById(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 String[]{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,
@NonNull String[] permissions,
@NonNull int[] grantResults)
{
switch (requestCode)
{
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager
.PERMISSION_GRANTED)
{
initVideoPath();
}
else
{
Toast.makeText(MainActivity.this, "拒絕權限將無法使用程序",
Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
@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();
}
}
}
首先在onCreate()方法中同樣進行了一個運行時權限處理。因為視頻文件將會放在SD卡上。當用戶同意授權了之后,就會調用initVideoPath()方法來設置視頻文件的路徑,這里我們需要事先在SD卡的根目錄下放置一個名為movie.mp4的視頻文件。
當點擊Play按鈕時會進行判斷,如果當前并沒有正在播放的視頻,則調用start()方法開始播放。當點擊Pause()按鈕時會判斷,如果當前視頻正在播放,則調用pause()按鈕暫停播放。當點擊Replay按鈕時會判斷,如果當前視頻正在播放,則調用resume()方法從頭播放視頻。
最后在onDestroy()方法中,我們還需要調用一下suspend()方法,將VideoView所占用的資源釋放掉。
仍然始終要記得聲明權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
VideoView只是幫我們做了一個很好的封裝而已,它的背后仍然是使用MediaPlayer來對視頻進行控制的。另外需要注意,VideoView并不是一個萬能的視頻播放工具類,它在視頻格式的支持以及播放效率方面都存在著較大的不足。所以如果想要僅僅使用VideoView就編寫出一個功能非常強大的視頻播放器是不太現實的。但是如果只是用于播放一些游戲的片頭動畫,或者某個應用的視頻宣傳,使用VideoView還是綽綽有余的。