Giao diện người dùng khung ứng dụng Android dựa trên hệ phân cấp các đối tượng bắt đầu bằng một Khung hiển thị. Tất cả các thành phần trên giao diện người dùng đều trải qua một loạt các phép đo và quy trình bố cục để vừa với một khu vực hình chữ nhật. Sau đó, tất cả đối tượng thành phần hiển thị hiển thị được kết xuất vào một nền tảng do WindowManager thiết lập khi ứng dụng được đưa lên nền trước. Luồng giao diện người dùng của ứng dụng thực hiện bố cục và kết xuất vào vùng đệm cho mỗi khung.
SurfaceView
SurfaceView là một thành phần mà bạn có thể sử dụng để nhúng một lớp tổng hợp bổ sung trong hệ phân cấp khung hiển thị. SurfaceView có các tham số bố cục giống như các thành phần hiển thị khác, vì vậy, bạn có thể thao tác với thành phần hiển thị này như với bất kỳ thành phần hiển thị nào khác, nhưng nội dung của SurfaceView là trong suốt.
Khi kết xuất bằng nguồn vùng đệm bên ngoài, chẳng hạn như ngữ cảnh GL hoặc bộ giải mã nội dung đa phương tiện, bạn cần sao chép vùng đệm từ nguồn vùng đệm để hiển thị vùng đệm trên màn hình. Bạn có thể làm việc này bằng cách sử dụng SurfaceView.
Khi thành phần hiển thị của SurfaceView sắp hiển thị, khung này sẽ yêu cầu SurfaceControl yêu cầu một nền tảng mới từ SurfaceFlinger. Để nhận lệnh gọi lại khi tạo hoặc huỷ bề mặt, hãy sử dụng giao diện SurfaceHolder. Theo mặc định, giao diện mới tạo được đặt phía sau giao diện người dùng của ứng dụng. Bạn có thể ghi đè thứ tự Z mặc định để đặt giao diện mới lên trên cùng.
Việc kết xuất bằng SurfaceView sẽ có lợi trong trường hợp bạn cần kết xuất vào một nền tảng riêng biệt, chẳng hạn như khi bạn kết xuất bằng Camera API hoặc ngữ cảnh OpenGL ES. Khi bạn kết xuất bằng SurfaceView, SurfaceFlinger sẽ trực tiếp kết hợp các vùng đệm với màn hình. Nếu không có SurfaceView, bạn cần kết hợp các vùng đệm với một bề mặt ngoài màn hình, sau đó kết hợp với màn hình, vì vậy, việc kết xuất bằng SurfaceView sẽ loại bỏ công việc không cần thiết. Sau khi kết xuất bằng SurfaceView, hãy sử dụng luồng giao diện người dùng để điều phối với vòng đời hoạt động và điều chỉnh kích thước hoặc vị trí của thành phần hiển thị nếu cần. Sau đó, Trình tổng hợp phần cứng sẽ kết hợp giao diện người dùng của ứng dụng và các lớp khác.
Nền tảng mới là phía nhà sản xuất của BufferQueue, trong đó người tiêu dùng là lớp SurfaceFlinger. Bạn có thể cập nhật giao diện bằng bất kỳ cơ chế nào có thể cấp dữ liệu cho BufferQueue, chẳng hạn như các hàm Canvas do giao diện cung cấp, đính kèm EGLSurface và vẽ trên giao diện bằng GLES hoặc định cấu hình bộ giải mã nội dung nghe nhìn để ghi giao diện.
SurfaceView và vòng đời hoạt động
Khi sử dụng SurfaceView, hãy kết xuất giao diện từ một luồng không phải là luồng giao diện người dùng chính.
Đối với một hoạt động có SurfaceView, có hai máy trạng thái riêng biệt nhưng phụ thuộc lẫn nhau:
- Ứng dụng
onCreate
/onResume
/onPause
- Bề mặt được tạo/thay đổi/huỷ
Khi hoạt động bắt đầu, bạn sẽ nhận được lệnh gọi lại theo thứ tự sau:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Nếu nhấp quay lại, bạn sẽ thấy:
onPause()
surfaceDestroyed()
(được gọi ngay trước khi giao diện biến mất)
Nếu bạn xoay màn hình, hoạt động sẽ bị phá bỏ và tạo lại, bạn sẽ có được toàn bộ chu kỳ. Bạn có thể biết đó là một quá trình khởi động lại nhanh bằng cách kiểm tra isFinishing()
. Bạn có thể bắt đầu/dừng một hoạt động nhanh đến mức surfaceCreated()
xảy ra sau onPause()
.
Nếu nhấn vào nút nguồn để làm trống màn hình, bạn sẽ chỉ nhận được onPause()
mà không có surfaceDestroyed()
. Bề mặt vẫn hoạt động và quá trình kết xuất có thể tiếp tục. Bạn có thể tiếp tục nhận được các sự kiện của Biên đạo múa nếu tiếp tục yêu cầu. Nếu bạn có màn hình khoá buộc một hướng khác, thì hoạt động của bạn có thể được khởi động lại khi thiết bị không ở trạng thái rỗng. Nếu không, bạn có thể thoát khỏi màn hình trống bằng cùng một nền tảng như trước.
Vòng đời của luồng có thể được liên kết với giao diện hoặc hoạt động, tuỳ thuộc vào những gì bạn muốn xảy ra khi màn hình trống. Luồng có thể bắt đầu/dừng khi Hoạt động bắt đầu/dừng hoặc khi tạo/huỷ bề mặt.
Việc bắt đầu/dừng luồng khi bắt đầu/dừng Hoạt động hoạt động tốt với vòng đời của ứng dụng. Bạn bắt đầu luồng trình kết xuất trong onResume()
và dừng luồng đó trong onStop()
.
Khi tạo và định cấu hình luồng, đôi khi giao diện đã tồn tại, đôi khi không (ví dụ: giao diện vẫn hoạt động sau khi bật/tắt màn hình bằng nút nguồn). Bạn phải đợi bề mặt được tạo trước khi khởi chạy trong luồng. Bạn không thể khởi chạy trong lệnh gọi lại surfaceCreate()
vì lệnh gọi lại này sẽ không kích hoạt lại nếu không tạo lại giao diện. Thay vào đó, hãy truy vấn hoặc lưu trạng thái bề mặt vào bộ nhớ đệm rồi chuyển tiếp trạng thái đó đến luồng kết xuất.
Việc bắt đầu/dừng luồng trên bề mặt tạo/huỷ hoạt động tốt vì bề mặt và trình kết xuất được kết hợp với nhau một cách logic. Bạn bắt đầu luồng sau khi tạo giao diện, điều này giúp tránh một số vấn đề về giao tiếp giữa các luồng; và các thông báo được tạo/thay đổi trên giao diện chỉ được chuyển tiếp. Để đảm bảo quá trình kết xuất dừng khi màn hình trống và tiếp tục khi màn hình không trống, hãy yêu cầu Choreographer ngừng gọi lệnh gọi lại vẽ khung. onResume()
tiếp tục các lệnh gọi lại nếu luồng trình kết xuất đang chạy. Tuy nhiên, nếu bạn tạo ảnh động dựa trên thời gian đã trôi qua giữa các khung hình, thì có thể có một khoảng trống lớn trước khi sự kiện tiếp theo đến; việc sử dụng thông báo tạm dừng/tiếp tục rõ ràng có thể giải quyết vấn đề này.
Cả hai tuỳ chọn, cho dù thời gian tồn tại của luồng có liên kết với Hoạt động hay giao diện, đều tập trung vào cách định cấu hình luồng trình kết xuất và liệu luồng đó có đang thực thi hay không. Một vấn đề liên quan là trích xuất trạng thái từ luồng khi hoạt động bị huỷ (trong onStop()
hoặc onSaveInstanceState()
); trong những trường hợp như vậy, việc liên kết thời gian hoạt động của luồng với hoạt động sẽ hoạt động hiệu quả nhất vì sau khi luồng trình kết xuất được kết hợp, bạn có thể truy cập trạng thái của luồng đã kết xuất mà không cần các nguyên hàm đồng bộ hoá.
GLSurfaceView
Lớp GLSurfaceView cung cấp các lớp trợ giúp để quản lý ngữ cảnh EGL, giao tiếp giữa các luồng và tương tác với vòng đời hoạt động. Bạn không cần phải sử dụng GLSurfaceView để sử dụng GLES.
Ví dụ: GLSurfaceView tạo một luồng để kết xuất và định cấu hình một ngữ cảnh EGL tại đó. Trạng thái này sẽ tự động được dọn dẹp khi hoạt động tạm dừng. Hầu hết các ứng dụng không cần biết gì về EGL để sử dụng GLES với GLSurfaceView.
Trong hầu hết các trường hợp, GLSurfaceView có thể giúp bạn làm việc với GLES dễ dàng hơn. Trong một số trường hợp, điều này có thể gây trở ngại.