Android Realizes Background Service Photographing Function

  • 2021-09-11 21:26:23
  • OfStack

1. Background introduction

Recently, I encountered a requirement in the project to realize a background photo function. 1 began to look for solutions on the Internet, and tried many ways to achieve them, but there was no satisfactory solution. However, the difficulty is determined: the photo should be previewed first, and then the photo method should be called. The problem comes with it. Since we want to take pictures in the background, we hope to do it in Service or asynchronous threads, which is somewhat contradictory to the preview step. Is there any way to preview and take pictures normally without letting users notice? Presumably, everyone will also think of a clever way: hide the preview interface.

Illustration 1, this is just a solution that I think of in my exploration, which can solve the business needs well. I'm not sure how many mobile phone manufacturers take photos when they provide the "retrieve mobile phone" function. If you have a better implementation plan, you may wish to communicate 1.

Whether this function violates the privacy of users, affects the safety of users and so on, is not within the scope of our consideration and discussion.

2. Introduction to the programme

The implementation steps of the scheme are roughly as follows:

1. Initialize the preview interface (core part) of taking pictures;
2. Get the camera Camera when taking pictures, and set the preview interface for Camera;
3. Open the preview, complete the photo, and release Camera resources (important)
4. Save, Rotate, Upload... (Business decides)

First, introduce the business requirements: from the user login to logout, take photos, save and upload after receiving the instruction of taking photos in the background. The implementation of each step will be described in detail based on this business scenario.

1. Initialize the preview interface for taking pictures

During the test, it was found that the preview interface for taking pictures needs to be generated under the condition that it can be displayed in order to take pictures normally. If the SurfaceView instance is directly created as the preview interface, and then the native layer exception will be thrown when taking pictures directly: take_failed. I thought about looking at the source code to find the cause of the problem, and found that the functional codes of the camera core are all on the native layer, so put it down for the time being, assuming that the preview interface 1 should be displayed on the top layer 1 when taking pictures.

Since the application needs to meet this condition whether it is in the foreground or returning to the desktop by pressing home, the preview interface should be global, which is easily associated with using a global window as the carrier of the preview interface. If this global window is invisible, it will not affect the normal interaction of the following interface. Therefore, I thought of using the global context to get the WindowManager object to manage this global window. Next, look directly at the code:


package com.yuexunit.zjjk.service; 
 
import com.yuexunit.zjjk.util.Logger; 
 
import android.content.Context; 
import android.view.SurfaceView; 
import android.view.WindowManager; 
import android.view.WindowManager.LayoutParams; 
 
/** 
 *  Hidden global window for background photo taking  
 * 
 * @author WuRS 
 */ 
public class CameraWindow { 
 
  private static final String TAG = CameraWindow.class.getSimpleName(); 
 
  private static WindowManager windowManager; 
 
  private static Context applicationContext; 
 
  private static SurfaceView dummyCameraView; 
 
  /** 
   *  Show Global Window  
   * 
   * @param context 
   */ 
  public static void show(Context context) { 
    if (applicationContext == null) { 
      applicationContext = context.getApplicationContext(); 
      windowManager = (WindowManager) applicationContext 
          .getSystemService(Context.WINDOW_SERVICE); 
      dummyCameraView = new SurfaceView(applicationContext); 
      LayoutParams params = new LayoutParams(); 
      params.width = 1; 
      params.height = 1; 
      params.alpha = 0; 
      params.type = LayoutParams.TYPE_SYSTEM_ALERT; 
      //  Mask click events  
      params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL 
          | LayoutParams.FLAG_NOT_FOCUSABLE 
          | LayoutParams.FLAG_NOT_TOUCHABLE; 
      windowManager.addView(dummyCameraView, params); 
      Logger.d(TAG, TAG + " showing"); 
    } 
  } 
 
  /** 
   * @return  Get a view of the window  
   */ 
  public static SurfaceView getDummyCameraView() { 
    return dummyCameraView; 
  } 
 
