8 豐富你的程序-運用手機多媒體

image.jpg

使用通知

通知(Notification)是Android系統(tǒng)中比較有特色的一個功能隘马,當某個應用程序希望向用戶發(fā)出一些提示信息舆吮,而該應用程序又不在前臺運行時红淡,就可以借助通知來實現(xiàn)外莲。發(fā)出一條通知后猪半,手機最上方的狀態(tài)欄中會顯示一個通知的圖標,下拉狀態(tài)欄后可以看到通知的詳細內容偷线。

通知的基本用法

通知的用法還是比較靈活的磨确,既可以在活動里創(chuàng)建,也可以在廣播接收器里創(chuàng)建声邦,當然還可以在下一章中我們即將學習的服務里創(chuàng)建乏奥。相比于廣播接收器和服務,在活動里創(chuàng)建通知的場景還是比較少的翔忽,因為一般只有當程序進入到后臺的時候我們才需要使用通知英融。

首先需要一個NotificationManager來對通知進行管理盏檐,可以調用Context的getSystemService()方法獲取到歇式。getSystemService()方法接收一個字符串參數(shù)用于確定獲取系統(tǒng)的哪個服務,這里我們傳入Context.NOTIFICATION_SERVICE即可胡野。

獲取NotificationManager的實例就可以寫成:

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

接下來使用一個Builder構造器來創(chuàng)建Notification對象材失,但問題是,幾乎Android系統(tǒng)的每一個版本都會對通知這部分功能進行或多或少的修改硫豆,API不穩(wěn)定性問題在通知上面凸顯的尤其嚴重龙巨。

support-v4庫中提供了一個NotificationCompat類,使用這個類的構造器來創(chuàng)建Notification對象熊响,就可以保證我們的程序在所有Android系統(tǒng)版本上都能正常工作了旨别。

Notification notification = new NotificationCompat.Builder(MainActivity.this)
                .build();

上訴代碼只是創(chuàng)建了一個空的Notification對象,并沒有什么實際作用汗茄,我們可以在最終的build()方法之前連綴任意多的設置方法來創(chuàng)建一個豐富的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()方法用于指定通知的標題內容,下拉系統(tǒng)狀態(tài)欄就可以看到這部分內容。

setContentText()方法用于指定通知的標題內容递览,同樣下拉系統(tǒng)狀態(tài)欄就可以看到這部分內容叼屠。

setWhen()方法用于指定通知被創(chuàng)建的時間,以毫秒為單位绞铃,當下拉系統(tǒng)狀態(tài)欄時镜雨,這里指定的時間會顯示在相應的通知上。

setSmallIcon()方法用于設置通知的小圖標儿捧,注意只能使用純alpha圖層的圖片進行設置荚坞,小圖標會顯示在系統(tǒng)狀態(tài)欄上。

setLargeIcon()方法用于設置通知的大圖標纯命,當下拉系統(tǒng)狀態(tài)欄時西剥,就可以看到設置的大圖標了。

只需要調用NotificationManager的notify方法就可以讓通知顯示出來了亿汞。notify()方法接收兩個參數(shù)瞭空,第一個參數(shù)是id,要保證為每個通知所指定的id都是不同的。第二個參數(shù)則是Notification對象疗我,這里直接將我們剛剛創(chuàng)建好的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的點擊

想實現(xiàn)通知的點擊效果,這就涉及了一個新的概念:PendingIntent

PendingIntent從名字上看起來就和Intent有些類似吴裤,它們之間也確實存在不少共同點旧找。比如它們都可以去指明某一個意圖,都可以用于啟動活動麦牺,啟動服務以及發(fā)送廣播等钮蛛。不同的是,Intent更加傾向于去立即執(zhí)行某個動作剖膳,而PendingIntent更加傾向于在某個合適的時機去執(zhí)行某個動作魏颓。所以,也可以把PendingIntent簡單里理解為延遲執(zhí)行的Intent吱晒。

PendingIntent的用法同樣很簡單甸饱,他主要提供了幾個靜態(tài)方法用于獲取PendingIntent的實例,可以根據(jù)需求來選擇是使用getActivity()方法仑濒,getBroadcast()方法叹话,還是getService()方法。這幾個方法接受的參數(shù)都是相同的墩瞳,第一個參數(shù)依舊是Context驼壶。第二個參數(shù)一般用不到,通常傳入0即可喉酌。第三個參數(shù)是一個Intent對象热凹,我們可以通過這個對象構建出PendingIntent的“意圖”箩溃。第四個參數(shù)用于確定PendingIntent的行為,有FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT這四種值可選碌嘀,通常情況下傳入0就可以了涣旨。

