2012-07-15 26 views
63

Tôi đã cố gắng triển khai một ứng dụng yêu cầu xem trước máy ảnh trên một bề mặt. Như tôi đã nhìn thấy những điều, cả hai hoạt động và bề mặt vòng đời bao gồm các tình trạng sau:Cách gọi lại của SurfaceHolder có liên quan đến vòng đời Activity?

  1. Khi tôi lần đầu tiên khởi động Hoạt động của tôi: onResume()->onSurfaceCreated()->onSurfaceChanged()
  2. Khi tôi rời khỏi Hoạt động của tôi: onPause()->onSurfaceDestroyed()

Trong này lược đồ, tôi có thể thực hiện các cuộc gọi tương ứng như máy ảnh mở/phát hành và xem trước/dừng ở onPause/onResumeonSurfaceCreated()/onSurfaceDestroyed().

Nó hoạt động tốt, trừ khi tôi khóa màn hình. Khi tôi khởi chạy ứng dụng, sau đó khóa màn hình và mở khóa màn hình sau, tôi thấy:

onPause() - và không có gì khác sau khi màn hình bị khóa - sau đó onResume() sau khi mở khóa - và không có gọi lại bề mặt sau đó. Trên thực tế, onResume() được gọi sau khi nút nguồn được nhấn và màn hình đang bật, nhưng màn hình khóa vẫn hoạt động, vì vậy, trước khi hoạt động trở nên hiển thị.

Với lược đồ này, tôi nhận được một màn hình màu đen sau khi mở khóa và không có gọi lại bề mặt nào được gọi.

Đây là đoạn mã không liên quan đến công việc thực tế với máy ảnh mà là số gọi lại SurfaceHolder. Vấn đề trên được sao chép ngay cả với mã này vào điện thoại của tôi (callbacks được gọi trong một chuỗi bình thường khi bạn nhấn nút "Back", nhưng bị thiếu khi bạn khóa màn hình):

class Preview extends SurfaceView implements SurfaceHolder.Callback { 

    private static final String tag= "Preview"; 

    public Preview(Context context) { 
     super(context); 
     Log.d(tag, "Preview()"); 
     SurfaceHolder holder = getHolder(); 
     holder.addCallback(this); 
     holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
    } 

    public void surfaceCreated(SurfaceHolder holder) { 
     Log.d(tag, "surfaceCreated"); 
    } 

    public void surfaceDestroyed(SurfaceHolder holder) { 
     Log.d(tag, "surfaceDestroyed"); 
    } 

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
     Log.d(tag, "surfaceChanged"); 
    } 
} 

Bất kỳ ý tưởng về lý do tại sao bề mặt vẫn không bị phá hủy sau khi Hoạt động bị tạm dừng? Ngoài ra, làm thế nào để bạn xử lý vòng đời của camera trong những trường hợp như vậy?

+0

Bạn đang phát triển cấp Plaform/API ở đâu? – FerranB

Trả lời

51

Edit: nếu targetSDK lớn hơn 10, đưa ứng dụng vào chế độ ngủ gọi onStop. Source

Tôi đã xem vòng đời của cả Hoạt động và SurfaceView trong ứng dụng máy ảnh nhỏ trên điện thoại bánh gừng của tôi. Bạn hoàn toàn chính xác; bề mặt không bị phá hủy khi nhấn nút nguồn để đặt điện thoại vào chế độ ngủ. Khi điện thoại chuyển sang chế độ ngủ, Hoạt động sẽ thực hiện . (Và không làm onStop.) Nó onResume khi điện thoại tỉnh dậy, và, như bạn chỉ ra, nó làm điều này trong khi màn hình khóa vẫn còn nhìn thấy và chấp nhận đầu vào, đó là một chút lẻ. Khi tôi tắt Hoạt động bằng cách nhấn nút Trang chủ, hoạt động sẽ thực hiện cả hai và onStop. Một cái gì đó gây ra một cuộc gọi lại đến surfaceDestroyed trong trường hợp này giữa cuối và bắt đầu onStop.Nó không phải là rất rõ ràng, nhưng nó có vẻ rất nhất quán.

Khi nhấn nút nguồn để ngủ điện thoại, trừ khi một cái gì đó được thực hiện một cách rõ ràng để ngăn chặn nó, máy ảnh tiếp tục chạy! Nếu tôi có máy ảnh thực hiện gọi lại mỗi hình ảnh cho mỗi khung xem trước, với một Log.d() trong đó, các báo cáo nhật ký tiếp tục đến trong khi điện thoại đang giả vờ ngủ. Tôi nghĩ rằng đó là Rất lén lút.

Như nhầm lẫn khác, callbacks để surfaceCreatedsurfaceChanged xảy ra sauonResume trong hoạt động, nếu bề mặt đã được tạo ra.

Theo quy tắc, tôi quản lý máy ảnh trong lớp thực hiện gọi lại SurfaceHolder.

class Preview extends SurfaceView implements SurfaceHolder.Callback { 
    private boolean previewIsRunning; 
    private Camera camera; 

    public void surfaceCreated(SurfaceHolder holder) { 
     camera = Camera.open(); 
     // ... 
     // but do not start the preview here! 
    } 

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
     // set preview size etc here ... then 
     myStartPreview(); 
    } 

    public void surfaceDestroyed(SurfaceHolder holder) { 
     myStopPreview(); 
     camera.release(); 
     camera = null; 
    } 

    // safe call to start the preview 
    // if this is called in onResume, the surface might not have been created yet 
    // so check that the camera has been set up too. 
    public void myStartPreview() { 
     if (!previewIsRunning && (camera != null)) { 
      camera.startPreview(); 
      previewIsRunning = true; 
     } 
    } 

    // same for stopping the preview 
    public void myStopPreview() { 
     if (previewIsRunning && (camera != null)) { 
      camera.stopPreview(); 
      previewIsRunning = false; 
     } 
    } 
} 

và sau đó trong Hoạt động:

@Override public void onResume() { 
    preview.myStartPreview(); // restart preview after awake from phone sleeping 
    super.onResume(); 
} 
@Override public void onPause() { 
    preview.myStopPreview(); // stop preview in case phone is going to sleep 
    super.onPause(); 
} 

và điều đó dường như làm việc OK cho tôi. Sự kiện xoay khiến cho Hoạt động bị hủy và tái tạo, khiến cho SurfaceView bị hủy và tái tạo.

+0

Tại sao trước cuộc gọi siêu? –

+0

Tôi không biết rằng nó quan trọng cho dù mã được thực hiện trước hoặc sau cuộc gọi siêu. Điều quan trọng là 'super.onResume' được gọi ở đâu đó trong thường trình' onResume'.Tôi nghĩ. – emrys57

+0

Thực ra nó phụ thuộc vào việc khởi tạo surfaceview và surfaceviewholder. Nếu bạn làm một số nhiệm vụ đồng bộ thì bạn khởi tạo khung nhìn sau đó nó không bao giờ gọi sau khi onResume. thậm chí không sau khi task.but đồng bộ khi tôi chuyển sang hoạt động khác và tiếp tục sau đó nó gọi lại hàm onSurfaceCreated hoặc Change. BTW cảm ơn bạn! @ emrys57. ít nhất tôi đã sắp xếp ra vấn đề của tôi là với surfaceview. :) –

1

SurfaceHolder.Callback liên quan đến Surface của nó.

Hoạt động trên màn hình phải không? Nếu vậy, sẽ không có SurfaceHolder.Callback, vì Surface vẫn còn trên màn hình.

Để kiểm soát bất kỳ SurfaceView nào, bạn có thể xử lý nó ở chế độ onPause/onResume. Đối với SurfaceHolder.Callback, bạn có thể sử dụng nó nếu bề mặt được thay đổi (tạo ra, sizechanged, và phá hủy), giống như khởi tạo OpenGL khi surfaceCreated, và tiêu diệt OpenGL khi surfaceDestroyed vv

18

Một giải pháp đơn giản khác hoạt động tốt - để thay đổi mức hiển thị của bề mặt xem trước.

private SurfaceView preview; 

xem trước là init trong phương thức onCreate. Trong onResume phương pháp thiết lập View.VISIBLE cho bề mặt preview:

@Override 
public void onResume() { 
    preview.setVisibility(View.VISIBLE); 
    super.onResume(); 
} 

và tương ứng trong bộ tầm nhìn View.GONE:

@Override 
public void onPause() { 
    super.onPause(); 
    preview.setVisibility(View.GONE); 
    stopPreviewAndFreeCamera(); //stop and release camera 
} 
+0

Bạn là một phao cứu sinh! – gtsouk

+0

Xác nhận điều này cũng hoạt động với API Camera2. –

1

Nhờ cả tất cả các câu trả lời trước tôi quản lý để làm cho công việc xem trước máy ảnh của tôi rõ ràng trong khi đi lại từ nền hoặc màn hình khóa.

Khi @ e7fendy được đề cập, cuộc gọi lại của SurfaceView sẽ không được gọi khi đang bật khóa màn hình vì chế độ xem bề mặt vẫn hiển thị cho hệ thống.

Do đó, như @validcat đã thông báo, hãy gọi preview.setVisibility(View.VISIBLE);preview.setVisibility(View.GONE); trong tương ứng onPause() và onResume() sẽ buộc chế độ xem bề mặt tự chuyển tiếp và gọi nó là gọi lại.

Đến lúc đó, các giải pháp từ @ emrys57 cộng những phương pháp hai tầm nhìn gọi trên sẽ làm cho máy ảnh làm việc xem trước của bạn rõ ràng :)

