无法启动服务从服务

3
我希望能够实现以下内容:我有一部旧手机(安卓系统),不再使用,想将其放在我们周末度假屋的窗户后面。我想用Firebase控制它。当我在特定的键上添加一个子元素时,服务应该触发手机的相机并拍照。现在我有两件事情:
  1. 一个简单的服务,监听Firebase是否有任何更改
  2. 相机服务,可以在没有任何用户交互的情况下拍照
我的问题是,我无法将这两个东西结合起来,因为我可以从活动中启动APictureCapturingService的startCapturing()方法,但无法从服务中启动。我该如何解决这个问题?谢谢
public class FiBaService extends Service  {

    public static final int MY_PERMISSIONS_REQUEST_ACCESS_CODE = 1;
    //public APictureCapturingService pictureService;
    DatabaseReference mdb;
    String user, post;

@Override
public IBinder onBind(Intent intent) {
    throw new UnsupportedOperationException("Not yet implemented");
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    handleFIBA();
    return START_STICKY;
}

@Override
public void onCreate() {
    mdb = FirebaseDatabase.getInstance().getReference();
}


@Override
public void onDestroy() {
    super.onDestroy();
    Toast.makeText(this, "Service is Destroyed", Toast.LENGTH_SHORT).show();
}


public void handleFIBA() {
    //Toast.makeText(this, "dosomethingban!", Toast.LENGTH_SHORT).show();
    FirebaseDatabase.getInstance().getReference().child
            ("phones4streams").addChildEventListener(new ChildEventListener() {
        @Override
        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
            /*
            String userName = (dataSnapshot.getValue(String.class)).toString();
            Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
            for (DataSnapshot dsp : dataSnapshot.getChildren()) {
                //Userlist.add(String.valueOf(dsp.geValue())); //add result into array list
                Toast.makeText(CameraActivity.this, dsp.toString(), Toast.LENGTH_SHORT).show();
            }
            Toast.makeText(FiBa.this, userName, Toast.LENGTH_SHORT).show();
            */
        }

        @Override
        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
            //String userName = (dataSnapshot.getValue(String.class).toString());
            //Toast.makeText(SettingsActivity.this, "Történt változás", Toast.LENGTH_SHORT)
            //        .show();
            Toast.makeText(FiBaService.this, "Starting capture!", Toast.LENGTH_SHORT)
                    .show();
            //pictureService.startCapturing(FiBaService.this);
            FirebaseDatabase.getInstance().getReference().child
                    ("phones4streams").child("alamade").setValue("csalamade");
            takePic();
        }

        @Override
        public void onChildRemoved(DataSnapshot dataSnapshot) {

        }

        @Override
        public void onChildMoved(DataSnapshot dataSnapshot, String s) {

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
        }
    });
}

APictureCapturingService - 这个服务负责拍照:

public abstract class APictureCapturingService {

private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

static {
    ORIENTATIONS.append(Surface.ROTATION_0, 90);
    ORIENTATIONS.append(Surface.ROTATION_90, 0);
    ORIENTATIONS.append(Surface.ROTATION_180, 270);
    ORIENTATIONS.append(Surface.ROTATION_270, 180);
}

private final Activity activity;

final Context context;
final CameraManager manager;

/***
 * constructor.
 *
 * @param activity the activity used to get display manager and the application context
 */
APictureCapturingService(final Activity activity) {
    this.activity = activity;
    this.context = activity.getApplicationContext();
    this.manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}

/***
 * @return  orientation
 */
int getOrientation() {
    final int rotation = this.activity.getWindowManager().getDefaultDisplay().getRotation();
    return ORIENTATIONS.get(rotation);
}


/**
 * starts pictures capturing process.
 *
 * @param listener picture capturing listener
 */
public abstract void startCapturing(final PictureCapturingListener listener);

在其他活动中,我会像这样实例化这个类:
public APictureCapturingService pictureService;
pictureService = PictureCapturingServiceImpl.getInstance(this);
pictureService.startCapturing(SettingsActivity.this);

已编辑 尝试使用广播接收器时,我得到了相同的错误:需要活动。 在此输入图片描述

PictureCapturingServiceImpl:

/**
 * The aim of this service is to secretly take pictures (without preview or opening device's
 * camera app)
 * from all available cameras using Android Camera 2 API
 *
 * @author hzitoun (zitoun.hamed@gmail.com)
 */

@TargetApi(Build.VERSION_CODES.LOLLIPOP) //NOTE: camera 2 api was added in API level 21
public class PictureCapturingServiceImpl extends APictureCapturingService {

    private static final String TAG = PictureCapturingServiceImpl.class.getSimpleName();