NotificationCompat.Builder。這個構造器還可以在連綴一個setContentIntent()方法股冗,接收的參數(shù)正是一個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()方法,把它作為參數(shù)傳入即可怯疤。

如果我們沒有在代碼中對該通知進行取消浆洗,它就會一直顯示在狀態(tài)欄上。解決的方法有兩種集峦,一種是在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來讓我們創(chuàng)建出更加多樣的通知效果。我們只能從中選一些比較常用的API來進行學習备恤。先來看看setSound()方法稿饰,它可以在通知發(fā)出的時候播放一段音頻,這樣就能夠更好地告知用戶有通知到來露泊。setSound()方法接收一個Uri參數(shù)喉镰,所以在指定音頻文件的時候還需要先獲取到音頻文件對應的URI。每個手機的/system/media/audio/ringtones目錄下都有很多的音頻文件滤淳,我們可以從中隨便選一個音頻文件梧喷。

//播放鈴聲
                        .setSound(Uri.fromFile(new File("/system/media/audio/ringtones/BirdLoop.ogg")))

除了允許播放音頻外砌左,我們還可以在通知到來的時候讓手機進行震動脖咐。使用的是vibrate這個屬性。它是一個長整型的數(shù)組汇歹,用于設置手機靜止和震動的時長屁擅,以毫秒為單位。下標為0的值表示手機靜止的時長产弹,下標為1的值表示手機震動的時長派歌,下標為2的值又表示手機靜止的時長弯囊,以此類推。

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

//震動
 .setVibrate(new long[]{0,2000,1000,2000})

想要控制手機震動還需要聲明權限胶果。

現(xiàn)在的手機基本上都會前置一個LED燈匾嘱,當有未接電話或未讀短信,而此時手機又處于鎖屏狀態(tài)時早抠,LED燈就會不停地閃爍霎烙,提醒用戶去查看。我們可以使用setLights()方法接收3個參數(shù)
第一個參數(shù)用于指定LED燈的顏色蕊连,第二個參數(shù)用于指定LED燈亮起的時長悬垃,以毫秒為單位,第三個參數(shù)用于指定LED燈暗去的時長甘苍,也是以毫秒為單位尝蠕。

//LED燈顯示
.setLights(Color.RED,2000,1000)

如果你不想進行那么多繁雜的設置,也可以直接使用通知的默認效果载庭,它會根據(jù)當前手機的環(huán)境來決定播放什么鈴聲看彼,以及如何震動。

//默認的鈴聲和震動
.setDefaults(NotificationCompat.DEFAULT_ALL)

通知的高級功能

先來看看setStyle()方法囚聚,這個方法允許我們我們構建出富文本的通知內容闲昭。也就是說通知中不光可以有文字和圖標,還可以包含更多的東西靡挥。setStyle()方法接收一個NotificationCompat.Style參數(shù)序矩,這個參數(shù)就是用來構建具體的富文本信息的,如長文字跋破,圖片等簸淀。

.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()方法中創(chuàng)建了一個NotificationCompat.BigTextStyle對象,這個對象就是用于封裝長文字信息的毒返,我們調用它的bigText()方法并將文字內容傳入就可以了租幕。

除了顯示長文字之外,通知里還可以顯示一張大圖片:

.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory
                                .decodeResource(getResources(),R.drawable.w)))

調用的setStyle()方法拧簸,這次我們在參數(shù)中創(chuàng)建了一個NotificationCompat.BigPictureStyle對象劲绪,這個對象就是用于放置大圖片的,然后調用它的bigPicture()方法并將圖片傳入盆赤。通過BitmapFactory的decodeResource()方法將圖片解析成Bitmap對象贾富,再傳入到bigPicture()方法中就可以了。

再學習一下setPriority()方法牺六,它可以用于設置通知的重要程度颤枪。setPriority()方法接收一個整形參數(shù)用于設置這條通知的重要程度。
一共有5個常量值可選:

PRIORITY_DEFAULT:表示默認的重要程度淑际,和不設置效果是不一樣的畏纲。

PRIORITY_MIN:表示最低的的重要程度扇住,系統(tǒng)可能只會在特定的場景才顯示這條通知,比如用戶下拉狀態(tài)欄的時候盗胀。

PRIORITY_LOW:表示較低的重要程度艘蹋,系統(tǒng)可能會將這類通知縮小或改變其顯示的順序,排在更重要的通知之后票灰。