Vì vậy, tôi chỉ có thể cung cấp 1 cho mỗi bạn là tất cả các bạn xứng đáng với nó;)

-2

Đây là giải pháp thay thế cho tất cả các phương thức gọi lại, tất cả có thể phải tuân theo cùng hành vi đơn hàng sự kiện chưa xác định với chu kỳ hoạt động. Trừ khi bạn sẽ kiểm tra tất cả mã android cho mỗi cuộc gọi trở lại bạn sử dụng để xác định trình kích hoạt gốc và ai kiểm soát việc triển khai và hy vọng rằng cơ sở mã không thay đổi trong tương lai, có thể thực sự tuyên bố rằng thứ tự sự kiện giữa các cuộc gọi lại và các sự kiện vòng đời hoạt động có thể được đảm bảo.

Hiện tại, các tương tác này của đơn đặt hàng thường có thể được gọi là hành vi không xác định, cho mục đích phát triển.Vì vậy, tốt nhất là luôn luôn xử lý chính xác hành vi không xác định này, như vậy nó sẽ không bao giờ là một vấn đề ở nơi đầu tiên, bằng cách đảm bảo các đơn đặt hàng được xác định hành vi.

Ví dụ Sony Xperia của tôi về chế độ ngủ, xoay vòng ứng dụng hiện tại của tôi bằng cách hủy ứng dụng và sau đó khởi động lại và đặt nó vào trạng thái tạm dừng, tin hay không.

Bao nhiêu sự kiện sắp xếp hành vi thử nghiệm google cung cấp trong SDK của họ như là xây dựng thử nghiệm đặc biệt cho môi trường máy chủ thực hiện tôi không biết, nhưng họ chắc chắn cần phải nỗ lực để đảm bảo, hành vi của các đơn đặt hàng sự kiện đều bị khóa khá nghiêm khắc về vấn đề này.

https://code.google.com/p/android/issues/detail?id=214171&sort=-opened&colspec=ID%20Status%20Priority%20Owner%20Summary%20Stars%20Reporter%20Opened

nhập android.util.Log; nhập android.util.SparseArray;

/** * Được tạo bởi woliver vào ngày 2016/06/24. * * Môi trường máy chủ Android, quy định Chu kỳ hoạt động cho OnCreate, onStart, onResume, onPause, onStop, onDestory, * nơi chúng tôi yêu cầu phát hành bộ nhớ và xử lý cho các ứng dụng khác để sử dụng. * Khi tiếp tục, chúng tôi được yêu cầu vào các thời điểm để rebind và kích hoạt các mục này với các đối tượng khác. * Thông thường các đối tượng khác cung cấp các phương thức gọi lại từ môi trường máy chủ cung cấp * một onCreated và onDestroy, trong đó chúng ta chỉ có thể liên kết với đối tượng này từ OnCreated và loose * out bind onDestory. * Các loại phương thức gọi lại này, thời gian chạy ngẫu nhiên là điều khiển bởi môi trường máy chủ * và không đảm bảo rằng hành vi/thứ tự thực hiện Vòng đời hoạt động và các phương thức gọi lại * vẫn nhất quán. * Với mục đích phát triển, các tương tác và thứ tự thực thi có thể được gọi là undefined * vì nó phụ thuộc vào trình triển khai thực hiện máy chủ, samsung, sony, htc. * * Xem tài liệu nhà phát triển sau: https://developer.android.com/reference/android/app/Activity.html * Trích: * Nếu hoạt động bị che khuất hoàn toàn bởi một hoạt động khác, nó sẽ bị dừng lại. Tuy nhiên, nó vẫn giữ lại tất cả trạng thái * và thông tin thành viên, nó không còn hiển thị với người dùng nữa nên cửa sổ của nó là * bị ẩn và thường sẽ bị hệ thống giết khi cần bộ nhớ ở nơi khác. * EndQuato: * * Nếu hoạt động không bị ẩn, thì bất kỳ cuộc gọi lại nào được mong đợi sẽ được gọi bởi máy chủ * hệ thống, sẽ không được gọi, chẳng hạn như giao diện OnCreate và OnDestory gọi lại SurfaceView. * Điều này có nghĩa là bạn sẽ phải dừng đối tượng đã được liên kết với SurfaceView chẳng hạn như máy ảnh * tạm dừng và sẽ không bao giờ rebind đối tượng khi cuộc gọi lại OnCreate sẽ không bao giờ được gọi. * */

