Hướng máy ảnh

Nếu ứng dụng Android của bạn dùng camera, thì bạn cần cân nhắc một số yếu tố đặc biệt khi xử lý hướng. Tài liệu này giả định rằng bạn hiểu các khái niệm cơ bản về API camera2 của Android. Bạn có thể đọc bài đăng trên blog hoặc bản tóm tắt của chúng tôi để biết thông tin tổng quan về camera2. Bạn cũng nên thử viết một ứng dụng máy ảnh trước khi xem tài liệu này.

Thông tin khái quát

Việc xử lý hướng trong các ứng dụng camera Android khá phức tạp và cần xem xét các yếu tố sau:

  • Hướng tự nhiên: Hướng màn hình khi thiết bị ở vị trí "bình thường" theo thiết kế của thiết bị – thường là hướng dọc đối với điện thoại di động và hướng ngang đối với máy tính xách tay.
  • Hướng của cảm biến: Hướng của cảm biến được gắn trên thiết bị.
  • Độ xoay màn hình: Mức độ xoay của thiết bị so với hướng tự nhiên.
  • Kích thước kính ngắm: Kích thước kính ngắm dùng để hiển thị bản xem trước của camera.
  • Kích thước hình ảnh do camera xuất ra.

Sự kết hợp của các yếu tố này tạo ra nhiều cấu hình giao diện người dùng và bản xem trước có thể có cho các ứng dụng máy ảnh. Tài liệu này nhằm minh hoạ cách nhà phát triển có thể điều hướng các hướng này và xử lý đúng hướng camera trong ứng dụng Android.

Để đơn giản hoá, hãy giả định rằng tất cả ví dụ đều liên quan đến camera sau, trừ phi có đề cập khác. Ngoài ra, tất cả ảnh sau đây đều là ảnh mô phỏng để minh hoạ rõ ràng hơn.

Mọi điều cần biết về hướng

Hướng tự nhiên

Hướng tự nhiên được xác định là hướng màn hình khi thiết bị ở vị trí mà người dùng thường mong đợi. Đối với điện thoại, hướng tự nhiên thường là hướng dọc. Nói cách khác, điện thoại có chiều rộng ngắn hơn và chiều cao dài hơn. Đối với máy tính xách tay, hướng tự nhiên của chúng là hướng ngang, tức là chiều rộng dài hơn và chiều cao ngắn hơn. Máy tính bảng phức tạp hơn một chút so với điện thoại – máy tính bảng có thể ở chế độ dọc hoặc ngang.

Hình minh hoạ hướng tự nhiên với một chiếc điện thoại, một máy tính xách tay và một vật thể từ phía người quan sát

Hướng cảm biến

Nói một cách chính thức, hướng cảm biến được đo bằng số độ mà hình ảnh đầu ra từ cảm biến cần được xoay theo chiều kim đồng hồ để khớp với hướng tự nhiên của thiết bị. Nói cách khác, hướng cảm biến là số độ mà cảm biến xoay ngược chiều kim đồng hồ trước khi được gắn trên thiết bị. Khi nhìn vào màn hình, bạn sẽ thấy hướng xoay theo chiều kim đồng hồ. Điều này là do cảm biến camera sau được lắp đặt ở mặt "sau" của thiết bị.

Theo Định nghĩa về khả năng tương thích với Android 10 7.5.5 Hướng của camera, camera trước và camera sau "PHẢI được định hướng sao cho chiều dài của camera phù hợp với chiều dài của màn hình".

Các vùng đệm đầu ra của camera có kích thước ngang. Vì hướng tự nhiên của điện thoại thường là hướng dọc, nên hướng cảm biến thường là 90 hoặc 270 độ so với hướng tự nhiên để cạnh dài của vùng đệm đầu ra khớp với cạnh dài của màn hình. Hướng cảm biến khác nhau đối với những thiết bị có hướng tự nhiên là hướng ngang, chẳng hạn như Chromebook. Trên những thiết bị này, cảm biến hình ảnh được đặt sao cho cạnh dài của vùng đệm đầu ra khớp với cạnh dài của màn hình. Vì cả hai đều có kích thước ngang, nên hướng của chúng sẽ khớp và hướng của cảm biến là 0 hoặc 180 độ.