    private CameraDevice cameraDevice;
    private ImageReader imageReader;
    /***
     * camera ids queue.
     */
    private Queue<String> cameraIds;

    private String currentCameraId;
    private boolean cameraClosed;
    /**
     * stores a sorted map of (pictureUrlOnDisk, PictureData).
     */
    private TreeMap<String, byte[]> picturesTaken;
    private PictureCapturingListener capturingListener;

    /***
     * private constructor, meant to force the use of {@link #getInstance}  method
     */
    private PictureCapturingServiceImpl(final Activity activity) {
        super(activity);
    }

    /**
     * @param activity the activity used to get the app's context and the display manager
     * @return a new instance
     */
    public static APictureCapturingService getInstance(final Activity activity) {
        return new PictureCapturingServiceImpl(activity);
    }

    /**
     * Starts pictures capturing treatment.
     *
     * @param listener picture capturing listener
     */
    @Override
    public void startCapturing(final PictureCapturingListener listener) {
        this.picturesTaken = new TreeMap<>();
        this.capturingListener = listener;
        this.cameraIds = new LinkedList<>();
        try {
            final String[] cameraIds = manager.getCameraIdList();
            System.out.println("cameraIds length: " + cameraIds.length);
            System.out.println("cameraId[0]: " + cameraIds[0]);
            System.out.println("cameraId[1]: " + cameraIds[1]);
            if (cameraIds.length > 0) {
                this.cameraIds.addAll(Arrays.asList(cameraIds));
                // a currentCameraId lesz a cameraIds első eleme, de a caneraIds-ból remove-olva
                // lesz
                // ez az első elem
                this.currentCameraId = this.cameraIds.poll();
                openCamera();
            } else {
                //No camera detected!
                capturingListener.onDoneCapturingAllPhotos(picturesTaken);
            }
        } catch (final CameraAccessException e) {
            Log.e(TAG, "Exception occurred while accessing the list of cameras", e);
        }
    }


    private void openCamera() {
        Log.d(TAG, "opening camera " + currentCameraId);
        try {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED
                    && ActivityCompat.checkSelfPermission(context,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                manager.openCamera(currentCameraId, stateCallback, null);
            }
        } catch (final CameraAccessException e) {
            Log.e(TAG, " exception occurred while opening camera " + currentCameraId, e);
        }
    }


    private final CameraCaptureSession.CaptureCallback captureListener = new CameraCaptureSession
            .CaptureCallback() {
        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull
                CaptureRequest request,
                                       @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
            if (picturesTaken.lastEntry() != null) {
                capturingListener.onCaptureDone(picturesTaken.lastEntry().getKey(), picturesTaken
                        .lastEntry().getValue());
                Log.i(TAG, "done taking picture from camera " + cameraDevice.getId());
            }
            closeCamera();
        }
    };


    private final ImageReader.OnImageAvailableListener onImageAvailableListener = (ImageReader
                                                                                           imReader) -> {
        final Image image = imReader.acquireLatestImage();
        final ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        final byte[] bytes = new byte[buffer.capacity()];
        buffer.get(bytes);
        saveImageToDisk(bytes, timeStamp);
        image.close();
    };


