一、Service 启动
1、基本介绍
(1)startService()
-
其他组件通过调用 startService() 启动 Service 后,Service 可在后台无限期运行,即使启动 Service 的组件被销毁也不受影响,一般情况下 startService() 是执行单一操作,不会将执行结果返回给调用者,例如,下载文件或者上传文件,操作完成后会自动停止
-
这种方式允许多个组件同时对同一 Service 进行 startService() 操作,但只要有其中有一个组件调用了 stopSelf() 或 stopService(), 该 Service 就会被销毁
(2)bindService()
-
组件通过调用 bindService() 启动 Service ,Service 就处于绑定状态,可让调用者与 Service 进行发送请求和返回结果的操作,还可以进行进程间的通信
-
一个组件对该 Service 进行绑定,那该 Service 就不会销毁,若多个组件同时对一个 Service 进行绑定,只有全部绑定的该 Service 的组件都解绑后,Service 才会销毁
2、注意事项
-
虽然 Service 是在后台运行,但其实还是在主线程中进行所有的操作,Service 启动时除非单独进行了定义,否则没有单独开启线程且都运行在主线程中
-
任何能阻塞主线程的操作,应该在 Service 中单独开启新线程来进行操作,否则很容易出现 ANR
4、Service 生命周期
- Service 同样有生命周期回调方法
-
startService 方式的方法回调:onCreate() -> onStartCommand() -> onDestroy()
-
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
-
点击【开启服务】按钮,输出结果
I/MyService: ------------------------------ onCreate
I/MyService: ------------------------------ onStartCommand
I/MyService: ------------------------------ onStart -
点击【停止服务】按钮,输出结果
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
-
点击【绑定服务】按钮,输出结果
I/MyService: ------------------------------ onCreate
I/MyService: ------------------------------ onBind -
点击【解绑服务】按钮,输出结果
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 自定义设备类型和大小
- 创建自定义虚拟设备

- 新建硬件配置文件,主要配置屏幕大小和分辨率
- 选择硬件配置文件,完成自定义虚拟设备的创建
- 使用自定义虚拟设备
