Как создать приложение Фонарик для Android

Это новая рубрика – как сделать приложение, где мы с вами будем учиться делать простые приложения для android, опираясь на материал бесплатных уроков нашего канала. Начнем с простого приложения – это фонарик. Приложение состоит из одной кнопки на экране, которая включает и выключает вспышку камеры устройства – если она есть, конечно.

Для начала создадим проект из шаблона Empty Activity.
В манифесте добавим такие разрешения:
<uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.FLASHLIGHT"/>
    <uses-feature android:name="android.hardware.camera"/>

Также в активити добавим директиву, которая отключит его пересоздание при повороте экрана:

android:configChanges="orientation|keyboardHidden|screenSize"

Нам понадобятся такие строки, добавьте в res/values/strings.xml:

<resources>
    <string name="app_name">Фонарик</string>
    <string name="error_text">Вспышка камеры недоступна!</string>
    <string name="error_title">Ошибка! Нет вспышки!</string>
    <string name="exit_message">Выход</string>
</resources>
Здесь имя приложения и тексты для окна сообщения об ошибке.
В папке drawable создадим файл фона для активити:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
    android:type="radial"
    android:startColor="#303030"
    android:endColor="#050505"
    android:gradientRadius="450"
    android:angle="050"/>
</shape>

В папку res/raw (создайте, если ее нет) загрузите звук щелчка по ссылке.

В макете экрана приложения такие компоненты:

<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:background="@drawable/flash_background"
    tools:context="info.fandroid.flashlight.MainActivity">

    <Switch
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/my_switch"
        android:layout_centerInParent="true"/>
</RelativeLayout>

Здесь используем экранный компонент Switch – переключатель, выравниваем его по центру.

Теперь код главного класса MainActivity.java:

package ...

import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.CompoundButton;
import android.widget.Switch;

import java.io.IOException;
import java.util.List;

public class MainActivity extends AppCompatActivity implements SoundPool.OnLoadCompleteListener {

    private int sound;
    private SoundPool soundPool;
    private Camera camera;
    Parameters parameters;
    private Switch mySwitch;

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

        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            createSoundPoolWithBuilder();
        } else {
            createSoundPoolWithConstructor();
        }

        soundPool.setOnLoadCompleteListener(this);
        sound = soundPool.load(this, R.raw.click, 1);



        mySwitch = (Switch) findViewById(R.id.my_switch);
        mySwitch.setChecked(true);
        mySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean isChecked) {
                if (isChecked) {
                    setFlashLigthOn();
                } else {
                    setFlashLightOff();
                }
            }
        });

        boolean isCameraFlash = getApplicationContext().getPackageManager()
                .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);

        if (!isCameraFlash) {
            showCameraAlert();
        } else {
            camera = Camera.open();
        }

    }
    private void showCameraAlert() {
        new AlertDialog.Builder(this)
                .setTitle(R.string.error_title)
                .setMessage(R.string.error_text)
                .setPositiveButton(R.string.exit_message, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                })
                .setIcon(android.R.drawable.ic_dialog_alert)
                .show();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    protected void createSoundPoolWithBuilder() {
        AudioAttributes attributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_GAME)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build();

        soundPool = new SoundPool.Builder().setAudioAttributes(attributes).setMaxStreams(1).build();
    }

    @SuppressWarnings("deprecation")
    protected void createSoundPoolWithConstructor() {
        soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
    }

    private void setFlashLigthOn() {
        soundPool.play(sound, 1, 1, 0, 0, 1);

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (camera != null) {
                    parameters = camera.getParameters();

                    if (parameters != null) {
                        List supportedFlashModes = parameters.getSupportedFlashModes();

                        if (supportedFlashModes.contains(Parameters.FLASH_MODE_TORCH)) {
                            parameters.setFlashMode(Parameters.FLASH_MODE_TORCH);
                        } else if (supportedFlashModes.contains(Parameters.FLASH_MODE_ON)) {
                            parameters.setFlashMode(Parameters.FLASH_MODE_ON);
                        } else camera = null;

                        if (camera != null) {
                            camera.setParameters(parameters);
                            camera.startPreview();
                            try {
                                camera.setPreviewTexture(new SurfaceTexture(0));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }).start();
    }

    private void setFlashLightOff() {
        soundPool.play(sound, 1, 1, 0, 0, 1);
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (camera != null) {
                    parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
                    camera.setParameters(parameters);
                    camera.stopPreview();
                }
            }
        }).start();
    }

    private void releaseCamera() {
        if (camera != null) {
            camera.release();
            camera = null;
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        releaseCamera();
    }

    @Override
    protected void onPause() {
        super.onPause();
        releaseCamera();
        mySwitch.setChecked(false);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (camera == null) {
            camera = Camera.open();
        } else{
            setFlashLigthOn();
        }
        mySwitch.setChecked(true);
    }

    @Override
    public void onLoadComplete(SoundPool soundPool, int i, int i1) {

    }
}
Пройдемся по коду. Объявляем необходимые переменные. Для проигрывания звука щелчка кнопки используем класс  SoundPool – он подходит для случая, когда вам необходимо многократное воспроизведение небольших файлов. Далее объявляем переменную класса Camera, который нам нужен для работы со вспышкой. Камера нужна из пакета android.hardware. Android Studio помечает класс  Camera и класс Parameters как deprecated – это связано с тем, что в API 21 добавлены новые классы для работы с камерой – пакет Camera2. Но  он поддерживается только устройствами с android 5 и выше. А класс Camera поддерживается и старыми, и новыми устройствами, поэтому будем использовать его.
Также нам нужен экранный компонент Switch и логическая переменная для хранения состояния вспышки. В методе onCreate находим переключатель по ID, устанавливаем его в положение “вкл”. Также  присваиваем ему слушатель. Здесь позже пропишем вызов методов включения и выключения вспышки.
Поскольку не во всех устройствах есть вспышка, нужно при старте приложения проверять ее наличие и оповещать пользователя при невозможности использования фонарика. Для оповещения служит метод:
private void showCameraAlert() {
      new AlertDialog.Builder( this)
                  .setTitle(R.string. error_title )
                  .setMessage(R.string. error_text )
                  .setPositiveButton(R.string. exit_message , new DialogInterface.OnClickListener() {


                         @Override
                         public void onClick(DialogInterface dialog, int which) {
                              finish();
                        }
                  })
                  .setIcon(android.R.drawable. ic_dialog_alert )
                  .show();
}
Здесь создается диалоговое окно с заголовком, текстом и кнопкой выхода.
А в onCreate проверяем наличие вспышки:
boolean isCameraFlash = getApplicationContext().getPackageManager()
            .hasSystemFeature(PackageManager. FEATURE_CAMERA_FLASH);


