Hướng dẫn sử dụng Android Camera
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Android Camera

Camera là một thiết bị cho phép bạn chụp hình hoặc quay video. Trong Android có 2 cách để bạn làm việc với Camera.

Cách 1:

Trên hệ điều hành Android đã có sẵn ứng dụng để làm việc với Camera, ứng dụng của bạn có thể gọi ứng dụng đó thông qua một Intent không tường minh (Implicit Intent) để yêu cầu một hành động nào đó với Camera, chẳng hạn yêu cầu mở Camera và chụp ảnh, hoặc yêu cầu mở Camera để quay video, và sau đó nhận kết quả trả về.

Cách 2:

Android cung cấp cho bạn các API để làm việc trực tiếp với Camera.

Với Android Level < 21 bạn có thể làm việc trực tiếp với Camera thông qua class android.hardware.Camera, tuy nhiên class này đã lỗi thời (Deprected) và không còn được sử dụng trong Android Level >= 21, khuyến cáo bạn nên sử dụng camera2 API.
Trong tài liệu này tôi sẽ hướng dẫn bạn sử dụng Intent không tường minh để gọi vào ứng dụng Camera có sẵn trong hệ thống yêu cầu mở Camera để chụp hình hoặc quay video.
Bạn có thể xem hướng dẫn sử dụng Camera2 API tại:
  • TODO

2- Tổng quan

Trên hệ điều hành Android đã có sẵn ứng dụng để làm việc với Camera, trên ứng dụng của bạn có thể tạo một Intent không tường minh (implicit intent) để gọi tới ứng dụng này, yêu cầu mở Camera để quay phim hoặc chụp hình.
// Tạo một Intent không tường minh,
// để yêu cầu hệ thống mở Camera chuẩn bị chụp hình.
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

int REQUEST_ID_IMAGE_CAPTURE = 100;

// Start Activity chụp hình, và chờ đợi kết quả trả về.
this.startActivityForResult(intent, REQUEST_ID_IMAGE_CAPTURE);
Các loại Intent cho Camera:
Loại Intent Mô tả
ACTION_IMAGE_CAPTURE_SECURE

Nó sẽ trả về một Image được chụp từ Camera, khi thiết bị được bảo mật

ACTION_VIDEO_CAPTURE

Nó sẽ gọi một ứng dụng video trong Android quay video từ Camera.

EXTRA_SCREEN_ORIENTATION

Sử dụng để sét đặt chiều của màn hình là "vertical" hoặc "landscape"

EXTRA_FULL_SCREEN

Nó được sử dụng để điều khiển giao diện người dùng của ViewImage

INTENT_ACTION_VIDEO_CAMERA

Sử dụng để khởi động Camera ở chế độ quay video.

EXTRA_SIZE_LIMIT

Sử dụng để chỉ định giá trị lớn nhất cho kích thước file ảnh và video.

Trong trường hợp bạn muốn lưu trữ ảnh chụp hoặc video vừa quay được trên thiết bị bạn cần cấu hình cho phép đọc và ghi dữ liệu vào thiết bị. Cấu hình trên AndroidManifest.xml.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Với Android Level >= 23, bạn cần phải sử dụng code để hỏi người dùng cho phép đọc và ghi dữ liệu vào thiết bị.
// Với Android Level >= 23 bạn phải hỏi người dùng cho phép ghi dữ liệu vào thiết bị.
if (android.os.Build.VERSION.SDK_INT >= 23) {


   // Kiểm tra quyền đọc/ghi dữ liệu vào thiết bị lưu trữ ngoài.
   int readPermission = ActivityCompat.checkSelfPermission(this,
                                  Manifest.permission.READ_EXTERNAL_STORAGE);
   int writePermission = ActivityCompat.checkSelfPermission(this,
                                  Manifest.permission.WRITE_EXTERNAL_STORAGE);

 if (writePermission != PackageManager.PERMISSION_GRANTED ||
         readPermission != PackageManager.PERMISSION_GRANTED) {

     // Nếu không có quyền, cần nhắc người dùng cho phép.
     this.requestPermissions(
             new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                     Manifest.permission.READ_EXTERNAL_STORAGE},
             REQUEST_ID_READ_WRITE_PERMISSION
     );
 }
}
Sử lý khi người dùng trả lời yêu cầu.
// Khi yêu cầu hỏi người dùng được trả về (Chấp nhận hoặc không chấp nhận).
@Override
public void onRequestPermissionsResult(int requestCode,
                                  String permissions[], int[] grantResults) {

   super.onRequestPermissionsResult(requestCode, permissions, grantResults);
   //
   switch (requestCode) {
       case REQUEST_ID_READ_WRITE_PERMISSION: {

           // Chú ý: Nếu yêu cầu bị hủy, mảng kết quả trả về là rỗng.
           // Người dùng đã cấp quyền (đọc/ghi).
           if (grantResults.length > 1
                   && grantResults[0] == PackageManager.PERMISSION_GRANTED
                   && grantResults[1] == PackageManager.PERMISSION_GRANTED) {

               Toast.makeText(this, "Permission granted!", Toast.LENGTH_LONG).show();

               this.captureVideo();

           }
           // Hủy bỏ hoặc bị từ chối.
           else {
               Toast.makeText(this, "Permission denied!", Toast.LENGTH_LONG).show();
           }
           break;
       }
   }
}