PRIORITY_HIGH:表示較高的重要程度簿训,系統(tǒng)可能會將這類通知放大,或改變其顯示的順序米间,將其排在比較靠前的位置强品。

PRIORITY_MAX:表示最高的重要程度,這類通知消息必須要讓用戶立刻看到屈糊,甚至需要用戶做出相應操作的榛。

 .setPriority(NotificationCompat.PRIORITY_MAX)

這次的通知不是在系統(tǒng)狀態(tài)欄顯示一個小圖標了,而是彈出了一個橫幅逻锐,并附帶了通知的詳細內容夫晌,表示這是一條非常重要的通知。不管用戶現(xiàn)在是在玩游戲還是看電影昧诱,這條通知都會顯示在最上方晓淀,以此引起用戶的注意。

調用攝像頭和相冊

調用攝像頭拍照

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:
                // 創(chuàng)建File對象盏档,用于存儲拍照后的圖片
                File outputImage = new File(getExternalCacheDir(),"output_image.jpg");

                try
                {
                    if (outputImage.exists()) // 判斷outputImage目錄是否存在
                    {
                        outputImage.delete(); // 刪除outputImage目錄
                    }
                    outputImage.createNewFile();  // 創(chuàng)建一個新的目錄
                } catch (Exception e)
                {
                    e.printStackTrace();
                }
                
                //判斷系統(tǒng)版本是否大于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;
        }
    }
}

首先這里創(chuàng)建了一個File對象凶掰,用于存儲攝像頭拍下的照片,這里我們把圖片命名為output_image.jpg蜈亩。并把它存放在手機SD卡的應用關聯(lián)目錄下懦窘,什么叫應用關聯(lián)目錄呢?就是指SD卡中專門用于存放當前應用緩存數(shù)據(jù)的位置稚配,調用getExternalCacheDir()方法就可以得到這個目錄畅涂,具體的路徑是/sdcard/Android/data/<package name>/cache。那么為什么要使用應用關聯(lián)緩目錄來存放圖片呢道川?因為從Android6.0系統(tǒng)開始午衰,讀寫SD卡被列為了危險權限,如果將圖片存放在SD卡的任何其他目錄冒萄,都要進行運行時權限處理才行臊岸,而使用應用關聯(lián)目錄則可以跳過這一步。

如果運行設備的系統(tǒng)版本低于Android7.0宦言,就調用Uri的fromFile()方法將File對象轉換成Uri對象扇单,這個Uri對象標識著output_image.jpg這張圖片的本地真實路徑商模。否則奠旺,就調用FileProvider的getUriForFile()方法將File對象轉換成一個封裝過的Uri對象蜘澜。getUriForFile()方法接收3個參數(shù),第一個參數(shù)要求傳入Context對象响疚,第二個參數(shù)可以是任意唯一的字符串鄙信,第三個參數(shù)則是我們剛剛創(chuàng)建的File對象。之所以要進行這樣一層轉換忿晕,是因為從Android7.0系統(tǒng)開始装诡,直接使用本地真實路徑的Uri被認為是不安全的,會拋出一個FileUriExposedException異常践盼。而FileProvider則是一種特殊的內容提供器鸦采,它使用了和內容提供器類似的機制來對數(shù)據(jù)進行保護,可以選擇性的將封裝過的Uri共享給外部咕幻,從而提高了應用的安全性渔伯。

構建出來了一個Intent對象,并將這個Intent的action指定為android.media.action.IMAGE_CAPTURE,再調用Intent的putExtra()方法指定圖片的輸出地址肄程,這里填入剛剛得到的Uri對象锣吼,最后調用startActivityForResult()方法來啟動活動。我么使用的是一個隱式Intent,系統(tǒng)會找出能夠響應這個Intent的活動去啟動蓝厌,這樣照相機程序就會被打開玄叠,拍下的照片就會輸出到output_image.jpg中。

如果發(fā)現(xiàn)拍照成功拓提,就可以調用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()方法中的第二個參數(shù)一致代态。另外這里還在<provider>標簽的內部使用<meta-data>來指定Uri的共享路徑狐粱,并引用了一個@xml\file_paths資源。

右擊res目錄---New--Directory,創(chuàng)建一個xml目錄胆数,接著右擊xml目錄--New--File,創(chuàng)建一個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系統(tǒng)之前判莉,訪問SD卡的應用關聯(lián)目錄也是要聲明權限的豆挽,從4.4系統(tǒng)開始不再需要權限聲明。

