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