3- Camera trên thiết bị mô phỏng

Nếu bạn làm việc với thiết bị mô phỏng bạn cần phải cấu hình Camera, có 2 lựa chọn:
  1. Sử dụng Camera mô phỏng.
  2. Hoặc sử dụng Webcam của máy tính như một Camera cho cái điện thoại mô phỏng của Android.
Trong trường hợp bạn không cấu hình Camera, bạn sẽ nhận được lỗi:
java.lang.RuntimeException: Fail to connect to camera service
Ở đây tôi cấu hình để sử dụng thiết bị Camera mô phỏng.
Sử dụng Camera mô phỏng.

4- Ví dụ

Tạo mới một project có tên AndroidCameraDemo:
Thêm cấu hình cho phép đọc và ghi dữ liệu trên thiết bị.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.o7planning.androidcamerademo">


    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_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>
    </application>

</manifest>
Thiết kế giao diện ví dụ:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="org.o7planning.androidcamerademo.MainActivity">

    <ImageView
        android:layout_width="fill_parent"
        android:layout_height="180dp"
        android:id="@+id/imageView"
        android:layout_alignParentTop="true" />

    <VideoView
        android:layout_width="wrap_content"
        android:layout_height="180dp"
        android:id="@+id/videoView"
        android:layout_below="@+id/imageView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Capture Image"
        android:id="@+id/button_image"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Capture Video"
        android:id="@+id/button_video"
        android:layout_alignTop="@+id/button_image"
        android:layout_toRightOf="@+id/button_image"
        android:layout_toEndOf="@+id/button_image" />

</RelativeLayout>
MainActivity.java
package org.o7planning.androidcamerademo;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;
import android.widget.VideoView;

import java.io.File;

public class MainActivity extends AppCompatActivity {

   private Button buttonImage;
   private Button buttonVideo;

   private VideoView videoView;
   private ImageView imageView;

   private static final int REQUEST_ID_READ_WRITE_PERMISSION = 99;
   private static final int REQUEST_ID_IMAGE_CAPTURE = 100;
   private static final int REQUEST_ID_VIDEO_CAPTURE = 101;

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       this.buttonImage = (Button) this.findViewById(R.id.button_image);
       this.buttonVideo = (Button) this.findViewById(R.id.button_video);
       this.videoView = (VideoView) this.findViewById(R.id.videoView);
       this.imageView = (ImageView) this.findViewById(R.id.imageView);

       this.buttonImage.setOnClickListener(new Button.OnClickListener() {
           @Override
           public void onClick(View v) {
               captureImage();
           }
       });