  /** 
   *  Hide window  
   */ 
  public static void dismiss() { 
    try { 
      if (windowManager != null && dummyCameraView != null) { 
        windowManager.removeView(dummyCameraView); 
        Logger.d(TAG, TAG + " dismissed"); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
    } 
  } 
} 

The code is simple, the main function is to display this window, get the SurfaceView for preview, and close the window.

In this business, the show method can be called directly from the custom Application class. In this way, after the application starts, the window will be there, and only after the application is destroyed (note that ending all Activity will not close, because it is initialized in Application, and its life cycle will be application-level, unless the dismiss method is actively called and closed).

Completed the initialization of the preview interface, and the whole implementation is actually very simple. Perhaps the problem encountered by many people is how to take pictures when they are stuck without a preview interface. I hope that such an ingenious way can help everyone to solve problems from another angle when they encounter problems that cannot be solved directly in future projects.

2. Complete the Service photo function

The following steps above will be merged here. Start with the code:


package com.yuexunit.zjjk.service; 
 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
 
import android.app.Service; 
import android.content.Intent; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.BitmapFactory.Options; 
import android.hardware.Camera; 
import android.hardware.Camera.CameraInfo; 
import android.hardware.Camera.PictureCallback; 
import android.os.IBinder; 
import android.os.Message; 
import android.text.TextUtils; 
import android.view.SurfaceView; 
 
import com.yuexunit.sortnetwork.android4task.UiHandler; 
import com.yuexunit.sortnetwork.task.TaskStatus; 
import com.yuexunit.zjjk.network.RequestHttp; 
import com.yuexunit.zjjk.util.FilePathUtil; 
import com.yuexunit.zjjk.util.ImageCompressUtil; 
import com.yuexunit.zjjk.util.Logger; 
import com.yuexunit.zjjk.util.WakeLockManager; 
 
/** 
 *  Background photo service, used with the global window  
 * 
 * @author WuRS 
 */ 
public class CameraService extends Service implements PictureCallback { 
 
  private static final String TAG = CameraService.class.getSimpleName(); 
 
  private Camera mCamera; 
 
  private boolean isRunning; //  Are you already monitoring and taking pictures  
 
  private String commandId; //  Instruction ID 
 
  @Override 
  public void onCreate() { 
    Logger.d(TAG, "onCreate..."); 
    super.onCreate(); 
  } 
 
  @Override 
  public int onStartCommand(Intent intent, int flags, int startId) { 
    WakeLockManager.acquire(this); 
    Logger.d(TAG, "onStartCommand..."); 
    startTakePic(intent); 
    return START_NOT_STICKY; 
  } 
 
  private void startTakePic(Intent intent) { 
    if (!isRunning) { 
      commandId = intent.getStringExtra("commandId"); 
      SurfaceView preview = CameraWindow.getDummyCameraView(); 
      if (!TextUtils.isEmpty(commandId) && preview != null) { 
        autoTakePic(preview); 
      } else { 
        stopSelf(); 
      } 
    } 
  } 
 
  private void autoTakePic(SurfaceView preview) { 
    Logger.d(TAG, "autoTakePic..."); 
    isRunning = true; 
    mCamera = getFacingFrontCamera(); 
    if (mCamera == null) { 
      Logger.w(TAG, "getFacingFrontCamera return null"); 
      stopSelf(); 
      return; 
    } 
    try { 
      mCamera.setPreviewDisplay(preview.getHolder()); 
      mCamera.startPreview();//  Start previewing  
      //  Prevent the brightness of photos taken by some mobile phones from being insufficient  
      Thread.sleep(200); 
      takePicture(); 
    } catch (Exception e) { 
      e.printStackTrace(); 
      releaseCamera(); 
      stopSelf(); 
    } 
  } 
 
  private void takePicture() throws Exception { 
    Logger.d(TAG, "takePicture..."); 
    try { 
      mCamera.takePicture(null, null, this); 
    } catch (Exception e) { 
      Logger.d(TAG, "takePicture failed!"); 
      e.printStackTrace(); 
      throw e; 
    } 
  } 
 
  private Camera getFacingFrontCamera() { 
    CameraInfo cameraInfo = new CameraInfo(); 
    int numberOfCameras = Camera.getNumberOfCameras(); 
    for (int i = 0; i < numberOfCameras; i++) { 
      Camera.getCameraInfo(i, cameraInfo); 
      if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { 
        try { 
          return Camera.open(i); 
        } catch (Exception e) { 
          e.printStackTrace(); 
        } 
      } 
    } 
    return null; 
  } 
 