    private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            cameraClosed = false;
            Log.d(TAG, "camera " + camera.getId() + " opened");
            cameraDevice = camera;
            Log.i(TAG, "Taking picture from camera " + camera.getId());
            //Take the picture after some delay. It may resolve getting a black dark photos.
            new Handler().postDelayed(() -> {
                try {
                    takePicture();
                } catch (final CameraAccessException e) {
                    Log.e(TAG, " exception occurred while taking picture from " +
                            currentCameraId, e);
                }
            }, 500);
        }


        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            Log.d(TAG, " camera " + camera.getId() + " disconnected");
            if (cameraDevice != null && !cameraClosed) {
                cameraClosed = true;
                cameraDevice.close();
            }
        }


        @Override
        public void onClosed(@NonNull CameraDevice camera) {
            cameraClosed = true;
            Log.d(TAG, "camera " + camera.getId() + " closed");
            //once the current camera has been closed, start taking another picture
            //if (!cameraIds.isEmpty()) {
            if (cameraIds.size() != 1 && !cameraIds.isEmpty()) {
                takeAnotherPicture();
            } else {
                capturingListener.onDoneCapturingAllPhotos(picturesTaken);
            }
        }


        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.e(TAG, "camera in error, int code " + error);
            if (cameraDevice != null && !cameraClosed) {
                cameraDevice.close();
            }
        }
    };


    private void takePicture() throws CameraAccessException {
        if (null == cameraDevice) {
            Log.e(TAG, "cameraDevice is null");
            return;
        }
        final CameraCharacteristics characteristics = manager.getCameraCharacteristics
                (cameraDevice.getId());
        Size[] jpegSizes = null;
        StreamConfigurationMap streamConfigurationMap = characteristics.get(CameraCharacteristics
                .SCALER_STREAM_CONFIGURATION_MAP);
        if (streamConfigurationMap != null) {
            jpegSizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
        }
        final boolean jpegSizesNotEmpty = jpegSizes != null && 0 < jpegSizes.length;
        int width = jpegSizesNotEmpty ? jpegSizes[0].getWidth() : 640;
        int height = jpegSizesNotEmpty ? jpegSizes[0].getHeight() : 480;
        final ImageReader reader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 1);
        final List<Surface> outputSurfaces = new ArrayList<>();
        outputSurfaces.add(reader.getSurface());
        final CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest
                (CameraDevice.TEMPLATE_STILL_CAPTURE);
        captureBuilder.addTarget(reader.getSurface());
        captureBuilder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
        captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation());
        reader.setOnImageAvailableListener(onImageAvailableListener, null);
        cameraDevice.createCaptureSession(outputSurfaces, new CameraCaptureSession.StateCallback() {
                    @Override
                    public void onConfigured(@NonNull CameraCaptureSession session) {
                        try {
                            session.capture(captureBuilder.build(), captureListener, null);
                        } catch (final CameraAccessException e) {
                            Log.e(TAG, " exception occurred while accessing " + currentCameraId, e);
                        }
                    }

                    @Override
                    public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                    }
                }
                , null);
    }

    //cYou - átírva a képek mentési helye a cyou mappába 240. sor
    private void saveImageToDisk(final byte[] bytes, String timeStamp) {
        final String cameraId = this.cameraDevice == null ? UUID.randomUUID().toString() : this
                .cameraDevice.getId();
        final File file = new File(Environment.getExternalStorageDirectory() + "/mup/" +
                Sta.getCurrentTimeStamp() + "_pic.jpg");
        try (final OutputStream output = new FileOutputStream(file)) {
            output.write(bytes);
            this.picturesTaken.put(file.getPath(), bytes);
        } catch (final IOException e) {
            Log.e(TAG, "Exception occurred while saving picture to external storage ", e);
        }
    }

    private void takeAnotherPicture() {
        this.currentCameraId = this.cameraIds.poll();
        openCamera();
    }

    private void closeCamera() {
        Log.d(TAG, "closing camera " + cameraDevice.getId());
        if (null != cameraDevice && !cameraClosed) {
            cameraDevice.close();
            cameraDevice = null;
        }
        if (null != imageReader) {
            imageReader.close();
            imageReader = null;
        }
    }

为什么不从FiBaService启动一个Activity,然后从那里启动APictureCapturingService呢?虽然在您的用例中不会有用户在屏幕上看到它,但在没有Activity的情况下尝试捕获图像可能比较困难(我认为相机API可能没有为此设计,因为这将允许恶意使用相机)。您可以在Activity中使用窗口标志来确保即使在屏幕锁定/关闭时启动Activity,它也会显示,类似于闹钟的工作方式。 - Patrick Grayson
请直接发布代码而不是截图。同时展示PictureCapturingServiceImpl及其getInstance()函数。你的服务在构造函数中接受一个参数Activity的目的是什么?为什么要在它上面调用getApplicationContext()?因为Service已经扩展了Context,所以这些都是不必要的。 - Code-Apprentice
请注意,Activity通常会在屏幕上可视化,但Service通常不是可视的。您应该使用其中一个,而不是在同一类中同时使用两者。 - Code-Apprentice
PictureCapturingServiceImpl 已添加。 - Denis Dore
1个回答

1
一种解决方案是Firebase服务广播一条消息。接着实现一个BroadcastReceiver来监听该消息,当收到消息时启动你的图片服务。
我没有看到你的代码中为什么需要一个Activity。似乎你正在尝试从Activity中获取Context。但Service已经作为Context,所以你可以直接使用它,并大大简化你的构造函数:
APictureCapturingService() {
    this.manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
}

现在,我假设你也可以去掉PictureCapturingServiceImpl.getInstance()中的Activity参数。

谢谢你的回答!我还没有使用过广播接收器,但是我应该把广播接收器放在哪里?因为现在只能从一个活动开始拍照,我想在后台进行拍照。 - Denis Dore
@DenisDore 我建议你开始用谷歌做一些研究。Android官方开发网站有教程,告诉你如何实现广播接收器。(提示:它是一个独立的类。) - Code-Apprentice
我做了研究,你说的对,广播接收器并不是什么高深莫测的东西,但我得到了相同的结果。你能否看一下我编辑过的帖子部分?谢谢。 - Denis Dore

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接