Журнал LinuxFormat - перейти на главную

LXF164: Android

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск

Версия 15:19, 26 октября 2018


Android

Программирование: Взаимодействие с web-камерой телефона


Содержание

Де­ла­ем сним­ки

Не нра­вит­ся про­грам­ма для ка­ме­ры в сво­ем те­ле­фоне? Есть идея про­грам­мы для ра­бо­ты с фо­то? Джуль­ет­та Кемп зна­ко­мит с Android Camera API.

(thumbnail)
Наш эксперт Джуль­ет­та Кемп еще пом­нит вре­ме­на, ко­гда в фо­то­ап­па­ра­тах бы­ла на­стоя­щая плен­ка. Сей­час она де­ла­ет гораздо поболее сним­ков, чем то­гда.

В по­дав­ляю­щем боль­шин­ст­ве те­ле­фо­нов с Android есть встро­ен­ные ка­ме­ры, и с раз­ви­ти­ем тех­но­ло­гий они ста­но­вят­ся все удобнее. Про­грамм, ко­то­рые так или ина­че ра­бо­та­ют с ка­ме­рой, ве­ли­кое мно­же­ст­во, и пре­достав­ляе­мое Android API мак­си­маль­но уп­ро­ща­ет эту за­да­чу.

Ес­ли вам ну­жен все­го-то один снимок, воз­мож­но, про­ще восполь­зо­вать­ся На­ме­рением [Intent] для ак­ти­ва­ции встро­ен­ной ка­ме­ры (см. врез­ку внизу), но ес­ли вам нуж­но боль­ше ры­ча­гов управ­ления, мож­но на­пи­сать соб­ст­вен­ное За­ня­тие [Activity] Camera.

На на­шем уро­ке мы соз­да­дим За­ня­тие, ко­то­рое про­сто ис­поль­зу­ет ка­ме­ру, но, ра­зу­ме­ет­ся, ничто не ме­ша­ет соз­дать при­ло­жение с дру­гим глав­ным За­ня­ти­ем, где за­од­но бу­дет и За­ня­тие для ка­ме­ры, вы­зы­вае­мое при необ­хо­ди­мо­сти.

Под­го­тов­ка ка­ме­ры

Пре­ж­де чем пи­сать код, сле­ду­ет объ­я­вить тре­бо­ва­ния для ка­ме­ры в AndroidManifest.xml:

<manifest .... >

<uses-permission android:name=”android.permission.CAMERA” />

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

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

Ес­ли вы не со­би­рае­тесь со­хра­нять изо­бра­жения, WRITE_EXTERNAL_STORAGE не по­на­до­бит­ся (но так как мы поз­же это сде­ла­ем, то до­ба­вим ее сра­зу). Ди­рек­ти­ва uses-feature оз­на­ча­ет, что для уста­нов­ки при­ло­жения в те­ле­фоне обя­за­тель­но долж­на быть ка­ме­ра. Ес­ли ка­ме­ра – толь­ко часть ва­шей про­грам­мы, и поль­зо­ва­те­ли пре­крас­но смо­гут ра­бо­тать с про­грам­мой и без нее, до­бавь­те android:required=“false” к этой стро­ке. Так­же мож­но за­дать ха­рак­те­ри­сти­ки ка­ме­ры; бо­лее под­роб­но они опи­са­ны в од­ном из сле­дую­щих раз­де­лов.

Те­перь мож­но про­дол­жить с ко­дом пер­во­на­чаль­ной на­строй­ки, в ко­то­ром мы впер­вые по­лу­ча­ем эк­зем­п­ляр объ­ек­та ка­ме­ры. Ме­тод onCreate() в MyCameraActivity дол­жен вы­гля­деть так:

@Override public void onCreate(Bundle savedInstanceState){

super.onCreate(savedInstanceState);

if (!checkCameraExists(this)) {

Toast.makeText(this, “Увы, ка­ме­ры нет!”, Toast.LENGTH_LONG);

finish();

}

camera = getCameraInstance();

}

Один из па­ра­мет­ров ма­ни­фе­ста тре­бу­ет обя­за­тель­но­го на­ли­чия ка­ме­ры на уст­рой­ст­ве, но ее на­ли­чие сто­ит про­ве­рить и здесь. Ме­тод checkCameraExists() прост:

