Android 相機(jī)使用教程(一)

Android 框架提供對設(shè)備上可用的相機(jī)和各種相機(jī)功能的支持,通過它我們可以在應(yīng)用程序中拍攝圖片和視頻。本文將介紹一種快速,簡單的拍攝圖像和視頻方法,并概述創(chuàng)建自定義相機(jī)的高級用法。

你還在為開發(fā)中頻繁切換環(huán)境打包而煩惱嗎?快來試試 Environment Switcher 吧!使用它可以在app運(yùn)行時(shí)一鍵切換環(huán)境,而且還支持其他貼心小功能,有了它媽媽再也不用擔(dān)心頻繁環(huán)境切換了。https://github.com/CodeXiaoMai/EnvironmentSwitcher

注意:Camera類已被棄用,官方建議使用更新的 android.hardware.camera2,但是它適用于Android 5.0(API級別21)或更高版本,所以...... 你懂的。

前言

在拍攝照片和視頻之前,首先我們應(yīng)該考慮一些關(guān)于我們的應(yīng)用程序應(yīng)該如何使用相機(jī)的問題。

  • 相機(jī)的必要性 - 相機(jī)對我們的應(yīng)用程序是否非常重要?我們的應(yīng)用程序是否不允許安裝在沒有相機(jī)的設(shè)備上?如果是這樣,我們應(yīng)該在 manifest 中聲明對相機(jī)的要求。

  • 快速拍照還是自定義相機(jī) - 我們的應(yīng)用程序?qū)⑷绾问褂孟鄼C(jī)?我們是只想快速的拍攝圖片或視頻剪輯,還是提供新的方式使用相機(jī)?如果是想快速的拍攝圖片或剪輯,我們應(yīng)該考慮使用現(xiàn)有的相機(jī)應(yīng)用程序(系統(tǒng)已經(jīng)提供默認(rèn)的相機(jī)應(yīng)用)。而要開發(fā)定制的相機(jī)功能,我們應(yīng)該創(chuàng)建自己的相機(jī)應(yīng)用程序部分。

  • 存儲 - 我們的應(yīng)用程序生成的圖像或視頻是僅在我們的應(yīng)用程序中可見,還是為了方便其他應(yīng)用程序(如Gallery或其他媒體和社交應(yīng)用程序)使用它們?如果我們的應(yīng)用程序被卸載,是否希望圖片和視頻仍然可用?

相關(guān)基礎(chǔ)內(nèi)容

Android框架支持通過android.hardware.camera2 API或 Intent 啟動攝像機(jī) 兩種方式捕獲圖像和視頻。以下是我們需要用到的類:

  • android.hardware.camera2
    這是用于控制設(shè)備攝像機(jī)的主要API。當(dāng)我們創(chuàng)建相機(jī)應(yīng)用程序時(shí),可用于拍攝照片或視頻。

  • Camera
    這個類是用于控制設(shè)備攝像頭的舊版API(已經(jīng)被廢棄)。

  • SurfaceView
    這個類用于呈現(xiàn)實(shí)時(shí)相機(jī)預(yù)覽。

  • MediaRecorder
    該類用于記錄相機(jī)錄制的視頻。

  • Intent
    使用 MediaStore.ACTION_IMAGE_CAPTURE 可用于捕獲圖像,而使用 MediaStore.ACTION_VIDEO_CAPTURE 可用于拍攝視頻。這兩個 Intent 是為了方便我們直接調(diào)用其他相機(jī)應(yīng)用進(jìn)行拍照或錄制視頻,而不用直接使用Camera對象。

清單聲明

在使用Camera API開始開發(fā)應(yīng)用程序之前,我們首先應(yīng)該正確的配置 manifest,以允許使用相機(jī)硬件和其他相關(guān)功能。

  • 相機(jī)權(quán)限 - 如果我們通過調(diào)用現(xiàn)有的相機(jī)應(yīng)用程序來使用相機(jī),則應(yīng)用程序不需要請求此權(quán)限,反之如果是自定義相機(jī)則我們的應(yīng)用程序必須要求使用設(shè)備攝像頭的權(quán)限。

    <uses-permission android:name="android.permission.CAMERA" />
    
  • 相機(jī)功能 - 我們的應(yīng)用程序還必須聲明使用相機(jī)功能

    <uses-feature android:name="android.hardware.camera" />
    

將相機(jī)功能添加到 manifest 中會導(dǎo)致Google Play阻止我們的應(yīng)用程序安裝到?jīng)]有相機(jī)或不支持我們所指定的相機(jī)功能的設(shè)備上。