public abstract class WaitAllActiveExecuter<Size> 
{ 
    private SparseArray<Boolean> mReferancesState = null; 

// Use a dictionary and not just a counter, as hosted code 
// environment implementer may make a mistake and then may double executes things. 
private int mAllActiveCount = 0; 
private String mContextStr; 

public WaitAllActiveExecuter(String contextStr, int... identifiers) 
{ 
    mReferancesState = new SparseArray<Boolean>(identifiers.length); 

    mContextStr = contextStr; 

    for (int i = 0; i < identifiers.length; i++) 
     mReferancesState.put(identifiers[i], false); 
} 

public void ActiveState(int identifier) 
{ 
    Boolean state = mReferancesState.get(identifier); 

    if (state == null) 
    { 
     // Typically panic here referance was not registered here. 
     throw new IllegalStateException(mContextStr + "ActiveState: Identifier not found '" + identifier + "'"); 
    } 
    else if(state == false){ 

     mReferancesState.put(identifier, true); 
     mAllActiveCount++; 

     if (mAllActiveCount == mReferancesState.size()) 
      RunActive(); 
    } 
    else 
    { 
     Log.e(mContextStr, "ActivateState: called to many times for identifier '" + identifier + "'"); 
     // Typically panic here and output a log message. 
    } 
} 

public void DeactiveState(int identifier) 
{ 
    Boolean state = mReferancesState.get(identifier); 

    if (state == null) 
    { 
     // Typically panic here referance was not registered here. 
     throw new IllegalStateException(mContextStr + "DeActiveState: Identifier not found '" + identifier + "'"); 
    } 
    else if(state == true){ 

     if (mAllActiveCount == mReferancesState.size()) 
      RunDeActive(); 

     mReferancesState.put(identifier, false); 
     mAllActiveCount--; 
    } 
    else 
    { 
     Log.e(mContextStr,"DeActiveState: State called to many times for identifier'" + identifier + "'"); 
     // Typically panic here and output a log message. 
    } 
} 

private void RunActive() 
{ 
    Log.v(mContextStr, "Executing Activate"); 

    ExecuterActive(); 
} 

private void RunDeActive() 
{ 
    Log.v(mContextStr, "Executing DeActivate"); 

    ExecuterDeActive(); 
} 


abstract public void ExecuterActive(); 

abstract public void ExecuterDeActive(); 
} 

Ví dụ về thực hiện và sử dụng các lớp, trong đó đề với hoặc hành vi không xác định của android chủ môi trường người thực hiện.

private final int mBCTSV_SurfaceViewIdentifier = 1; 
private final int mBCTSV_CameraIdentifier = 2; 

private WaitAllActiveExecuter mBindCameraToSurfaceView = 
     new WaitAllActiveExecuter("BindCameraToSurfaceViewe", new int[]{mBCTSV_SurfaceViewIdentifier, mBCTSV_CameraIdentifier}) 
{ 
    @Override 
    public void ExecuterActive() { 

     // Open a handle to the camera, if not open yet and the SurfaceView is already intialized. 
     if (mCamera == null) 
     { 
      mCamera = Camera.open(mCameraIDUsed); 

      if (mCamera == null) 
       throw new RuntimeException("Camera could not open"); 

      // Look at reducing the calls in the following two methods, some this is unessary. 
      setDefaultCameraParameters(mCamera); 
      setPreviewSizesForCameraFromSurfaceHolder(getSurfaceHolderForCameraPreview()); 
     } 

     // Bind the Camera to the SurfaceView. 
     try { 
      mCamera.startPreview(); 
      mCamera.setPreviewDisplay(getSurfaceHolderForCameraPreview()); 
     } catch (IOException e) { 

      e.printStackTrace(); 
      ExecuterDeActive(); 

      throw new RuntimeException("Camera preview could not be set"); 
     } 
    } 

    @Override 
    public void ExecuterDeActive() { 

     if (mCamera != null) 
     { 
      mCamera.stopPreview(); 

      mCamera.release(); 
      mCamera = null; 
     } 
    } 
}; 

@Override 
protected void onPause() { 


    mBindCameraToSurfaceView.DeactiveState(mBCTSV_CameraIdentifier); 

    Log.v(LOG_TAG, "Activity Paused - After Super"); 
} 

@Override 
public void onResume() { 

    mBindCameraToSurfaceView.ActiveState(mBCTSV_CameraIdentifier); 
} 

private class SurfaceHolderCallback implements SurfaceHolder.Callback 
{ 
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
    Log.v(LOG_TAG, "Surface Changed"); 

    } 

    public void surfaceCreated(SurfaceHolder surfaceHolder) { 

     Log.v(LOG_TAG, "Surface Created"); 
     mBindCameraToSurfaceView.ActiveState(mBCTSV_SurfaceViewIdentifier); 
    } 

    public void surfaceDestroyed(SurfaceHolder arg0) { 

     Log.v(LOG_TAG, "Surface Destoryed"); 
     mBindCameraToSurfaceView.DeactiveState(mBCTSV_SurfaceViewIdentifier); 
    } 
} 
Các vấn đề liên quan