Hình minh hoạ hướng tự nhiên với một chiếc điện thoại, một máy tính xách tay và một vật thể từ phía người quan sát

Các hình minh hoạ sau đây cho thấy mọi thứ trông như thế nào theo quan điểm của người quan sát, nhìn vào màn hình thiết bị:

Hình minh hoạ hướng cảm biến với một chiếc điện thoại, một máy tính xách tay và một vật thể từ phía người quan sát

Hãy xem xét cảnh sau:

Một cảnh có bức tượng nhỏ Android dễ thương (bugdroid)

Điện thoại Máy tính xách tay
Hình minh hoạ khi nhìn qua cảm biến camera sau trên điện thoại Hình minh hoạ khi nhìn qua cảm biến camera sau trên máy tính xách tay

Vì hướng cảm biến thường là 90 hoặc 270 độ trên điện thoại, nên nếu không tính đến hướng cảm biến, hình ảnh bạn nhận được sẽ trông như sau:

Điện thoại Máy tính xách tay
Hình minh hoạ khi nhìn qua cảm biến camera sau trên điện thoại Hình minh hoạ khi nhìn qua cảm biến camera sau trên máy tính xách tay

Giả sử hướng cảm biến ngược chiều kim đồng hồ được lưu trữ trong biến sensorOrientation. Để bù cho hướng cảm biến, bạn cần xoay các vùng đệm đầu ra theo `sensorOrientation` theo chiều kim đồng hồ để đưa hướng trở lại phù hợp với hướng tự nhiên của thiết bị.

Trong Android, các ứng dụng có thể dùng TextureView hoặc SurfaceView để hiển thị bản xem trước của camera. Cả hai đều có thể xử lý hướng cảm biến nếu các ứng dụng sử dụng đúng cách. Chúng tôi sẽ trình bày chi tiết cách bạn nên tính đến hướng của cảm biến trong các phần sau.

Độ xoay màn hình

Độ xoay màn hình được xác định chính thức bằng độ xoay của đồ hoạ được vẽ trên màn hình, theo hướng ngược lại với độ xoay thực của thiết bị so với hướng tự nhiên. Các phần sau đây giả định rằng tất cả các hướng xoay màn hình đều là bội số của 90. Nếu bạn truy xuất hướng xoay màn hình theo độ tuyệt đối, hãy làm tròn hướng xoay đó đến giá trị gần nhất trong số {0, 90, 180, 270}.

"Hướng hiển thị" trong các phần sau đây đề cập đến việc thiết bị được giữ theo hướng ngang hay dọc và khác với "xoay màn hình".

Giả sử bạn xoay các thiết bị 90 độ ngược chiều kim đồng hồ so với vị trí trước đó như minh hoạ trong hình sau:

Hình minh hoạ xoay màn hình 90 độ với một chiếc điện thoại, một máy tính xách tay và một đối tượng từ phía người quan sát

Giả sử các vùng đệm đầu ra đã được xoay dựa trên hướng cảm biến, thì bạn sẽ có các vùng đệm đầu ra sau:

Điện thoại Máy tính xách tay
Hình minh hoạ khi nhìn qua cảm biến camera sau trên điện thoại Hình minh hoạ khi nhìn qua cảm biến camera sau trên máy tính xách tay

Nếu độ xoay màn hình được lưu trữ trong biến displayRotation, để có được hình ảnh chính xác, bạn nên xoay các vùng đệm đầu ra theo chiều ngược chiều kim đồng hồ bằng displayRotation.

Đối với camera trước, chế độ xoay màn hình sẽ tác động lên các vùng đệm hình ảnh theo hướng ngược lại so với màn hình. Nếu đang xử lý camera trước, bạn nên xoay các vùng đệm theo displayRotatation theo chiều kim đồng hồ.

Chú ý

Độ xoay màn hình đo độ xoay ngược chiều kim đồng hồ của thiết bị. Điều này không đúng với tất cả các API về hướng/xoay.

Ví dụ:

Điều quan trọng cần lưu ý ở đây là độ xoay màn hình tương ứng với hướng tự nhiên. Ví dụ: nếu xoay điện thoại theo chiều dọc 90 hoặc 270 độ, bạn sẽ thấy màn hình có hình dạng ngang. Ngược lại, bạn sẽ có màn hình dọc nếu xoay máy tính xách tay cùng một góc. Các ứng dụng phải luôn ghi nhớ điều này và không bao giờ được giả định về hướng tự nhiên của thiết bị.