if (!isCameraFlash) {
      showCameraAlert();
} else {
      camera = Camera.open();
}

Получаем контекст приложения, PackageManager и выполняем метод hasSystemFeatureс с параметром FEATURE_CAMERA_FLASH. Добавляем условие – если вспышки нет, вызываем метод showCameraAlert(), а если вспышка есть – получаем экземпляр класса Camera.

Звук воспроизводит класс SoundPool.  В последнее время рекомендуется создание и настройка экземпляра Soundpool с помощью класса SoundPool.Builder.  Soundpool Builder доступен только на API 21+, так что нам нужно будет создать свой Soundpool по разному в зависимости от версии SDK устройства. Для Android 5 и выше будет выполняться этот метод:

@TargetApi (Build.VERSION_CODES. LOLLIPOP)
protected void createSoundPoolWithBuilder(){
      AudioAttributes attributes = new AudioAttributes.Builder()
                  .setUsage(AudioAttributes. USAGE_GAME )
                  .setContentType(AudioAttributes. CONTENT_TYPE_SONIFICATION )
                  .build();


      sp = new SoundPool.Builder().setAudioAttributes(attributes).setMaxStreams( 1).build();
}
Создаем Soundpool Builder с необходимыми атрибутами.
Для устройств, работающих под управлением 4.4 и старше используем конструктор Soundpool:
@SuppressWarnings ("deprecation" )
protected void createSoundPoolWithConstructor(){
      sp = new SoundPool( 1, AudioManager. STREAM_MUSIC , 0 );
}
На вход объекту SoundPool передаем:
– максимальное кол-во одновременно воспроизводимых файлов
– аудио-поток, который будет использоваться
– некий параметр качества, который игнорируется системой. Рекомендуется передавать туда 0
Поскольку класс deprecated, можно использовать подавление предупреждения об этом.
Теперь в onCгeate вызываем один из этих методов через проверку версии устройства:
if (Build.VERSION. SDK_INT >= Build.VERSION_CODES. LOLLIPOP ) {
      createSoundPoolWithBuilder();
} else {
      createSoundPoolWithConstructor();
}
sp .setOnLoadCompleteListener( this);
sound = sp .load( this, R.raw. click , 1 );
Методом setOnLoadCompleteListener мы устанавливаем слушателя загрузки. Загрузка аудио-файлов происходит асинхронно, и по ее окончании срабатывает метод onLoadComplete этого слушателя. Далее загружаем файлы методом load. Чтобы загрузить файл из raw, необходимо указать Context, ID raw-файла и приоритет. Приоритет также игнорируется системой, рекомендуется передавать туда 1.
Осталось написать методы включения и выключения фонарика.
Метод setFlashlightOn():
private void setFlashlightOn() {
      sp .play( sound, 1, 1, 0, 0, 1);


      new Thread( new Runnable() {
             public void run() {
      if ( camera != null) {
             params = camera .getParameters();


             if( params != null ) {
                  List supportedFlashModes = params .getSupportedFlashModes();


                   if(supportedFlashModes.contains(Parameters. FLASH_MODE_TORCH )) {
                         params .setFlashMode( Parameters. FLASH_MODE_TORCH );
                  } else if(supportedFlashModes.contains(Parameters. FLASH_MODE_ON )) {
                         params .setFlashMode( Parameters. FLASH_MODE_ON );
                  } else camera = null;


                   if( camera != null ) {
                         camera .setParameters( params);
                         camera .startPreview();
                         try  {
                               camera .setPreviewTexture( new SurfaceTexture( 0));


                        }  catch  (IOException e)  {
                              e.printStackTrace();
                        }
                  }
            }
      }
            }
      }).start();
}
Проигрываем звук методом play, которому передаем файл с такими параметрами
– громкость левого канала (от 0.0 до 1.0)
– громкость правого канала
– приоритет
– количество повторов
– скорость воспроизведения (0.5 – 2)
Дальнейшие действия будем выполнять в отдельном потоке, чтобы не перегружать основной поток интерфейса приложения. Проверяем, что объект камеры создан и получаем ее параметры. Если параметры получены, получаем список поддерживаемых камерой режимов и сохраняем его в массив. Он нам нужен для проверки, какой параметр нужно использовать для включения вспышки камеры. Дело в том, что таких параметров два у разных производителей телефонов: FLASH_MODE_TORCH или FLASH_MODE_ON. Проверяем, какой из этих параметров присутствует в массиве и присваиваем его переменной params. Далее проверяем, что camera не null, устанавливаем параметры и  вызываем метод startPreview(). Этот метод стартует режим превью камеры, нам он нужен для включения вспышки.
В принципе, этого кода достаточно для включения вспышки на многих телефонах, кроме Nexus 5. Для него нужно вызвать также метод  setPreviewTexture с конструктором класса SurfaceTexture в качестве параметра. Этот класс используется для захвата кадров из потока изображения как OpenGL ES текстуры. Поскольку мы это не используем, вместо текстуры передаем 0. Вызов последнего метода выполняем в блоке try/catch с отловом исключения IOException. Методом start запускаем поток.
Теперь метод выключения вспышки. Здесь все проще:
private void setFlashlightOff() {
      sp .play( sound, 1, 1, 0, 0, 1);
      new Thread( new Runnable() {
             public void run() {
                   if( camera != null ) {
                         params .setFlashMode(Parameters. FLASH_MODE_OFF);
                         camera .setParameters( params);
                         camera .stopPreview();
                  }
            }
      }).start();
}
Также проигрываем звук, создаем новый поток. Проверяем, что camera не null, передаем в параметр режим FLASH_MODE_OFF и  устанавливаем параметр, и методом stopPreview останавливаем работу камеры.
Теперь добавим вызов методов включения и выключения вспышки в метод onCreate, в слушатель переключателя:
mySwitch .setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {

      @Override
      public void onCheckedChanged(CompoundButton buttonView,
                                                 boolean isChecked) {

             if(isChecked){
                  setFlashlightOn();
            } else{
                  setFlashlightOff();
            }
      }
});
После окончания работы с камерой ее нужно освобождать для других приложений, пишем метод:
private void releaseCamera() {
      if ( camera != null) {
             camera.release();
             camera = null ;
      }
}
  Вызывать его будем в случаях, когда активити главного экрана приложения становится невидимым, например, когда пользователь сворачивает это приложение или запускает другое. При этом выполняется метод жизненного цикла onStop, переопределим его здесь:
@Override
protected void onStop() {
      super .onStop();
      releaseCamera();
}
Также пользователь может перейти к другому экрану, при этом вызывается метод onPause. Его мы тоже переопределим и будем освобождать камеру здесь:
@Override
protected void onPause() {
      super .onPause();
      releaseCamera();
      mySwitch.setChecked( false);
}
Также будем переводить переключатель в положение “выкл”.
При возвращении к приложению вызывается метод жизненного цикла onResume, здесь будем через проверку выполнять инициализацию камеры, а также включать вспышку и переключатель:
@Override
protected void onResume() {
      super .onResume();
      if ( camera == null) {
             camera = Camera. open();
      } else {
      setFlashlightOn();
}
      mySwitch .setChecked( true);
}
Наш фонарик готов к работе. Если запустить его в эмуляторе, который не имеет вспышки, мы увидим окно предупреждения с кнопкой выхода.  При запуске на реальном устройстве фонарик отлично работает.
На этом наш урок закончен. Вопросы задавайте в комментариях.
Коментарі: 3
  1. ayrat1@mail.ru

    При запуске на 7 андроиде пришлось самостоятельно бороться с разрешениями системы, причём такого разрешения как FLASHLIGHT моя студия никак признавать не хотела, всё работает только с android.permission.CAMERA

  2. ayrat1@mail.ru

    Для чего мы имплементили интерфейс SoundPool.OnLoadCompleteListener если его не используем?

  3. Stinger

    Привет всем я начинаюший прогромист.

Додати коментар