       this.buttonVideo.setOnClickListener(new Button.OnClickListener() {
           @Override
           public void onClick(View v) {
               askPermissionAndCaptureVideo();
           }
       });
   }

   private void captureImage() {
       // Tạo một Intent không tường minh,
       // để yêu cầu hệ thống mở Camera chuẩn bị chụp hình.
       Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 
       // Start Activity chụp hình, và chờ đợi kết quả trả về.
       this.startActivityForResult(intent, REQUEST_ID_IMAGE_CAPTURE);
   }

   private void askPermissionAndCaptureVideo() {

 
       // Với Android Level >= 23 bạn phải hỏi người dùng cho phép đọc/ghi dữ liệu vào thiết bị.
       if (android.os.Build.VERSION.SDK_INT >= 23) {
 
           // Kiểm tra quyền đọc/ghi dữ liệu vào thiết bị lưu trữ ngoài.
           int readPermission = ActivityCompat.checkSelfPermission(this,
                                          Manifest.permission.READ_EXTERNAL_STORAGE);
           int writePermission = ActivityCompat.checkSelfPermission(this,
                                          Manifest.permission.WRITE_EXTERNAL_STORAGE);

           if (writePermission != PackageManager.PERMISSION_GRANTED ||
                   readPermission != PackageManager.PERMISSION_GRANTED) {
  
               // Nếu không có quyền, cần nhắc người dùng cho phép.
               this.requestPermissions(
                       new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                               Manifest.permission.READ_EXTERNAL_STORAGE},
                       REQUEST_ID_READ_WRITE_PERMISSION
               );
               return;
           }
       }
       this.captureVideo();
   }

   private void captureVideo() {
 
       // Tạo một Intent không tường minh,
       // để yêu cầu hệ thống mở Camera chuẩn bị quay video.
       Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
 
       // Thư mục lưu trữ ngoài.
       File dir = Environment.getExternalStorageDirectory();
       if (!dir.exists()) {
           dir.mkdirs();
       }
       // file:///storage/emulated/0/myvideo.mp4
       String savePath = dir.getAbsolutePath() + "/myvideo.mp4";
       File videoFile = new File(savePath);
       Uri videoUri = Uri.fromFile(videoFile);
 
       // Chỉ định vị trí lưu file video khi quay.
       intent.putExtra(MediaStore.EXTRA_OUTPUT, videoUri);
 
       // Start Activity quay video, và chờ đợi kết quả trả về.
       this.startActivityForResult(intent, REQUEST_ID_VIDEO_CAPTURE);
   }

 
   // Khi yêu cầu hỏi người dùng được trả về (Chấp nhận hoặc không chấp nhận).
   @Override
   public void onRequestPermissionsResult(int requestCode,
                                          String permissions[], int[] grantResults) {

       super.onRequestPermissionsResult(requestCode, permissions, grantResults);
       //
       switch (requestCode) {
           case REQUEST_ID_READ_WRITE_PERMISSION: {
        
               // Chú ý: Nếu yêu cầu bị hủy, mảng kết quả trả về là rỗng.
               // Người dùng đã cấp quyền (đọc/ghi).
               if (grantResults.length > 1
                       && grantResults[0] == PackageManager.PERMISSION_GRANTED
                       && grantResults[1] == PackageManager.PERMISSION_GRANTED) {

                   Toast.makeText(this, "Permission granted!", Toast.LENGTH_LONG).show();

                   this.captureVideo();

               }
               // Hủy bỏ hoặc bị từ chối.
               else {
                   Toast.makeText(this, "Permission denied!", Toast.LENGTH_LONG).show();
               }
               break;
           }
       }
   }

 
   // Khi activy chụp hình (Hoặc quay video) hoàn thành, phương thức này sẽ được gọi.
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       super.onActivityResult(requestCode, resultCode, data);

       if (requestCode == REQUEST_ID_IMAGE_CAPTURE) {
           if (resultCode == RESULT_OK) {
               Bitmap bp = (Bitmap) data.getExtras().get("data");
               this.imageView.setImageBitmap(bp);
           } else if (resultCode == RESULT_CANCELED) {
               Toast.makeText(this, "Action canceled", Toast.LENGTH_LONG).show();
           } else {
               Toast.makeText(this, "Action Failed", Toast.LENGTH_LONG).show();
           }
       } else if (requestCode == REQUEST_ID_VIDEO_CAPTURE) {
           if (resultCode == RESULT_OK) {
               Uri videoUri = data.getData();
               Log.i("MyLog", "Video saved to: " + videoUri);
               Toast.makeText(this, "Video saved to:\n" +
                       videoUri, Toast.LENGTH_LONG).show();
               this.videoView.setVideoURI(videoUri);
               this.videoView.start();
           } else if (resultCode == RESULT_CANCELED) {
               Toast.makeText(this, "Action Cancelled.",
                       Toast.LENGTH_LONG).show();
           } else {
               Toast.makeText(this, "Action Failed",
                       Toast.LENGTH_LONG).show();
           }
       }
   }

}
OK, bây giờ bạn có thể chạy ứng dụng. Ở đây tôi chạy ứng dụng trên thiết bị mô phỏng với Camera cũng là camera mô phỏng.
Chú ý: Bạn có thể cấu hình thiết bị mô phỏng để sử dụng Webcam của máy tính như là một Camera của thiết bị mô phỏng.
  • TODO

5- Vấn đề với thiết bị mô phỏng

Chú ý: Khi bạn chạy ứng dụng lần đầu tiên nhấn vào "Capture Video", ứng dụng sẽ  hỏi để được quyền đọc ghi trên thiết bị lưu trữ.
 
Sau khi đã người dùng đã cho phép đọc/ghi dữ liệu trên thiết bị lưu trữ ngoài, nhưng nó vẫn không có tác dụng (Đây là vấn đề của thiết bị mô phỏng).
 
Bạn có thể chạy lại ứng dụng từ Android Studio. Ứng dụng đã được cho phép trong lần chạy trước và không hỏi lại nữa.  Bạn cũng không gặp phải lỗi như hình minh họa ở trên.