Ví dụ

Hãy sử dụng các hình trước đó để minh hoạ hướng và góc xoay.

Hình minh hoạ kết hợp hướng dọc và ngang, trong đó điện thoại và máy tính xách tay không xoay, còn đối tượng thì xoay

Điện thoại Máy tính xách tay
Hướng tự nhiên = Dọc Hướng tự nhiên = Hướng ngang
Hướng cảm biến = 90 Hướng của cảm biến = 0
Độ xoay màn hình = 0 Độ xoay màn hình = 0
Hướng màn hình = Dọc Hướng màn hình = Ngang

Hình minh hoạ kết hợp hướng dọc và ngang, trong đó điện thoại và máy tính xách tay không xoay, còn đối tượng thì xoay

Điện thoại Máy tính xách tay
Hướng tự nhiên = Dọc Hướng tự nhiên = Hướng ngang
Hướng cảm biến = 90 Hướng của cảm biến = 0
Độ xoay màn hình = 90 Độ xoay màn hình = 90
Hướng màn hình = Ngang Hướng màn hình = Dọc

Kích thước kính ngắm

Các ứng dụng phải luôn điều chỉnh kích thước khung ngắm dựa trên hướng, chế độ xoay và độ phân giải màn hình. Nói chung, các ứng dụng phải làm cho hướng của khung ngắm giống với hướng hiển thị hiện tại. Nói cách khác, các ứng dụng phải căn chỉnh cạnh dài của khung ngắm với cạnh dài của màn hình.

Kích thước đầu ra của hình ảnh theo camera

Khi chọn kích thước đầu ra của hình ảnh cho bản xem trước, bạn nên chọn kích thước bằng hoặc lớn hơn một chút so với kích thước của Khung ngắm bất cứ khi nào có thể. Bạn thường không muốn các vùng đệm đầu ra được mở rộng vì điều này sẽ gây ra hiện tượng pixel hoá. Bạn cũng không nên chọn kích thước quá lớn vì có thể làm giảm hiệu suất và tiêu hao nhiều pin hơn.

Hướng JPEG

Hãy bắt đầu bằng một tình huống thường gặp – chụp ảnh JPEG. Trong API camera2, bạn có thể truyền JPEG_ORIENTATION trong yêu cầu chụp để chỉ định mức độ xoay theo chiều kim đồng hồ mà bạn muốn đối với các tệp JPEG đầu ra.

Sau đây là bản tóm tắt nhanh về những nội dung chúng tôi đã đề cập:

  • Để xử lý hướng cảm biến, bạn cần xoay vùng đệm hình ảnh theo chiều kim đồng hồ sensorOrientation.
  • Để xử lý việc xoay màn hình, bạn cần xoay một vùng đệm theo hướng ngược chiều kim đồng hồ displayRotation đối với camera sau, theo chiều kim đồng hồ đối với camera trước.

Cộng 2 yếu tố này lại, số lượng bạn muốn xoay theo chiều kim đồng hồ là

  • sensorOrientation - displayRotation cho camera sau.
  • sensorOrientation + displayRotation cho camera trước.

Bạn có thể xem mã mẫu cho logic này trong tài liệu JPEG_ORIENTATION. Xin lưu ý rằng deviceOrientation trong mã mẫu của tài liệu đang sử dụng chế độ xoay thiết bị theo chiều kim đồng hồ. Do đó, các dấu hiệu xoay màn hình sẽ bị đảo ngược.

Xem trước

Còn bản xem trước của camera thì sao? Có 2 cách chính để ứng dụng có thể hiển thị bản xem trước của camera: SurfaceView và TextureView. Mỗi loại yêu cầu một phương pháp riêng để xử lý đúng hướng.

SurfaceView

SurfaceView thường được đề xuất cho bản xem trước của camera, miễn là bạn không cần xử lý hoặc tạo hiệu ứng cho các vùng đệm xem trước. Nó có hiệu suất cao hơn và ít tốn tài nguyên hơn TextureView.

SurfaceView cũng tương đối dễ bố trí. Bạn chỉ cần lo lắng về tỷ lệ khung hình của SurfaceView mà bạn đang hiển thị bản xem trước của camera.

Nguồn

Bên dưới SurfaceView, nền tảng Android xoay các vùng đệm đầu ra để khớp với hướng màn hình của thiết bị. Nói cách khác, nó tính đến cả hướng của cảm biếnđộ xoay màn hình. Nói một cách đơn giản hơn, khi màn hình ở chế độ ngang, chúng ta sẽ có một bản xem trước cũng ở chế độ ngang và ngược lại đối với chế độ dọc.

Điều này được minh hoạ trong bảng sau. Điều quan trọng cần lưu ý ở đây là chỉ riêng việc xoay màn hình không xác định được hướng của nguồn.

Độ xoay màn hình Điện thoại (Hướng tự nhiên = Dọc) Máy tính xách tay (Hướng tự nhiên = Chiều ngang)
0 Hình ảnh dọc có đầu của bugdroid hướng lên trên Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên
90 Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên Hình ảnh dọc có đầu của bugdroid hướng lên trên
180 Hình ảnh dọc có đầu của bugdroid hướng lên trên Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên
270 Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên Hình ảnh dọc có đầu của bugdroid hướng lên trên

Bố cục

Như bạn thấy, SurfaceView đã xử lý một số vấn đề phức tạp cho chúng ta. Nhưng giờ đây, bạn cần cân nhắc kích thước của kính ngắm hoặc kích thước bạn muốn xem trước trên màn hình. SurfaceView tự động điều chỉnh tỷ lệ bộ nhớ đệm nguồn cho phù hợp với kích thước của bộ nhớ đệm đó. Bạn cần đảm bảo tỷ lệ khung hình của kính ngắm giống với tỷ lệ khung hình của sourcebuffer. Ví dụ: nếu cố gắng điều chỉnh một bản xem trước có hình chân dung cho vừa với SurfaceView có hình ngang, bạn sẽ thấy hình ảnh bị biến dạng như sau:

Hình minh hoạ cho thấy bugdroid bị kéo giãn do vừa với bản xem trước hình chân dung vào khung ngắm hình ngang

Thông thường, bạn muốn tỷ lệ khung hình (tức là chiều rộng/chiều cao) của khung ngắm giống với tỷ lệ khung hình của nguồn. Nếu bạn không muốn cắt hình ảnh trong khung ngắm (cắt bớt một số pixel để cố định màn hình), thì có 2 trường hợp cần xem xét, đó là khi aspectRatioActivity lớn hơn aspectRatioSource và khi aspectRatioActivity nhỏ hơn hoặc bằng aspectRatioSource

aspectRatioActivity > aspectRatioSource

Bạn có thể coi trường hợp này là hoạt động "rộng" hơn. Dưới đây là ví dụ về trường hợp bạn có một hoạt động 16:9 và một nguồn 4:3.

aspectRatioActivity = 16/9 ≈ 1.78
aspectRatioSource = 4/3 ≈ 1.33

Trước tiên, bạn cũng muốn kính ngắm của mình có tỷ lệ khung hình là 4:3. Sau đó, bạn muốn điều chỉnh nguồn và khung ngắm cho phù hợp với hoạt động như sau:

Hình minh hoạ về một hoạt động có tỷ lệ khung hình lớn hơn tỷ lệ khung hình của khung ngắm bên trong

Trong trường hợp này, bạn nên điều chỉnh chiều cao của khung ngắm sao cho khớp với chiều cao của hoạt động, đồng thời điều chỉnh tỷ lệ khung hình của khung ngắm sao cho giống với tỷ lệ khung hình của nguồn. Mã giả có dạng như sau:

viewfinderHeight = activityHeight;
viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource

Trường hợp khác là khi hoạt động "hẹp" hoặc "cao". Chúng ta có thể dùng lại ví dụ trước, ngoại trừ trong ví dụ sau, bạn xoay thiết bị 90 độ, khiến hoạt động có tỷ lệ khung hình là 9:16 và nguồn có tỷ lệ khung hình là 3:4.

aspectRatioActivity = 9/16 = 0.5625
aspectRatioSource = 3/4 = 0.75

Trong trường hợp này, bạn muốn điều chỉnh nguồn và khung ngắm cho vừa với hoạt động như sau:

Hình minh hoạ về một hoạt động có tỷ lệ khung hình nhỏ hơn tỷ lệ khung hình của khung ngắm bên trong

Bạn nên điều chỉnh chiều rộng của khung ngắm sao cho khớp với chiều rộng của hoạt động (thay vì chiều cao trong trường hợp trước) trong khi vẫn giữ nguyên tỷ lệ khung hình của khung ngắm so với tỷ lệ khung hình của nguồn. Mã giả:

viewfinderWidth = activityWidth;
viewfinderHeight = activityWidth / aspectRatioSource;
Cắt xén

AutoFitSurfaceView.kt (github) trong các mẫu Camera2 ghi đè SurfaceView và xử lý tỷ lệ khung hình không khớp bằng cách sử dụng hình ảnh bằng hoặc "chỉ lớn hơn" hoạt động theo cả hai chiều, sau đó cắt nội dung tràn. Điều này hữu ích cho những ứng dụng muốn bản xem trước bao phủ toàn bộ hoạt động hoặc lấp đầy hoàn toàn một khung hiển thị có kích thước cố định mà không làm biến dạng hình ảnh.

Cảnh báo

Mẫu trước đó cố gắng tối đa hoá không gian màn hình bằng cách làm cho bản xem trước lớn hơn hoạt động một chút để không còn khoảng trống nào. Điều này dựa trên thực tế là các phần tràn sẽ bị bố cục mẹ (hoặc ViewGroup) cắt theo mặc định. Hành vi này nhất quán với RelativeLayout và LinearLayout nhưng KHÔNG nhất quán với ConstraintLayout. ConstraintLayout có thể đổi kích thước các Thành phần hiển thị con để chúng vừa với bố cục. Điều này sẽ làm hỏng hiệu ứng "cắt ở giữa" dự kiến và khiến bản xem trước bị kéo giãn. Bạn có thể tham khảo cam kết này.

TextureView

TextureView cho phép bạn kiểm soát tối đa nội dung của bản xem trước camera, nhưng điều này sẽ ảnh hưởng đến hiệu suất. Bạn cũng cần phải làm nhiều việc hơn để hiển thị bản xem trước của camera một cách chính xác.

Nguồn

Bên dưới TextureView, nền tảng Android sẽ xoay các vùng đệm đầu ra theo hướng cảm biến để khớp với hướng tự nhiên của thiết bị. Mặc dù TextureView xử lý hướng của cảm biến, nhưng không xử lý việc xoay màn hình. Thao tác này sẽ căn chỉnh các vùng đệm đầu ra theo hướng tự nhiên của thiết bị, tức là bạn sẽ phải tự xử lý các chế độ xoay màn hình.

Điều này được minh hoạ trong bảng sau. Hãy thử xoay các số liệu theo hướng xoay màn hình tương ứng, bạn sẽ thực sự nhận được các số liệu tương tự trong SurfaceView.

Độ xoay màn hình Điện thoại (Hướng tự nhiên = Dọc) Máy tính xách tay (Hướng tự nhiên = Chiều ngang)
0 Hình ảnh dọc có đầu của bugdroid hướng lên trên Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên
90 Hình ảnh dọc có đầu của bugdroid hướng sang phải Hình ảnh có hình dạng ngang với đầu của bugdroid hướng sang phải
180 Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên Hình ảnh dọc có đầu của bugdroid hướng lên trên
270 Hình ảnh có dạng ngang với đầu của bugdroid hướng lên trên Hình ảnh dọc có đầu của bugdroid hướng lên trên

Bố cục

Bố cục hơi phức tạp đối với trường hợp TextureView. Trước đây, bạn nên sử dụng ma trận biến đổi cho TextureView, nhưng phương thức đó không hoạt động trên tất cả các thiết bị. Bạn nên làm theo các bước được mô tả tại đây.

Quy trình gồm 3 bước để bố trí chính xác bản xem trước trên TextureView:

  1. Đặt kích thước của TextureView giống với kích thước xem trước đã chọn.
  2. Điều chỉnh tỷ lệ TextureView có thể bị kéo giãn về kích thước ban đầu của bản xem trước.
  3. Xoay TextureView displayRotation ngược chiều kim đồng hồ.