如果我們的應(yīng)用程序的核心功能為拍攝照片,或者如果設(shè)備沒有相機(jī)則應(yīng)用無法運(yùn)行,我們可以通過上面的代碼將其在應(yīng)用商店(Google Play)上的可見性限制為具有相機(jī)的設(shè)備。而如果我們的應(yīng)用程序有一部分功能需要相機(jī),但并非必須需要相機(jī)才能運(yùn)行(其他功能還是可以用的嘛),則應(yīng)將 manifest 中的 android:required 設(shè)置為 false,否則會導(dǎo)致設(shè)備沒有相機(jī)的用戶無法安裝此應(yīng)用。

<manifest ... >
    <uses-feature android:name="android.hardware.camera" android:required="false" />
    ...
</manifest>
  • 存儲權(quán)限 - 如果我們的應(yīng)用程序需要將圖像或視頻保存到設(shè)備的外部存儲(SD卡),則還必須在 manifest 中添加:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
  • 錄音權(quán)限** - 對于使用視頻捕獲錄制音頻,我們的應(yīng)用程序必須請求音頻捕獲權(quán)限。

    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    
  • 定位權(quán)限 - 如果我們的應(yīng)用程序使用GPS位置信息標(biāo)記圖像,則必須請求ACCESS_FINE_LOCATION權(quán)限。需要注意的是,如果我們的應(yīng)用運(yùn)行在Android 5.0(API級別21)或更高版本,則還需要聲明使用設(shè)備的GPS:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- 如果你需要保存照片的位置信息就乖乖的添加吧,除非你不支持 5.0 以上的設(shè)備 -->
    <uses-feature android:name="android.hardware.location.gps" />
    

使用現(xiàn)有相機(jī)應(yīng)用拍攝照片

通過Intent來調(diào)用現(xiàn)有的相機(jī)應(yīng)用程序,可以快速地實(shí)現(xiàn)在應(yīng)用程序中拍攝照片或視頻,這是最簡單快速的方法。

通過現(xiàn)有的相機(jī)應(yīng)用程序來實(shí)現(xiàn)拍照這個過程涉及 3 個步驟:創(chuàng)建 Intent、啟動已有相機(jī)應(yīng)用 以及返回到我們的 Activity 時(shí)處理圖像數(shù)據(jù)。

1. 創(chuàng)建 Intent 并啟動相機(jī)應(yīng)用

這是一個通過 Intent 啟動系統(tǒng)相機(jī)的例子:

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

請注意,startActivityForResult()方法受到調(diào)用resolveActivity()的條件的保護(hù),resolveActivity()方法返回可處理該intent的第一個活動組件。執(zhí)行此檢查很重要,因?yàn)槿绻覀兪褂靡粋€沒有應(yīng)用程序可以響應(yīng)的 Intent 調(diào)用startActivityForResult(),程序?qū)罎?。所以只要結(jié)果不為空,就可以安全的使用它。

2. 處理圖像數(shù)據(jù)

當(dāng)使用相機(jī)應(yīng)用程序拍照成功后,系統(tǒng)會將照片進(jìn)行編碼放入返回的 Intent 中并傳遞給onActivityResult()。

2.1 獲取縮略圖

Intent 的 extras 中的 “data” 關(guān)鍵字 存儲的就是縮略圖。以下代碼是從返回結(jié)果中取出此圖像并將其顯示在ImageView中。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(imgetBitmap);
    }
}

注意:來自“data”的縮略圖可能對像素要求不高的圖片很適合。處理全尺寸圖像需要更多的工作。

2.2 保存全尺寸照片

顯然僅僅用縮略圖是不能滿足我們的要求的,我們可以用一個文件來保存一張全尺寸照片。

通常,用戶使用攝像頭拍攝的照片應(yīng)保存在公共外部存儲設(shè)備上,以便所有應(yīng)用都可以訪問。調(diào)用 Environment.getExternalStoragePublicDirectory()并傳遞參數(shù)DIRECTORY_PICTURES 將返回共享照片的正確目錄。由于此方法提供的目錄在所有應(yīng)用程序之間共享,所以讀取和寫入需要分別具有READ_EXTERNAL_STORAGE 和 WRITE_EXTERNAL_STORAGE權(quán)限。寫入權(quán)限隱含地允許讀取,所以如果你需要寫入外部存儲,那么你只需要請求一個權(quán)限:

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

但是,如果我們希望照片僅對我們自己的應(yīng)用程序保持私有狀態(tài),則可以使用getExternalFilesDir()提供的目錄。在Android 4.3及更低版本上,寫入此目錄也需要 WRITE_EXTERNAL_STORAGE 權(quán)限。從Android 4.4開始,不再需要權(quán)限,因?yàn)槟夸洸荒鼙黄渌麘?yīng)用程序訪問,因此我們可以通過添加maxSdkVersion屬性來聲明僅在較低版本的Android上的權(quán)限:

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

注意:當(dāng)用戶卸載應(yīng)用程序時(shí),系統(tǒng)將刪除由getExternalFilesDir()或 getFilesDir()提供的目錄中保存的文件。