<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)
                {
                    //判斷手機的系統(tǒng)版本號
                    if (Build.VERSION.SDK_INT >= 19)
                    {
                        // 4.4及以上系統(tǒng)使用這個方法處理圖片
                        handleImageOnKitKat(data);
                    }
                    else
                    {
                       // 4.4以下系統(tǒng)使用這個方法處理圖片
                        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()))
            {
                //解析出數(shù)字格式的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);  //根據(jù)圖片路徑顯示圖片
    }

    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();
        }
    }
}

我們現(xiàn)實進行了一個運行時權限處理帮哈,動態(tài)申請WRITE_EXTERNAL_STORAGE這個危險權限。因為相冊中的照片都是存儲在SD卡上的锰镀,我們要從SD卡中讀取照片就需要申請這個權限娘侍。WRITE_EXTERNAL_STORAGE表示同時授予程序對SD卡讀和寫的能力咖刃。

當用戶授權了權限申請之后會調用openAlbum()方法黄琼,這里我們先是構建出了一個Intent對象翠语,并將它的action指定為android.intent.action.GET_CONTENT。接著給這個Intent對象設置一些必要的參數(shù)发侵,然后調用StartActivityForResult()方法就可以打開相冊程序選擇照片了氧腰。

接下來的邏輯就比較復雜了枫浙,首先為了兼容新老版本的手機,我們做了一個判斷古拴,如果是4.4及以上的手機就調用handleImageOnKitKat()方法來處理圖片箩帚,否則就調用handleImageBeforeKitKat()方法來處理圖片。之所以要這樣做黄痪,是因為Android系統(tǒng)從4.4版本開始膏潮,選取相冊中的照片不再返回照片的真實Uri了,而是一個封裝過的Uri满力,因此如果是4.4版本以上的手機就需要對這個Uri進行解析才行焕参。

這里有好幾種判斷情況,如果返回的Uri是document類型的話油额,那就取出document id 進行處理叠纷,如果不是的話,那就是用普通的方式處理潦嘶。另外涩嚣,如果Uri的authority是media格式的話,document id還需要再進行一次解析掂僵,要通過字符串分割的方式取出后半部分才能得到真正的數(shù)字id航厚。取出的id用于構建新的Uri和條件語句,然后把這些值作為參數(shù)傳入到getImagePath()方法當中锰蓬,就可以獲取到圖片的真實路徑了幔睬。拿到圖片的路徑之后,再調用displayImage()方法將圖片顯示到界面上芹扭。

相比于handleImageOnKitKat()方法麻顶,handleImageBeforeKitKat()方法中的邏輯就要簡單的多了,因為他的Uri是沒有封裝過的舱卡,不需要任何解析辅肾,直接將Uri傳入到getImagePath()方法當中就能獲取到圖片的真實路徑了。最后同樣是調用displayImage()方法來讓圖片顯示到界面上轮锥。

不過目前我們的實現(xiàn)還不算完美矫钓,因為某些照片即使經(jīng)過裁剪后體積仍然很大,直接加載到內存中有可能會導致內存崩潰。更好的做法是根據(jù)項目的需求先對照片進行適當?shù)膲嚎s新娜,然后在加載到內存中赵辕。

播放多媒體文件

image

播放音頻

在Android中播放音頻文件一般使用都是使用MediaPlayer類來實現(xiàn)的,它對多種格式的音頻文件提供了非常全面的控制方法杯活,從而使得播放音樂的工作變得十分簡單匆帚。

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進入到準備狀態(tài)
        } 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, "拒絕權限將無法實現(xiàn)程序",
                            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();
        }
    }
}

首先需要創(chuàng)建出一個MediaPlayer對象旁钧,然后調用setDataSource()方法來設置音頻文件的路徑,再調用prepare()方法使MediaPlayer進入到準備狀態(tài)互拾,接下來調用start()方法就可以開始播放音頻歪今,調用pause()方法就會暫停播放,調用reset()方法就會停止播放颜矿。

在類初始化的時候我們就先創(chuàng)建了一個MediaPlayer的實例寄猩,然后在onCreate()方法中進行了運行時權限處理,動態(tài)申請WRITE_EXTERNAL_STORAGE權限骑疆。這是由于待會我們會在SD卡中放置一個音頻文件田篇,程序為了播放這個音頻文件必須擁有訪問SD卡的權限才行。注意:在onRequestPermissionsResult()方法中箍铭,如果用戶拒絕了權限申請泊柬,那么就調用finish()方法將程序直接關掉,因為如果沒有SD卡的訪問權限诈火,我們這個程序將什么都干不了兽赁。