Giả sử bạn có một chiếc điện thoại có chế độ xoay màn hình 90 độ.

Hình minh hoạ một chiếc điện thoại có màn hình xoay 90 độ và một vật thể

1. Đặt kích thước của TextureView giống với kích thước xem trước đã chọn

Giả sử kích thước xem trước bạn chọn là previewWidth × previewHeight, trong đó previewWidth > previewHeight (đầu ra của cảm biến có dạng ngang theo bản chất). Khi định cấu hình một phiên chụp, bạn nên gọi SurfaceTexture#setDefaultBufferSize(int width, height) để chỉ định kích thước xem trước (previewWidth × previewHeight).

Trước khi gọi setDefaultBufferSize, bạn cũng phải đặt kích thước của TextureView thành `previewWidth × previewHeight` bằng View#setLayoutParams(android.view.ViewGroup.LayoutParams). Lý do là TextureView gọi SurfaceTexture#setDefaultBufferSize(int width, height) bằng chiều rộng và chiều cao đo được. Nếu bạn không đặt kích thước của TextureView một cách rõ ràng từ trước, thì điều này có thể gây ra tình trạng xung đột. Vấn đề này được giảm thiểu bằng cách đặt kích thước của TextureView một cách rõ ràng trước.

Giờ đây, TextureView có thể không khớp với kích thước của nguồn. Trong trường hợp điện thoại, nguồn có hình dạng dọc, nhưng TextureView có hình dạng ngang do layoutParams mà bạn vừa đặt. Điều này sẽ dẫn đến việc bản xem trước bị kéo giãn, như minh hoạ ở đây:

Hình minh hoạ về bản xem trước có hình chân dung được kéo giãn cho vừa với TextureView có cùng kích thước với kích thước bản xem trước đã chọn

2. Điều chỉnh tỷ lệ TextureView có thể bị kéo giãn về kích thước ban đầu của bản xem trước

Hãy cân nhắc những điều sau đây để điều chỉnh tỷ lệ bản xem trước bị kéo giãn về kích thước của nguồn.

Phương diện của nguồn (sourceWidth × sourceHeight) là:

  • previewHeight × previewWidth, nếu hướng tự nhiên là hướng dọc hoặc hướng dọc lộn ngược (hướng cảm biến là 90 hoặc 270 độ)
  • previewWidth × previewHeight, nếu hướng tự nhiên là hướng ngang hoặc hướng ngang ngược (hướng cảm biến là 0 hoặc 180 độ)

Khắc phục tình trạng kéo giãn bằng cách sử dụng View#setScaleX(float)View#setScaleY(float)

  • setScaleX(sourceWidth / previewWidth)
  • setScaleY(sourceHeight / previewHeight)

Hình minh hoạ cho thấy quy trình thu nhỏ bản xem trước bị kéo giãn về kích thước ban đầu

3. Xoay bản xem trước theo `displayRotation` ngược chiều kim đồng hồ

Như đã đề cập trước đó, bạn nên xoay bản xem trước theo hướng ngược chiều kim đồng hồ displayRotation để bù cho việc xoay màn hình.

Bạn có thể thực hiện việc này bằng cách View#setRotation(float)

  • setRotation(-displayRotation), vì thao tác này sẽ xoay theo chiều kim đồng hồ.

Ảnh minh hoạ cho thấy quy trình xoay bản xem trước để khớp với hướng hiển thị của thiết bị

Mẫu
  • PreviewView từ camerax trong Jetpack xử lý bố cục TextureView như đã mô tả trước đó. Thao tác này định cấu hình phép biến đổi bằng PreviewCorrector.

Lưu ý: Nếu trước đây bạn đã sử dụng ma trận biến đổi cho TextureView trong mã của mình, thì bản xem trước có thể không hiển thị đúng trên một thiết bị có hướng ngang tự nhiên như Chromebook. Có thể ma trận biến đổi của bạn giả định sai hướng của cảm biến là 90 hoặc 270 độ. Bạn có thể tham khảo cam kết này trên GitHub để tìm giải pháp thay thế, nhưng bạn nên di chuyển ứng dụng để sử dụng phương thức được mô tả ở đây.