確定文件的目錄后,我們需要創(chuàng)建一個防止沖突的文件名。我們可能還希望將路徑保存在成員變量中以備以后使用。以下是使用日期時(shí)間戳作為新照片的唯一文件名的方法的示例解決方案:

String mCurrentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
            imageFileName, /* prefix */
            ".jpg",        /* suffix */
            storageDir     /* directory */
    );

    // Save a file: 用于ACTION_VIEW意圖的路徑
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

使用這種方法可以為照片保存為一個文件,現(xiàn)在可以像這樣創(chuàng)建和調(diào)用Intent:

public static final int REQUEST_TAKE_PHOTO = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        File photoFile = null;
        try {
            // Create the File where the photo should go
            photoFile = createImageFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this,
                    "com.xiaomai.myproject.fileprovider",
                    photoFile
            );
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
        }
    }
}

注意:我們使用getUriForFile(Context,String,F(xiàn)ile)返回一個content:// URI。對于Android 7.0和更高(API等級大于等于24)的應(yīng)用,通過一個包邊界傳遞一個file:// URI會導(dǎo)致FileUriExposedException。因此,我們現(xiàn)在介紹一種使用FileProvider存儲圖像的更通用的方法。

現(xiàn)在,我們需要配置FileProvider。在應(yīng)用的清單中,為我們的應(yīng)用添加一個提供者:

<application>
   ...
   <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.xiaomai.myproject.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
    </provider>
    ...
</application>

確保android:authorities的字符串內(nèi)容匹配 getUriForFile(Context,String,F(xiàn)ile)的第二個參數(shù)。在提供程序定義的元數(shù)據(jù)部分,我們可以看到提供程序期望在專用資源文件res/xml/file_paths.xml中配置符合條件的路徑。以下是此特定示例所需的內(nèi)容:

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

路徑組件對應(yīng)于調(diào)用getExternalFilesDir() 并使用Environment.DIRECTORY_PICTURES時(shí)返回的路徑。確保你已經(jīng)將com.example.package.name替換為應(yīng)用程序的實(shí)際包名稱。另外,可以查看FileProvider的文檔,以便使用除了外部路徑之外的路徑說明符。

將照片添加到圖庫

當(dāng)通過 Intent 啟動拍攝照片時(shí),我們知道圖像所在的位置,因?yàn)槲覀冎付藢⑵浔4嬖谀睦?。對于其他人來說,也許最簡單的方法是使我們的照片可以訪問,使其可以從系統(tǒng)的媒體訪問。

注意:如果我們將照片保存到由getExternalFilesDir()提供的目錄中,則媒體掃描程序無法訪問這些文件,因?yàn)樗鼈儗ξ覀兊膽?yīng)用程序是私有的。

以下示例方法演示了如何調(diào)用系統(tǒng)的媒體掃描程序?qū)⑽覀兊恼掌砑拥矫襟w提供商的數(shù)據(jù)庫,使其可以在Android圖庫應(yīng)用程序和其他應(yīng)用程序中使用。

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File file = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(file);
    mediaScanIntent.setData(contentUri);
    sendBroadcast(mediaScanIntent);
}

解碼縮放圖片

使用有限的內(nèi)存來管理多個全尺寸圖像可能很棘手。如果在僅僅顯示幾個圖像后就發(fā)現(xiàn)應(yīng)用程序內(nèi)存不足,則可以通過將JPEG縮放到匹配目標(biāo)視圖大小并存入內(nèi)存中,來大大減少動態(tài)堆的使用數(shù)量。以下示例方法演示了此技術(shù)。

private void setPic() {
    // 獲取ImageView的尺寸
    int targetWidth = mImageView.getWidth();
    int targetHeight = mImageView.getHeight();

    // 獲取圖片的真實(shí)尺寸
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    int photoWidth = bmOptions.outWidth;
    int photoHeight = bmOptions.outHeight;

    // 確定圖像的縮放比例
    int scaleFactor = Math.min(photoWidth / targetWidth, photoHeight / targetHeight);

    // 將圖像文件解碼為位圖大小以填充視圖
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    mImageView.setImageBitmap(bitmap);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,983評論 25 709
  • 上一篇介紹了如何使用系統(tǒng)相機(jī)簡單、快速的進(jìn)行拍照,本篇將介紹如何使用框架提供的API直接控制攝像機(jī)硬件。 你還在為...
    Xiao_Mai閱讀 7,375評論 4 18
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 7,322評論 0 17
  • (1)鬧鐘 創(chuàng)建鬧鐘(ACTION_SET_ALARM)示例Intent: 注:為了調(diào)用ACTION_SET_AL...
    sunnygarden閱讀 1,810評論 0 10
  • 九天之上,宇宙邊緣,銀樹火花,一張石桌,兩杯濁酒,笑對星河,把酒言歡。 菩提:昔日之事,多虧太上照拂。 老君:何須...
    傅人閱讀 747評論 5 18

友情鏈接更多精彩內(nèi)容