background

Develop a simple flash application.

  1. You can use a certain key to control the flash switch (the company is working on Android devices, system customization, so you have permission to listen for key values)
  2. There is an icon on the flash page that controls the flash switch

The flash app has a single screen that shows whether it is currently on or off.

However, I noticed that sometimes the flash status was always gray in the interface.

code

I wrote a singleton class,FlashLightUtil, to control the start of the flash.

package com.seuic.flashlight.util;

import android.content.Context;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.util.Log;

import com.seuic.flashlight.MyApplication;

import java.lang.ref.WeakReference;


public class FlashLightUtil {
    private volatile static FlashLightUtil instance;
    private boolean isFlashLightOn = false;

    private Context mContext;
    private WeakReference<FlashLightStatusListener> flashLightStatusListener = null;

    private FlashLightUtil(Context context) {
        mContext = context;
    }

    public static FlashLightUtil getInstance(a) {
        if (instance == null) {
            synchronized (FlashLightUtil.class) {
                if (null == instance) {
                    instance = newFlashLightUtil(MyApplication.mApplication); }}}return instance;
    }

    private static final String TAG = "FlashLightUtil";

    public boolean isFlashLightOn(a) {
        return isFlashLightOn;
    }

    public synchronized void toggleFlashLight(a) { isFlashLightOn = ! isFlashLightOn; changeFlashLight(isFlashLightOn); }public void changeFlashLight(boolean open) {
        try {
            //获取CameraManager
            CameraManager mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
            // Get all camera device ids of the current phone
            String[] ids = mCameraManager.getCameraIdList();
            for (String id : ids) {
                CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
                // Check whether the camera component contains a flash
                Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
                Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
                if(flashAvailable ! =null&& flashAvailable && lensFacing ! =null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
                    notifyChange(open);
                    // Turn on or off the flashlightmCameraManager.setTorchMode(id, open); }}}catch(Exception e) { e.printStackTrace(); }}public boolean supportFlashLight(a) {
        boolean isSupport = false;
        try {
            //获取CameraManager
            CameraManager mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
            // Get all camera device ids of the current phone
            String[] ids = mCameraManager.getCameraIdList();
            for (String id : ids) {
                CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
                // Check whether the camera component contains a flash
                Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
                Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
                if(flashAvailable ! =null&& flashAvailable && lensFacing ! =null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
                    // Turn on or off the flashlight
                    isSupport = true; }}}catch (Exception e) {
            e.printStackTrace();
        }
        return isSupport;
    }

    public void setFlashLightStatusListener(FlashLightStatusListener flashLightStatusListener) {
        this.flashLightStatusListener = new WeakReference<>(flashLightStatusListener);
    }

    private void notifyChange(boolean open) {
        if (this.flashLightStatusListener ! =null) { FlashLightStatusListener f = flashLightStatusListener.get(); f.onStatusChange(open); }}public interface FlashLightStatusListener {
        void onStatusChange(boolean isOpen); }}Copy the code

Then a mainActivity. kt code looks like this:

package com.seuic.flashlight

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.ImageButton
import com.seuic.flashlight.service.PptService
import com.seuic.flashlight.util.FlashLightUtil

class MainActivity : AppCompatActivity(a){
    companion object{
        private const val TAG = "MainActivity"
    }
    private val flashLightUtil by lazy { FlashLightUtil.getInstance() }
    private val ivControl by lazy { findViewById<ImageButton>(R.id.iv_control) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ivControl.setOnClickListener {
            flashLightUtil.toggleFlashLight()
        }
        flashLightUtil.setFlashLightStatusListener { isOpen ->
            if (isOpen) {
                ivControl.setBackgroundResource(R.drawable.ic_flash_light_open)
            } else {
                ivControl.setBackgroundResource(R.drawable.ic_flash_light_colse)
            }
        }
        startService(Intent(this.application, KeyService::class.java))
    }

    override fun onResume(a) {
        super.onResume()
        if (flashLightUtil.isFlashLightOn) {
            ivControl.setBackgroundResource(R.drawable.ic_flash_light_open)
        } else {
            ivControl.setBackgroundResource(R.drawable.ic_flash_light_colse)
        }
    }
}
Copy the code

There is also a service that listens for custom keys (because it is a system application, there is no need to use the foreground service to keep alive).

class KeyService : Service() {companion object {
        private const val TAG = "PptService"
        private const val KEY_MONITORING = "252"
    }

    private val flashLightUtil by lazy { FlashLightUtil.getInstance() }
    private val mScanKeyService by lazy { ScanKeyService.getInstance() }
    private val mCallback: IKeyEventCallback = object : IKeyEventCallback.Stub() {
        @Throws(RemoteException::class)
        override fun onKeyDown(keyCode: Int) {
            flashLightUtil.toggleFlashLight()
        }

        @Throws(RemoteException::class)
        override fun onKeyUp(keyCode: Int) = Unit
    }

    override fun onCreate(a) {
        super.onCreate()
        Log.i(TAG, "onCreate: ")
        mScanKeyService.registerCallback(
            mCallback,
            KEY_MONITORING
        )
    }

    override fun onStartCommand(intent: Intent? , flags:Int, startId: Int): Int {
        Log.i(TAG, "onStartCommand: ")

        return START_STICKY
    }

    override fun onBind(intent: Intent?).: IBinder? {
        return null
    }

    override fun onDestroy(a) {
        super.onDestroy()
        Log.i(TAG, "onDestroy: ")
        mScanKeyService.unregisterCallback(mCallback)
    }
}
Copy the code

I found the code didn’t go to ivControl. The setBackgroundResource this place.

why

Code didn’t go to this place because setFlashLightStatusListener incoming object has no strong references, so flashLightStatusListener. The get (); You get a NULL object. At the beginning, the reason why WeakReference is used to save the flashLightStatusListener object is to prevent strong reference to the Object of MainActivity and affect the reclamation of MainActivity (there is still a service living in the background after MainActivity exits). The object that holds FlashLightUtil), but I actually passed in a local object instead of the properties of MainActivity. The local FlashLightStatusListener object is reclaimed because there is no strong reference to it.