private boolean checkCameraExists(Context c) {

if (c.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {

return true;

} else {

return false;

}

}

Ме­тод getCameraInstance() не­на­мно­го слож­нее:

private Camera getCameraInstance() {

Camera c = null;

try {

c = Camera.open();

} catch (Exception e) {

Toast.makeText(this, “Увы, ка­ме­ры нет!”, Toast.LENGTH_LONG);

Log.e(TAG, “Нет ка­ме­ры: ис­клю­че­ние “ + e.getMessage());

e.getStackTrace();

finish();

}

return c;

}

Ме­тод Camera.open() об­ра­ща­ет­ся к пер­вой ка­ме­ре на задней по­верх­но­сти уст­рой­ст­ва.

Ка­мер на уст­рой­ст­вах Android мо­жет быть несколь­ко; для досту­па к кон­крет­ной ка­ме­ре восполь­зуй­тесь ме­то­дом Camera.open(int cameraId). Ме­тод Camera.getNumberOfCameras() вернет ко­ли­че­­ст­во ка­мер уст­рой­ст­ва, а ме­тод Camera. getCameraInfo() – ин­фор­ма­цию о за­дан­ной ка­ме­ре. Для боль­шин­ст­ва за­дач нам по­дой­дет пер­вая ка­ме­ра на задней по­верх­но­сти уст­рой­ст­ва.

В сле­дую­щих раз­де­лах мы зай­мем­ся про­смот­ром изо­бра­жения с ка­ме­ры и соз­данием сним­ка. Но пе­ред этим нуж­но сде­лать еще од­ну важ­ную вещь: за­кон­чив ра­бо­ту с ка­ме­рой, осво­бо­ди­те ее.

Ина­че дру­гие про­цес­сы не смо­гут по­лу­чить доступ к ка­ме­ре – это пло­хая прак­ти­ка; кро­ме то­го, это обес­по­ко­ит поль­зо­ва­те­лей.

Внесем нуж­ные из­менения в ме­тод onPause():

@Override protected void onPause() {

super.onPause();

releaseCamera();

}

private void releaseCamera() {

if (camera != null) {

camera.release();

camera = null;

}

}

@Override protected void onResume() {

if (camera == null) {

camera.getCameraInstance();

}

super.onResume();

}

Когда мы соз­дадим ко­д про­смот­ра изо­бра­жения с ка­ме­ры, данный ме­тод при­дет­ся несколько усложнить, но мы зай­мем­ся этим по­том. А по­ка вам важнее все­го помнить, что следует обязательно осво­бо­ждать ка­ме­ру, когда наше при­ло­жение приоста­нав­ли­ва­ет­ся.

Соз­да­ем бы­ст­рый сни­мок

Ес­ли нуж­но сде­лать бы­ст­рый сни­мок, при­го­дит­ся На­ме­ре­ние:

{

Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE);

i.putExtra(MediaStore.EXTRA_OUTPUT, fileUri);

startActivityForResult(i, CAPTURE_IMAGE_ACTIVITY_REQ);

}

protected Uri getOutputMediaFileUri(int type) {

// См. код в ос­нов­ном тек­сте для getOutputMediaFile()

return Uri.fromFile(getOutputMediaFile(type));

}

По­мес­ти­те пер­вый фраг­мент ко­да в под­хо­дя­щее ме­сто; мож­но свя­зать его с кноп­кой, по­мес­тив в ме­тод onClick() (как в ко­де на на­шем DVD) или сде­лав пунк­том ме­ню. В этом фраг­мен­те ко­да соз­да­ет­ся но­вое На­ме­рение – на­ме­рение по умол­чанию для соз­дания сним­ка – и оно свя­зы­ва­ет­ся с URI, ука­зы­ваю­щим, где со­хранить ре­зуль­ти­рую­щее изо­бра­жение. За­тем мы за­пуска­ем На­ме­рение для за­пуска За­ня­тия. Нам так­же нуж­но об­ра­бо­тать ре­зуль­тат:

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQ) {

if (resultCode == RESULT_OK) {

if (data == null) {

// Здесь за­ме­че­на ошиб­ка! Сни­мок дол­жен со­хра­нить­ся в fileUri

Toast.makeText(this, “Сни­мок ус­пеш­но со­хра­нен”,

Toast.LENGTH_LONG).show();

} else {

Toast.makeText(this, “Сни­мок ус­пеш­но со­хра­нен: “ + data.getData(),

Toast.LENGTH_LONG).show();

}

// Здесь вы мо­же­те сде­лать что-ни­будь еще...

} else if (resultCode == RESULT_CANCELED){

// Поль­зо­ва­тель от­ме­нил опе­ра­цию; ни­че­го не де­ла­ем

} else {

Toast.makeText(this, “От­каз вы­зо­ва за­хва­та изо­бра­же­ния!”,

Toast.LENGTH_LONG).show();

}

}

}

На прак­ти­ке вы, на­вер­ное, за­хо­ти­те как-то об­ра­бо­тать изо­бра­жение. В неко­то­рых уст­рой­ст­вах за­ме­че­на ошиб­ка, свя­зан­ная с со­хранением изо­бра­жения по­сред­ст­вом ПО ка­ме­ры. URI изо­бра­жения дол­жен быть воз­вра­щен вме­сте с На­ме­рением. Од­на­ко иногда воз­вра­ща­ет­ся пустое На­ме­рение, хо­тя файл был со­хранен пра­виль­но. Что­бы обой­ти эту про­бле­му, со­храните URi изо­бра­жения, ко­то­рый вы от­прав­ля­ли с На­ме­рением, и сно­ва восполь­зуй­тесь им при по­лу­чении успеш­но­го ре­зуль­та­та.

Про­смотр изо­бра­же­ния с ка­ме­ры

С по­мо­щью Camera API мож­но сде­лать сни­мок так, что поль­зо­ва­тель ни о чем не уз­на­ет, но в по­дав­ляю­щем боль­шин­ст­ве слу­ча­ев сна­ча­ла нуж­но по­ка­зать изо­бра­же­ние с ка­ме­ры, а за­тем поль­зо­ва­тель на­жмет на кноп­ку. Для это­го соз­да­дим соб­ст­вен­ный класс CameraPreview, унас­ле­до­ван­ный от SurfaceView:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

private static final String TAG = “CameraPreview”;

private SurfaceHolder sh;

private Camera camera;

public CameraPreview(Context context, Camera cm) {

super(context);

camera = cm;

sh = getHolder();

sh.addCallback(this);

// ус­та­рев­ший, но тре­буе­мый pre-3.0

sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

}

public void surfaceCreated(SurfaceHolder holder) {

try {

camera.setPreviewDisplay(holder);

camera.startPreview();

} catch (IOException e) {

Log.e(TAG, “Ошиб­ка ус­та­нов­ки пред­про­смот­ра: “ + e.getMessage());

e.getStackTrace();

}

}

public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

if (sh.getSurface() == null) {

// Нет по­верх­но­сти пред­про­смот­ра!

return;

}

// Ос­та­нов­ка пред­про­смот­ра до вне­се­ния из­ме­не­ний.

try {

camera.stopPreview();

} catch (Exception e) {

// По­пыт­ка пре­сечь не­су­ще­ст­вую­щий пред­про­смотр

}

try {

camera.setPreviewDisplay(sh);

camera.startPreview();

} catch (Exception e) {

Log.e(TAG, “Ошиб­ка при рес­тар­те пред­про­смот­ра: ” + e.getMessage());

e.getStackTrace();

}

}

public void surfaceDestroyed(SurfaceHolder holder) {

// За­ня­тие сле­дит за ос­во­бо­ж­де­ни­ем пред­про­смот­ра ка­ме­ры

}

}

Этот класс унас­ле­до­ван от SurfaceView, осу­ще­ст­в­ляю­ще­го рен­де­ринг го­раз­до бы­ст­рее, чем стан­дарт­ный View (и от­ри­сов­ка мо­жет вы­пол­нять­ся фо­но­вы­ми нитя­ми, че­го не до­пуска­ет View), что иде­аль­но для бы­ст­рой от­ри­сов­ки, ко­то­рая необходима нам для про­смот­ра изо­бра­жения с ка­ме­ры.

Для досту­па к по­верх­но­сти изо­бра­жения нам по­на­до­бит­ся так­же реа­ли­зо­вать ин­тер­фейс SurfaceHolder.CallBack. Здесь необ­хо­ди­мы ме­то­ды surfaceCreated(), SurfaceChanged() и surfaceDestroyed().

Мы по­лу­ча­ем SurfaceHolder (с по­мо­щью ме­то­да ро­ди­тель­ско­го клас­са), за­тем до­ба­вим к ней Callback, ко­то­рый про­ин­фор­ми­ру­ет кли­ен­та о лю­бых из­менениях с по­верх­но­стью.

На­ша вер­сия surfaceChanged() ниче­го не ме­ня­ет. Же­лая вве­сти из­менения, де­лай­те это ме­ж­ду оста­нов­кой и за­пуском пред­про­смот­ра. Од­на из си­туа­ций, когда это мо­жет при­го­дить­ся – об­ра­бот­ка из­менения по­ло­жения уст­рой­ст­ва (ка­ме­ры и эк­ра­на) в про­стран­ст­ве.

В уст­рой­ст­вах с Android вер­сии до 2.2 пред­про­смотр ав­то­ма­ти­че­­ски вклю­чал­ся как Пей­заж, и для мак­си­маль­ной со­вмес­ти­мо­сти про­ще все­го при­ну­ди­тель­но уста­но­вить ре­жим Пей­за­жа, до­ба­вив ат­ри­бут к эле­мен­ту activity в AndroidManifest.xml:

<activity [ ... ] android:screenOrientation=”landscape” >

Од­на­ко в вер­си­ях с 2.2 и вы­ше это мож­но уб­рать, вза­мен за­дав по­ло­же­ние про­смот­ра со­глас­но по­ло­же­нию уст­рой­ст­ва. До­бавь­те та­кие стро­ки в ме­тод surfaceChanged() в CameraPreview.java ме­ж­ду вы­зо­ва­ми stopPreview() и startPreview():

if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {

camera.setDisplayOrientation(90);

}

Не за­будь­те уда­лить на­строй­ку пей­за­жа из AndroidManifest.xml, и вы уви­ди­те, что пред­про­смотр по­во­ра­чи­ва­ет­ся вслед за по­во­ро­том ка­ме­ры.

Соз­дав класс CameraPreview, до­бавь­те ме­тод setUpLayout(), вы­зы­вае­мый из ме­то­да onCreate() в MyCameraActivity.

Здесь он вы­де­лен в от­дель­ный ме­тод, что­бы про­ще при­ос­та­нав­ли­вать/во­зоб­нов­лять про­грам­му; под­роб­но­сти см. ни­же.

private void setUpLayout() {

setContentView(R.layout.main);

preview = new CameraPreview(this, camera);

FrameLayout frame = (FrameLayout) findViewById(R.id.camera_preview);

frame.addView(preview);

}

Так­же нуж­но до­ба­вить кое-что в файл main.xml:

<?xml version=”1.0” encoding=”utf-8”?>

<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”

android:orientation=”horizontal”

android:layout_width=”fill_parent”

android:layout_height=”fill_parent”

>

<FrameLayout

android:id=”@+id/camera_preview”

android:layout_width=”fill_parent”

android:layout_height=”fill_parent”

android:layout_weight=”1”

/>

<Button

android:id=”@+id/button_capture”

android:text=”Capture”

android:layout_width=”wrap_content”

android:layout_height=”wrap_content”

android:layout_gravity=”center”

/>

</LinearLayout>

Ко­дом кноп­ки мы зай­мем­ся в сле­дую­щем раз­де­ле. Ском­пи­ли­руй­те и за­пус­ти­те про­грам­му, и на эк­ра­не долж­но поя­вить­ся изо­бра­же­ние с ка­ме­ры с кноп­кой Capture [Сде­лать сни­мок] ря­дом, хо­тя и при на­жа­тии кноп­ки по­ка ни­че­го не про­изой­дет.

На­ко­нец, ра­нее я го­во­ри­ла, что по­сле на­пи­са­ния ко­да про­смот­ра ме­то­ды onPause() и onResume() нуж­но не­мно­го ус­лож­нить. Ес­ли про­сто ос­во­бо­дить ка­ме­ру, то при во­зоб­нов­ле­нии при­ло­же­ния ста­рый класс пред­про­смот­ра то­же по­про­бу­ет об­ра­тить­ся к объ­ек­ту ка­ме­ры, ко­то­рый у не­го был (и ко­то­рый те­перь ос­во­бо­ж­ден), и вы­зо­вет ис­клю­че­ние.

Что­бы это ис­пра­вить, нуж­но ос­во­бо­ж­дать объ­ект при при­ос­та­нов­ке при­ло­же­ния, а при во­зоб­нов­ле­нии – соз­да­вать но­вый:

protected void onPause() {

releaseCamera();

super.onPause();

}

private void releaseCamera() {

if (camera != null) {

camera.stopPreview();

camera.release();

camera = null;

preview = null;

}

}

protected void onResume() {

if (camera == null) {

camera = getCameraInstance();

setUpLayout();

}

super.onResume();

}

Те­перь вы смо­же­те вый­ти из про­грам­мы и за­тем пе­ре­за­пус­тить ее без оши­бок.

Персональные инструменты
купить
подписаться
Яндекс.Метрика