  @Override 
  public void onPictureTaken(byte[] data, Camera camera) { 
    Logger.d(TAG, "onPictureTaken..."); 
    releaseCamera(); 
    try { 
      //  Greater than 500K Compression to prevent memory overflow  
      Options opts = null; 
      if (data.length > 500 * 1024) { 
        opts = new Options(); 
        opts.inSampleSize = 2; 
      } 
      Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, 
          opts); 
      //  Rotate 270 Degrees  
      Bitmap newBitmap = ImageCompressUtil.rotateBitmap(bitmap, 270); 
      //  Save  
      String fullFileName = FilePathUtil.getMonitorPicPath() 
          + System.currentTimeMillis() + ".jpeg"; 
      File saveFile = ImageCompressUtil.convertBmpToFile(newBitmap, 
          fullFileName); 
      ImageCompressUtil.recyleBitmap(newBitmap); 
      if (saveFile != null) { 
        //  Upload  
        RequestHttp.uploadMonitorPic(callbackHandler, commandId, 
            saveFile); 
      } else { 
        //  Save failed, close  
        stopSelf(); 
      } 
    } catch (Exception e) { 
      e.printStackTrace(); 
      stopSelf(); 
    } 
  } 
 
  private UiHandler callbackHandler = new UiHandler() { 
 
    @Override 
    public void receiverMessage(Message msg) { 
      switch (msg.arg1) { 
      case TaskStatus.LISTENNERTIMEOUT: 
      case TaskStatus.ERROR: 
      case TaskStatus.FINISHED: 
        //  End of request, shutdown of service  
        stopSelf(); 
        break; 
      } 
    } 
  }; 
 
  //  Save photos  
  private boolean savePic(byte[] data, File savefile) { 
    FileOutputStream fos = null; 
    try { 
      fos = new FileOutputStream(savefile); 
      fos.write(data); 
      fos.flush(); 
      fos.close(); 
      return true; 
    } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
    } catch (IOException e) { 
      e.printStackTrace(); 
    } finally { 
      if (fos != null) { 
        try { 
          fos.close(); 
        } catch (IOException e) { 
          e.printStackTrace(); 
        } 
      } 
    } 
    return false; 
  } 
 
  private void releaseCamera() { 
    if (mCamera != null) { 
      Logger.d(TAG, "releaseCamera..."); 
      mCamera.stopPreview(); 
      mCamera.release(); 
      mCamera = null; 
    } 
  } 
 
  @Override 
  public void onDestroy() { 
    super.onDestroy(); 
    Logger.d(TAG, "onDestroy..."); 
    commandId = null; 
    isRunning = false; 
    FilePathUtil.deleteMonitorUploadFiles(); 
    releaseCamera(); 
    WakeLockManager.release(); 
  } 
 
  @Override 
  public IBinder onBind(Intent intent) { 
    return null; 
  } 
} 

There is not much code, but there are a few points that need special attention.

1. The camera can't be used when talking, or other applications can't get the camera when holding the camera, so it is necessary to capture the anomaly of camera.Open () to prevent application errors when the camera can't be obtained;

2. When testing with Huawei camera, I started to preview and take photos immediately, and found that the brightness of the obtained photos was very low. The reason was only speculation, and it was necessary to check the data. So for the time being, the solution is to let the thread sleep 200ms, and then call the photo.

3. When Camera resources are not used or any abnormality occurs, please remember to release Camera resources. Otherwise, in order to cause the camera to be held directly by 1, other applications including the camera of the system cannot be used, so you can only restart the mobile phone to solve the problem. We can optimize the code and dispose of the abnormal business logic. Or, use a custom UncaughtExceptionHandler to handle uncaught exceptions.

4. The WakeLocaManager class in the code is a wake-up lock management class encapsulated by myself, which is also a point that everyone needs to pay special attention to when dealing with key business in the background, so as to ensure that the system will not go to sleep when dealing with business logic. When the business logic is processed, release the wake-up lock and let the system go to sleep.

3. Summary

There are many problems in this scheme, but only one idea is provided. The global window is the core of this scheme. Camera operation needs to be careful, When getting, you need to catch exceptions (native exceptions, error in connecting cameras, I believe you have also encountered them). Release in time when it is not used or abnormal (you can write the camera object as static, and then release the camera in the global exception capture, so as to prevent the camera from being held abnormally when the exception is applied during the period of holding the camera), otherwise other camera applications will not be used.
The code can be used with a little modification. Remember to add relevant permissions. The following are the system window, wake-up lock and camera permissions. If autofocus is used to take pictures, remember to declare the following uses-feature labels. Other common permissions will not be repeated here.


<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />

Related articles: