Android 开发 - Service、Camera、Layout Design 自定义设备类型和大小

一、Service 启动

1、基本介绍
(1)startService()
  1. 其他组件通过调用 startService() 启动 Service 后,Service 可在后台无限期运行,即使启动 Service 的组件被销毁也不受影响,一般情况下 startService() 是执行单一操作,不会将执行结果返回给调用者,例如,下载文件或者上传文件,操作完成后会自动停止

  2. 这种方式允许多个组件同时对同一 Service 进行 startService() 操作,但只要有其中有一个组件调用了 stopSelf() 或 stopService(), 该 Service 就会被销毁

(2)bindService()
  1. 组件通过调用 bindService() 启动 Service ,Service 就处于绑定状态,可让调用者与 Service 进行发送请求和返回结果的操作,还可以进行进程间的通信

  2. 一个组件对该 Service 进行绑定,那该 Service 就不会销毁,若多个组件同时对一个 Service 进行绑定,只有全部绑定的该 Service 的组件都解绑后,Service 才会销毁

2、注意事项
  1. 虽然 Service 是在后台运行,但其实还是在主线程中进行所有的操作,Service 启动时除非单独进行了定义,否则没有单独开启线程且都运行在主线程中

  2. 任何能阻塞主线程的操作,应该在 Service 中单独开启新线程来进行操作,否则很容易出现 ANR

4、Service 生命周期
  • Service 同样有生命周期回调方法
  1. startService 方式的方法回调:onCreate() -> onStartCommand() -> onDestroy()

  2. bindService 方式的方法回调:onCreate() -> onBind() -> onUnbind() -> onDestroy()


二、Service 使用

1、startService
(1)Service
  • MyService.java
java 复制代码
package com.my.other.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable;

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "------------------------------ onCreate");
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.i(TAG, "------------------------------ onStart");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "------------------------------ onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "------------------------------ onBind");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "------------------------------ onUnbind");
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "------------------------------ onDestroy");
    }
}
(2)AndroidManifest
xml 复制代码
<application ...>  
    <service android:name=".service.MyService" />
    
    ...  
</application>
(3)Activity Layout
  • activity_start_service.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".StartServiceActivity">

    <Button
        android:id="@+id/btn_start_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="开启服务"
        android:onClick="startService"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_stop_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="停止服务"
        android:onClick="stopService"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_start_service" />
</androidx.constraintlayout.widget.ConstraintLayout>
(4)Activity Code
  • StartServiceActivity.java
java 复制代码
package com.my.other;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import com.my.other.service.MyService;

public class StartServiceActivity extends AppCompatActivity {

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

    public void startService(View view) {
        startService(new Intent(this, MyService.class));
    }

    public void stopService(View view) {
        stopService(new Intent(this, MyService.class));
    }
}
(5)Test
  1. 点击【开启服务】按钮,输出结果

    I/MyService: ------------------------------ onCreate
    I/MyService: ------------------------------ onStartCommand
    I/MyService: ------------------------------ onStart

  2. 点击【停止服务】按钮,输出结果

    I/MyService: ------------------------------ onDestroy

2、BindService
(1)Activity Layout
  • activity_bind_service.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".BindServiceActivity">

    <Button
        android:id="@+id/btn_my_bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="绑定服务"
        android:onClick="myBindService"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_my_unbind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="40dp"
        android:text="解绑服务"
        android:onClick="myUnbindService"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_my_bind_service" />
</androidx.constraintlayout.widget.ConstraintLayout>
(2)Activity Code
  • BindServiceActivity.java
java 复制代码
package com.my.other;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;

import com.my.other.service.MyService;

public class BindServiceActivity extends AppCompatActivity {

    private static final String TAG = "BindServiceActivity";

    // BindServiceActivity 与 MyService 的桥梁
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "------------------------------ onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "------------------------------ onServiceDisconnected");
        }
    };

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

    public void myBindService(View view) {
        bindService(new Intent(this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE);
    }

    public void myUnbindService(View view) {
        unbindService(serviceConnection);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}
(3)Test
  1. 点击【绑定服务】按钮,输出结果

    I/MyService: ------------------------------ onCreate
    I/MyService: ------------------------------ onBind

  2. 点击【解绑服务】按钮,输出结果

    I/MyService: ------------------------------ onUnbind
    I/MyService: ------------------------------ onDestroy


三、Camera 使用

1、基本使用
(1)Activity Layout
  • activity_camera_test.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E0F7FA"
    tools:context=".CameraTestActivity">

    <SurfaceView
        android:id="@+id/sv"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(2)Activity Code
  • CameraTestActivity.java
java 复制代码
package com.my.camera;

import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

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

@SuppressWarnings("all")
public class CameraTestActivity extends AppCompatActivity {
    public static final String TAG = CameraTestActivity.class.getSimpleName();

    private Camera camera;
    private SurfaceView sv;
    private SurfaceHolder surfaceHolder;

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

        // 保持屏幕常亮
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        sv = findViewById(R.id.sv);
        surfaceHolder = sv.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                Log.i(TAG, "++++++++++++++++++++++++++++++ surfaceCreated");
                openCamera();
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                Log.i(TAG, "++++++++++++++++++++++++++++++ surfaceChanged");
                if (camera == null) return;
                Camera.Parameters parameters = camera.getParameters();
                List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
                Camera.Size previewSize = getOptimalPreviewSize(supportedPreviewSizes, width, height);
                parameters.setPreviewSize(previewSize.width, previewSize.height);
                camera.setParameters(parameters);
                camera.startPreview();
            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
                Log.i(TAG, "++++++++++++++++++++++++++++++ surfaceDestroyed");
            }
        });
    }

    private void openCamera() {
        try {
            if (camera != null) return;
            camera = Camera.open();
            camera.setPreviewDisplay(surfaceHolder);
            camera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {

                    // 这里可以处理预览帧数据
                    Log.i(TAG, "得到预览帧数据 ------------------------------ size:" + data.length);
                }
            });
            camera.setDisplayOrientation(90);
        } catch (IOException e) {
            e.printStackTrace();
            releaseCamera();
        }
    }

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

    /**
     * 根据 SurfaceView 的尺寸和相机支持的预览尺寸来选择最优的预览尺寸
     *
     * @param sizes 相机支持的预览尺寸列表
     * @param w     SurfaceView 的宽度
     * @param h     SurfaceView 的高度
     * @return 最优的预览尺寸,如果没有合适的尺寸则返回 null
     */
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        if (sizes == null) return null;

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        int targetHeight = h;

        // 遍历所有支持的预览尺寸
        for (Camera.Size size : sizes) {

            // 检查宽高比是否接近目标宽高比
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;

            // 计算当前尺寸与目标尺寸的宽度差异
            // 如果差异小于当前最小差异,则更新最优尺寸和最小差异
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // 如果找不到接近目标宽高比的尺寸,则选择最接近目标高度的尺寸
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }
}
  • 输出结果

    I/CameraTestActivity: ++++++++++++++++++++++++++++++ surfaceCreated
    I/CameraTestActivity: ++++++++++++++++++++++++++++++ surfaceChanged
    I/CameraTestActivity: 得到预览帧数据 ------------------------------ size:1775616
    I/CameraTestActivity: 得到预览帧数据 ------------------------------ size:1775616
    I/CameraTestActivity: 得到预览帧数据 ------------------------------ size:1775616

2、优化使用
(1)Activity Layout
  • activity_camera_callback_buffer.xml
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E0F7FA"
    tools:context=".CameraCallbackBufferActivity">

    <SurfaceView
        android:id="@+id/sv"
        android:layout_width="300dp"
        android:layout_height="300dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(2)Activity Code
  • CameraCallbackBufferActivity.java
java 复制代码
package com.my.camera;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

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

@SuppressWarnings("all")
public class CameraCallbackBufferActivity extends AppCompatActivity {

    public static final String TAG = CameraCallbackBufferActivity.class.getSimpleName();

    private Camera camera;
    private SurfaceView sv;
    private SurfaceHolder surfaceHolder;
    private List<byte[]> previewBuffers;

    private int backCameraId;
    private Camera.CameraInfo backCameraInfo;
    private int frontCameraId;
    private Camera.CameraInfo frontCameraInfo;

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

        getCameraInfo();

        sv = findViewById(R.id.sv);
        surfaceHolder = sv.getHolder();
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(@NonNull SurfaceHolder holder) {
                openCamera();
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
                if (camera == null) return;
                camera.startPreview();
            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            }
        });
    }

    private void getCameraInfo() {
        int numberOfCameras = Camera.getNumberOfCameras();// 获取摄像头个数
        Log.i(TAG, "------------------------------ 摄像头个数:" + numberOfCameras);
        for (int cameraId = 0; cameraId < numberOfCameras; cameraId++) {
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId, cameraInfo);
            if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                Log.i(TAG, "------------------------------ 后置摄像头,cameraId 为:" + cameraId);
                backCameraId = cameraId;
                backCameraInfo = cameraInfo;
            } else if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                Log.i(TAG, "------------------------------ 前置摄像头,cameraId 为:" + cameraId);
                frontCameraId = cameraId;
                frontCameraInfo = cameraInfo;
            } else {
                Log.i(TAG, "------------------------------ 其他摄像头,cameraId 为:" + cameraId);
            }
            Log.i(TAG, "---------- facing 为:" + cameraInfo.facing);
            Log.i(TAG, "---------- orientation 为:" + cameraInfo.orientation);
            Log.i(TAG, "---------- canDisableShutterSound 为:" + cameraInfo.canDisableShutterSound);
        }
    }

    private void openCamera() {
        try {
            if (camera != null) return;
            camera = Camera.open(frontCameraId);
            if (camera == null) return;
            Camera.Parameters parameters = camera.getParameters();
            List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
            Camera.Size optimalPreviewSize = getOptimalPreviewSize(supportedPreviewSizes, sv.getWidth(), sv.getHeight());
            parameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
            parameters.setPreviewFormat(ImageFormat.YV12);
            camera.setParameters(parameters);

            int bufferSize = optimalPreviewSize.width * optimalPreviewSize.height * ImageFormat.getBitsPerPixel(ImageFormat.YV12) / 8;
            previewBuffers = new ArrayList<>();
            for (int i = 0; i < 3; i++) { // 通常需要 3 个缓冲区来避免丢失帧
                byte[] buffer = new byte[bufferSize];
                previewBuffers.add(buffer);
            }
            camera.setPreviewCallbackWithBuffer(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {

                    // 处理预览帧数据
                    Camera.Parameters parameters = camera.getParameters();
                    Camera.Size size = parameters.getPreviewSize();
                    if (size != null) processFrame(data, size.width, size.height);

                    // 回收缓冲区供相机再次使用
                    camera.addCallbackBuffer(data);
                }
            });

            camera.addCallbackBuffer(previewBuffers.get(0));
            camera.addCallbackBuffer(previewBuffers.get(1));
            camera.addCallbackBuffer(previewBuffers.get(2));
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
            releaseCamera();
        }
    }

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

    /**
     * 根据 SurfaceView 的尺寸和相机支持的预览尺寸来选择最优的预览尺寸
     *
     * @param sizes 相机支持的预览尺寸列表
     * @param w     SurfaceView 的宽度
     * @param h     SurfaceView 的高度
     * @return 最优的预览尺寸,如果没有合适的尺寸则返回 null
     */
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
        if (sizes == null) return null;

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio = (double) h / w;
        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;
        int targetHeight = h;

        // 遍历所有支持的预览尺寸
        for (Camera.Size size : sizes) {

            // 检查宽高比是否接近目标宽高比
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;

            // 计算当前尺寸与目标尺寸的宽度差异
            // 如果差异小于当前最小差异,则更新最优尺寸和最小差异
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        // 如果找不到接近目标宽高比的尺寸,则选择最接近目标高度的尺寸
        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }

        return optimalSize;
    }

    private void processFrame(byte[] data, int width, int height) {

        // 在这里处理预览帧数据,例如转换为位图、进行图像处理等
        Log.i(TAG, "------------------------------ Received preview frame: " + data.length + " bytes");
    }

    @Override
    protected void onResume() {
        super.onResume();
        openCamera();
    }

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

四、Layout Design 自定义设备类型和大小

  1. 创建自定义虚拟设备
  1. 新建硬件配置文件,主要配置屏幕大小和分辨率

  1. 选择硬件配置文件,完成自定义虚拟设备的创建

  1. 使用自定义虚拟设备