用戶同意授權之后,就會調用initMediaPlayer()方法為MediaPlayer對象進行初始化操作冷守。在initMediaPlayer()方法中刀崖,首先是通過創(chuàng)建一個File對象來指定音頻文件的路徑,從這里可以看出拍摇,我們需要事先在SD卡的根目錄下放置一個名為music.mp3的音頻文件亮钦。后面依次調用了setDataSource()方法和prepare()方法,為MediaPlayer做好了播放前的準備充活。

當點擊Play按鈕時會判斷或悲,如果當前MediaPlayer沒有正在播放音頻,則調用start()方法開始播放堪唐。

當點擊Pause按鈕時會判斷巡语,如果當前MediaPlayer正在播放音頻,則調用pause方法暫停播放淮菠。

當點擊Stop按鈕時會判斷男公,如果當前MediaPlayer正在播放音頻,則調用reset()方法將MediaPlayer重置為剛剛創(chuàng)建的狀態(tài)。然后重新調用一遍initMediaPlayer()方法枢赔。

最后在onDestroy()方法中澄阳,我們還需要分別調用stop()方法和release()方法,將與MediaPlayer相關的資源釋放掉踏拜。

最后千萬不要忘記加權限碎赢。

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

播放視頻

播放視頻文件其實并不比播放音頻文件復雜,主要是使用VideoView類來實現(xiàn)的速梗。這個類將視頻的顯示和控制集于一身肮塞,使得我們僅僅借助它就可以完成一個簡易的視頻播放器。

image
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就編寫出一個功能非常強大的視頻播放器是不太現(xiàn)實的款熬。但是如果只是用于播放一些游戲的片頭動畫,或者某個應用的視頻宣傳攘乒,使用VideoView還是綽綽有余的贤牛。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市则酝,隨后出現(xiàn)的幾起案子殉簸,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件般卑,死亡現(xiàn)場離奇詭異武鲁,居然都是意外死亡,警方通過查閱死者的電腦和手機蝠检,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門沐鼠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叹谁,你說我怎么就攤上這事饲梭。” “怎么了本慕?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵排拷,是天一觀的道長侧漓。 經(jīng)常有香客問我锅尘,道長,這世上最難降的妖魔是什么布蔗? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任藤违,我火速辦了婚禮,結果婚禮上纵揍,老公的妹妹穿的比我還像新娘顿乒。我一直安慰自己,他們只是感情好泽谨,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布璧榄。 她就那樣靜靜地躺著,像睡著了一般吧雹。 火紅的嫁衣襯著肌膚如雪骨杂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天雄卷,我揣著相機與錄音搓蚪,去河邊找鬼。 笑死丁鹉,一個胖子當著我的面吹牛妒潭,可吹牛的內容都是我干的。 我是一名探鬼主播揣钦,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼雳灾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冯凹?” 一聲冷哼從身側響起谎亩,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后团驱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摸吠,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年嚎花,在試婚紗的時候發(fā)現(xiàn)自己被綠了寸痢。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡紊选,死狀恐怖啼止,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情兵罢,我是刑警寧澤献烦,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站卖词,受9級特大地震影響巩那,放射性物質發(fā)生泄漏。R本人自食惡果不足惜此蜈,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一即横、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裆赵,春花似錦东囚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至植兰,卻和暖如春份帐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钉跷。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工弥鹦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爷辙。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓彬坏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親膝晾。 傳聞我的和親對象是個殘疾皇子栓始,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內容

  • 8.1使用通知 當應用程序不在前臺是,通過通知向用戶發(fā)送提示消息.發(fā)送后,最上方的狀態(tài)欄會顯示通知的圖標.,下拉后...
    wyxjoker閱讀 287評論 0 1
  • 在過去,手機的功能都比較單調血当,僅僅就是用來打電話和發(fā)短信幻赚。而如今禀忆,手機在我們的生活中正扮演著越來越重要的...
    AndYMJ閱讀 477評論 0 1
  • Media Playback Android多媒體框架包涵了對播放多種通用媒體的類型的支持,所以你可以很容易的集成...
    VegetableAD閱讀 874評論 0 0
  • 最近總想寫點什么東西落恼,拿起筆來又不知道干什么箩退。腦子里很空,空到覺得世界有些迷亂佳谦。 有的時候我很想弄清楚一些事情戴涝。又...
    二甜閱讀 347評論 0 0
  • 在這個連走路都要計較時間的生活里,我偶爾會想钻蔑,文字能給我?guī)硎裁矗?/div>
    顧果閱讀 154評論 0 0