一、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
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";@Overridepublic void onCreate() {super.onCreate();Log.i(TAG, "------------------------------ onCreate");}@Overridepublic void onStart(Intent intent, int startId) {super.onStart(intent, startId);Log.i(TAG, "------------------------------ onStart");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.i(TAG, "------------------------------ onStartCommand");return super.onStartCommand(intent, flags, startId);}@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.i(TAG, "------------------------------ onBind");return null;}@Overridepublic boolean onUnbind(Intent intent) {Log.i(TAG, "------------------------------ onUnbind");return super.onUnbind(intent);}@Overridepublic void onDestroy() {super.onDestroy();Log.i(TAG, "------------------------------ onDestroy");}
}
(2)AndroidManifest
<application ...> <service android:name=".service.MyService" />...
</application>
(3)Activity Layout
- activity_start_service.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"><Buttonandroid: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" /><Buttonandroid: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
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 {@Overrideprotected 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 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"><Buttonandroid: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" /><Buttonandroid: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
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() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(TAG, "------------------------------ onServiceConnected");}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.i(TAG, "------------------------------ onServiceDisconnected");}};@Overrideprotected 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);}@Overrideprotected 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 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"><SurfaceViewandroid: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
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;@Overrideprotected 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() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {Log.i(TAG, "++++++++++++++++++++++++++++++ surfaceCreated");openCamera();}@Overridepublic 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();}@Overridepublic 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() {@Overridepublic 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 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"><SurfaceViewandroid: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
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;@Overrideprotected 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() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder holder) {openCamera();}@Overridepublic void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {if (camera == null) return;camera.startPreview();}@Overridepublic 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() {@Overridepublic 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");}@Overrideprotected void onResume() {super.onResume();openCamera();}@Overrideprotected void onPause() {super.onPause();releaseCamera();}
}
四、Layout Design 自定義設備類型和大小
- 創建自定義虛擬設備

- 新建硬件配置文件,主要配置屏幕大小和分辨率


- 選擇硬件配置文件,完成自定義虛擬設備的創建



- 使用自定義虛擬設備
