我具有一项功能,用户可以单击多张照片并将其保存(使用camera2 api完成)。我想更新从ImageAvailableListener
拍摄的图像数量。我已经使用Activity.RunOnUiThread
在相机片段中编写了一个updateUI方法。我假设是因为RunOnUiThread
我的应用无法响应并崩溃了。
下面是代码:
ImageAvailableListener
public class ImageAvailableListener : Java.Lang.Object, ImageReader.IOnImageAvailableListener
{
public ImageAvailableListener(CameraFragment fragment)//, File file)
{
if (fragment == null)
throw new System.ArgumentNullException("fragment");
owner = fragment;
}
private readonly File file;
private readonly CameraFragment owner;
public void OnImageAvailable(ImageReader reader)
{
owner.mBackgroundHandler.Post(new AddImageToList(reader.AcquireNextImage(), owner));//, owner.mFile));
}
private class AddImageToList : Java.Lang.Object, IRunnable
{
private CameraFragment fragment;
private Image mImage;
public AddImageToList(Image image, CameraFragment _fragment)
{
if (image == null)
throw new System.ArgumentNullException("image");
mImage = image;
fragment = _fragment;
}
public void Run()
{
try
{
ByteBuffer buffer = mImage.GetPlanes()[0].Buffer;
byte[] bytes = new byte[buffer.Remaining()];
buffer.Get(bytes);
string base64String = Convert.ToBase64String(bytes, 0, bytes.Length);
string guid = Guid.NewGuid().ToString().ToUpper();
fragment.capturedPicBase64.Add(new Data.Models.ViewModel.DocumentImageViewModel { imgName = string.Format("{0}", guid), base64 = base64String });//base64String);
fragment.updateUICount();
mImage.Close();
}
catch (System.Exception ex)
{
}
}
}
}
相机片段
public class CameraFragment : Frag, View.IOnClickListener, FragmentCompat.IOnRequestPermissionsResultCallback
{
private static readonly SparseIntArray ORIENTATIONS = new SparseIntArray();
public static readonly int REQUEST_CAMERA_PERMISSION = 1;
private static readonly string FRAGMENT_DIALOG = "dialog";
// Tag for the {@link Log}.
private static readonly string TAG = "CameraFragment";
// Camera state: Showing camera preview.
public const int STATE_PREVIEW = 0;
// Camera state: Waiting for the focus to be locked.
public const int STATE_WAITING_LOCK = 1;
// Camera state: Waiting for the exposure to be precapture state.
public const int STATE_WAITING_PRECAPTURE = 2;
//Camera state: Waiting for the exposure state to be something other than precapture.
public const int STATE_WAITING_NON_PRECAPTURE = 3;
// Camera state: Picture was taken.
public const int STATE_PICTURE_TAKEN = 4;
// Max preview width that is guaranteed by Camera2 API
private static readonly int MAX_PREVIEW_WIDTH = 1920;
// Max preview height that is guaranteed by Camera2 API
private static readonly int MAX_PREVIEW_HEIGHT = 1080;
// TextureView.ISurfaceTextureListener handles several lifecycle events on a TextureView
private SurfaceTextureListener mSurfaceTextureListener;
// ID of the current {@link CameraDevice}.
private string mCameraId;
// An AutoFitTextureView for camera preview
private AutoFitTextureView mTextureView;
// A {@link CameraCaptureSession } for camera preview.
public CameraCaptureSession mCaptureSession;
// A reference to the opened CameraDevice
public CameraDevice mCameraDevice;
// The size of the camera preview
private Size mPreviewSize;
// CameraDevice.StateListener is called when a CameraDevice changes its state
private CameraStateListener mStateCallback;
// An additional thread for running tasks that shouldn't block the UI.
private HandlerThread mBackgroundThread;
// A {@link Handler} for running tasks in the background.
public Handler mBackgroundHandler;
// An {@link ImageReader} that handles still image capture.
private ImageReader mImageReader;
// This is the output file for our picture.
public List<ImageViewModel> capturedPicBase64 { get; set; } = new List<ImageViewModel>();
// This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a
// still image is ready to be saved.
private ImageAvailableListener mOnImageAvailableListener;
//{@link CaptureRequest.Builder} for the camera preview
public CaptureRequest.Builder mPreviewRequestBuilder;
// {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
public CaptureRequest mPreviewRequest;
// The current state of camera state for taking pictures.
public int mState = STATE_PREVIEW;
// A {@link Semaphore} to prevent the app from exiting before closing the camera.
public Semaphore mCameraOpenCloseLock = new Semaphore(1);
// Whether the current camera device supports Flash or not.
private bool mFlashSupported;
// Orientation of the camera sensor
private int mSensorOrientation;
// A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.
public CameraCaptureListener mCaptureCallback;
// Shows a {@link Toast} on the UI thread.
public void ShowToast(string text)
{
if (Activity != null)
{
Activity.RunOnUiThread(new ShowToastRunnable(Activity.ApplicationContext, text));
}
}
private class ShowToastRunnable : Java.Lang.Object, IRunnable
{
private string text;
private Context context;
public ShowToastRunnable(Context context, string text)
{
this.context = context;
this.text = text;
}
public void Run()
{
Toast.MakeText(context, text, ToastLength.Short).Show();
}
}
public void updateUICount()
{
if (Activity != null)
{
Activity.RunOnUiThread(() =>
{
_shootMP = null;
AudioManager meng = (AudioManager)Activity.GetSystemService(Context.AudioService);
int volume = meng.GetStreamVolume(Stream.Notification);
if (volume != 0)
{
if (_shootMP == null)
_shootMP = MediaPlayer.Create(Activity, Uri.Parse("file:///system/media/audio/ui/camera_click.ogg"));
if (_shootMP != null)
_shootMP.Start();
}
maximumPics++;
if (maximumPics > 0)
{
count.Text = string.Format("({0})", maximumPics);
back.Visibility = ViewStates.Gone;
done.Visibility = ViewStates.Visible;
}
else
{
back.Visibility = ViewStates.Visible;
done.Visibility = ViewStates.Gone;
}
});
}
}
private static Size ChooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio)
{
// Collect the supported resolutions that are at least as big as the preview Surface
var bigEnough = new List<Size>();
// Collect the supported resolutions that are smaller than the preview Surface
var notBigEnough = new List<Size>();
int w = aspectRatio.Width;
int h = aspectRatio.Height;
for (var i = 0; i < choices.Length; i++)
{
Size option = choices[i];
if ((option.Width <= maxWidth) && (option.Height <= maxHeight) &&
option.Height == option.Width * h / w)
{
if (option.Width >= textureViewWidth &&
option.Height >= textureViewHeight)
{
bigEnough.Add(option);
}
else
{
notBigEnough.Add(option);
}
}
}
// Pick the smallest of those big enough. If there is no one big enough, pick the
// largest of those not big enough.
if (bigEnough.Count > 0)
{
return (Size)Collections.Min(bigEnough, new CompareSizesByArea());
}
else if (notBigEnough.Count > 0)
{
return (Size)Collections.Max(notBigEnough, new CompareSizesByArea());
}
else
{
// Log.Error(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
public static CameraFragment NewInstance()
{
return new CameraFragment();
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
capturedPicBase64 = Activities.ViewActivity._documentImageViewModel;
mStateCallback = new CameraStateListener(this);
mSurfaceTextureListener = new SurfaceTextureListener(this);
// fill ORIENTATIONS list
ORIENTATIONS.Append((int)SurfaceOrientation.Rotation0, 90);
ORIENTATIONS.Append((int)SurfaceOrientation.Rotation90, 0);
ORIENTATIONS.Append((int)SurfaceOrientation.Rotation180, 270);
ORIENTATIONS.Append((int)SurfaceOrientation.Rotation270, 180);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.Inflate(Resource.Layout.cameraFragment, container, false);
}
public override void OnViewCreated(View view, Bundle savedInstanceState)
{
mTextureView = (AutoFitTextureView)view.FindViewById(Resource.Id.texture);
view.FindViewById(Resource.Id.picture).SetOnClickListener(this);
done = view.FindViewById<ImageView>(Resource.Id.done);
done.Click += CameraFragment_Click;
count = view.FindViewById<TextView>(Resource.Id.count);
back = view.FindViewById<TextView>(Resource.Id.back);
back.Click += Back_Click;
if (Activities.ViewActivity.finalImageViewModel.Count - 1 > 0)
{
maximumPics = Activities.ViewActivity.finalImageViewModel.Count - 1;
count.Text = string.Format("({0})", maximumPics);
}
}
public override void OnDestroy()
{
base.OnDestroy();
capturedPicBase64=null;
capturedPics = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
private void Back_Click(object sender, EventArgs e)
{
Activity.Finish();
}
private void CameraFragment_Click(object sender, EventArgs e)
{
Activities.ViewActivity.finalImageViewModel.AddRange(capturedPicBase64);
Activity.Finish();
Intent _viewActivity = new Intent(Activity, typeof(ViewActivity));
StartActivity(_viewActivity);
}
public override void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
mCaptureCallback = new CameraCaptureListener(this);
mOnImageAvailableListener = new ImageAvailableListener(this);//, mFile);
}
public override void OnResume()
{
base.OnResume();
StartBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (mTextureView.IsAvailable)
{
OpenCamera(mTextureView.Width, mTextureView.Height);
}
else
{
mTextureView.SurfaceTextureListener = mSurfaceTextureListener;
}
}
public override void OnPause()
{
CloseCamera();
StopBackgroundThread();
base.OnPause();
}
private void RequestCameraPermission()
{
if (FragmentCompat.ShouldShowRequestPermissionRationale(this, Manifest.Permission.Camera))
{
new ConfirmationDialog().Show(ChildFragmentManager, FRAGMENT_DIALOG);
}
else
{
FragmentCompat.RequestPermissions(this, new string[] { Manifest.Permission.Camera },
REQUEST_CAMERA_PERMISSION);
}
}
public void OnRequestPermissionsResult(int requestCode, string[] permissions, int[] grantResults)
{
if (requestCode != REQUEST_CAMERA_PERMISSION)
return;
if (grantResults.Length != 1 || grantResults[0] != (int)Permission.Granted)
{
// ErrorDialog.NewInstance(GetString(Resource.String.request_permission))
// .Show(ChildFragmentManager, FRAGMENT_DIALOG);
}
}
// Sets up member variables related to camera.
private void SetUpCameraOutputs(int width, int height)
{
var activity = Activity;
var manager = (CameraManager)activity.GetSystemService(Context.CameraService);
try
{
for (var i = 0; i < manager.GetCameraIdList().Length; i++)
{
var cameraId = manager.GetCameraIdList()[i];
CameraCharacteristics characteristics = manager.GetCameraCharacteristics(cameraId);
// We don't use a front facing camera in this sample.
var facing = (Integer)characteristics.Get(CameraCharacteristics.LensFacing);
if (facing != null && facing == (Integer.ValueOf((int)LensFacing.Front)))
{
continue;
}
var map = (StreamConfigurationMap)characteristics.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
if (map == null)
{
continue;
}
// For still image captures, we use the largest available size.
Size largest = (Size)Collections.Max(Arrays.AsList(map.GetOutputSizes((int)ImageFormatType.Jpeg)),
new CompareSizesByArea());
mImageReader = ImageReader.NewInstance(largest.Width, largest.Height, ImageFormatType.Jpeg, /*maxImages*/2);
mImageReader.SetOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
// Find out if we need to swap dimension to get the preview size relative to sensor
// coordinate.
var displayRotation = activity.WindowManager.DefaultDisplay.Rotation;
//noinspection ConstantConditions
mSensorOrientation = (int)characteristics.Get(CameraCharacteristics.SensorOrientation);
bool swappedDimensions = false;
switch (displayRotation)
{
case SurfaceOrientation.Rotation0:
case SurfaceOrientation.Rotation180:
if (mSensorOrientation == 90 || mSensorOrientation == 270)
{
swappedDimensions = true;
}
break;
case SurfaceOrientation.Rotation90:
case SurfaceOrientation.Rotation270:
if (mSensorOrientation == 0 || mSensorOrientation == 180)
{
swappedDimensions = true;
}
break;
default:
//Log.Error(TAG, "Display rotation is invalid: " + displayRotation);
break;
}
Point displaySize = new Point();
activity.WindowManager.DefaultDisplay.GetSize(displaySize);
var rotatedPreviewWidth = width;
var rotatedPreviewHeight = height;
var maxPreviewWidth = displaySize.X;
var maxPreviewHeight = displaySize.Y;
if (swappedDimensions)
{
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.Y;
maxPreviewHeight = displaySize.X;
}
if (maxPreviewWidth > MAX_PREVIEW_WIDTH)
{
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT)
{
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
mPreviewSize = ChooseOptimalSize(map.GetOutputSizes(Class.FromType(typeof(SurfaceTexture))),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
// We fit the aspect ratio of TextureView to the size of preview we picked.
var orientation = Resources.Configuration.Orientation;
if (orientation == Orientation.Landscape)
{
mTextureView.SetAspectRatio(mPreviewSize.Width, mPreviewSize.Height);
}
else
{
mTextureView.SetAspectRatio(mPreviewSize.Height, mPreviewSize.Width);
}
// Check if the flash is supported.
var available = (Boolean)characteristics.Get(CameraCharacteristics.FlashInfoAvailable);
if (available == null)
{
mFlashSupported = false;
}
else
{
mFlashSupported = (bool)available;
}
mCameraId = cameraId;
return;
}
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
catch (NullPointerException e)
{
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
// ErrorDialog.NewInstance(GetString(Resource.String.camera_error)).Show(ChildFragmentManager, FRAGMENT_DIALOG);
}
}
// Opens the camera specified by {@link CameraFragment #mCameraId}.
public void OpenCamera(int width, int height)
{
if (ContextCompat.CheckSelfPermission(Activity, Manifest.Permission.Camera) != Permission.Granted)
{
RequestCameraPermission();
return;
}
SetUpCameraOutputs(width, height);
ConfigureTransform(width, height);
var activity = Activity;
var manager = (CameraManager)activity.GetSystemService(Context.CameraService);
try
{
if (!mCameraOpenCloseLock.TryAcquire(2500, TimeUnit.Milliseconds))
{
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.OpenCamera(mCameraId, mStateCallback, mBackgroundHandler);
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
catch (InterruptedException e)
{
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
// Closes the current {@link CameraDevice}.
private void CloseCamera()
{
try
{
mCameraOpenCloseLock.Acquire();
if (null != mCaptureSession)
{
mCaptureSession.Close();
mCaptureSession = null;
}
if (null != mCameraDevice)
{
mCameraDevice.Close();
mCameraDevice = null;
}
if (null != mImageReader)
{
mImageReader.Close();
mImageReader = null;
}
}
catch (InterruptedException e)
{
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
}
finally
{
mCameraOpenCloseLock.Release();
}
}
// Starts a background thread and its {@link Handler}.
private void StartBackgroundThread()
{
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.Start();
mBackgroundHandler = new Handler(mBackgroundThread.Looper);
}
// Stops the background thread and its {@link Handler}.
private void StopBackgroundThread()
{
mBackgroundThread.QuitSafely();
try
{
mBackgroundThread.Join();
mBackgroundThread = null;
mBackgroundHandler = null;
}
catch (InterruptedException e)
{
e.PrintStackTrace();
}
}
// Creates a new {@link CameraCaptureSession} for camera preview.
public void CreateCameraPreviewSession()
{
try
{
SurfaceTexture texture = mTextureView.SurfaceTexture;
if (texture == null)
{
throw new IllegalStateException("texture is null");
}
// We configure the size of default buffer to be the size of camera preview we want.
texture.SetDefaultBufferSize(mPreviewSize.Width, mPreviewSize.Height);
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.Preview);
mPreviewRequestBuilder.AddTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
List<Surface> surfaces = new List<Surface>();
surfaces.Add(surface);
surfaces.Add(mImageReader.Surface);
mCameraDevice.CreateCaptureSession(surfaces, new CameraCaptureSessionCallback(this), null);
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
public static T Cast<T>(Java.Lang.Object obj) where T : class
{
var propertyInfo = obj.GetType().GetProperty("Instance");
return propertyInfo == null ? null : propertyInfo.GetValue(obj, null) as T;
}
// Configures the necessary {@link android.graphics.Matrix}
// transformation to `mTextureView`.
// This method should be called after the camera preview size is determined in
// setUpCameraOutputs and also the size of `mTextureView` is fixed.
public void ConfigureTransform(int viewWidth, int viewHeight)
{
Activity activity = Activity;
if (null == mTextureView || null == mPreviewSize || null == activity)
{
return;
}
var rotation = (int)activity.WindowManager.DefaultDisplay.Rotation;
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.Height, mPreviewSize.Width);
float centerX = viewRect.CenterX();
float centerY = viewRect.CenterY();
if ((int)SurfaceOrientation.Rotation90 == rotation || (int)SurfaceOrientation.Rotation270 == rotation)
{
bufferRect.Offset(centerX - bufferRect.CenterX(), centerY - bufferRect.CenterY());
matrix.SetRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.Fill);
float scale = Math.Max((float)viewHeight / mPreviewSize.Height, (float)viewWidth / mPreviewSize.Width);
matrix.PostScale(scale, scale, centerX, centerY);
matrix.PostRotate(90 * (rotation - 2), centerX, centerY);
}
else if ((int)SurfaceOrientation.Rotation180 == rotation)
{
matrix.PostRotate(180, centerX, centerY);
}
mTextureView.SetTransform(matrix);
}
// Initiate a still image capture.
private void TakePicture()
{
try
{
if (maximumPics < 10)//(capturedPics.Count + 1 <= 10)
{
LockFocus();
}
else
{
ShowAlert("Maximum 10 images are allowed.");
}
}
catch (System.Exception ex)
{
}
}
public void restartFocus()
{
try
{
mPreviewRequestBuilder.Set(
CaptureRequest.ControlAfTrigger
, (int)ControlAFTrigger.Cancel); //CONTROL_AF_TRIGGER_CANCEL);
if (mCaptureSession != null)
{
mCaptureSession.Capture(
mPreviewRequestBuilder.Build(),
mCaptureCallback,
mBackgroundHandler);
}
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
// Lock the focus as the first step for a still image capture.
private void LockFocus()
{
try
{
// This is how to tell the camera to lock focus.
mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Start);
// Tell #mCaptureCallback to wait for the lock.
mState = STATE_WAITING_LOCK;
if (mCaptureSession != null)
{
mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback,
mBackgroundHandler);
}
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
// Run the precapture sequence for capturing a still image. This method should be called when
// we get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.
public void RunPrecaptureSequence()
{
try
{
// This is how to tell the camera to trigger.
mPreviewRequestBuilder.Set(CaptureRequest.ControlAePrecaptureTrigger, (int)ControlAEPrecaptureTrigger.Start);
// Tell #mCaptureCallback to wait for the precapture sequence to be set.
mState = STATE_WAITING_PRECAPTURE;
mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback, mBackgroundHandler);
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
private CaptureRequest.Builder stillCaptureBuilder;
private TextView count;
private TextView back;
private ImageView done;
private int maximumPics;
private MediaPlayer _shootMP;
// private int countOfPictures=0;
public File _dir { get; set; }
// Capture a still picture. This method should be called when we get a response in
// {@link #mCaptureCallback} from both {@link #lockFocus()}.
public void CaptureStillPicture()
{
try
{
var activity = Activity;
if (null == activity || null == mCameraDevice)
{
return;
}
// This is the CaptureRequest.Builder that we use to take a picture.
if (stillCaptureBuilder == null)
stillCaptureBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.StillCapture);
stillCaptureBuilder.AddTarget(mImageReader.Surface);
// Use the same AE and AF modes as the preview.
stillCaptureBuilder.Set(CaptureRequest.ControlAfMode, (int)ControlAFMode.ContinuousPicture);
SetAutoFlash(stillCaptureBuilder);
// Orientation
int rotation = (int)activity.WindowManager.DefaultDisplay.Rotation;
stillCaptureBuilder.Set(CaptureRequest.JpegOrientation, GetOrientation(rotation));
mCaptureSession.StopRepeating();
mCaptureSession.Capture(stillCaptureBuilder.Build(), new CameraCaptureStillPictureSessionCallback(this), null);
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
// Retrieves the JPEG orientation from the specified screen rotation.
private int GetOrientation(int rotation)
{
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
return (ORIENTATIONS.Get(rotation) + mSensorOrientation + 270) % 360;
}
// Unlock the focus. This method should be called when still image capture sequence is
// finished.
public void UnlockFocus()
{
try
{
// Reset the auto-focus trigger
mPreviewRequestBuilder.Set(CaptureRequest.ControlAfTrigger, (int)ControlAFTrigger.Cancel);
SetAutoFlash(mPreviewRequestBuilder);
// After this, the camera will go back to the normal state of preview.
if (mCaptureSession != null)
{
mCaptureSession.Capture(mPreviewRequestBuilder.Build(), mCaptureCallback,
mBackgroundHandler);
mState = STATE_PREVIEW;
mCaptureSession.SetRepeatingRequest(mPreviewRequest, mCaptureCallback,
mBackgroundHandler);
}
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
}
public void OnClick(View v)
{
if (v.Id == Resource.Id.picture)
{
TakePicture();
}
}
public void SetAutoFlash(CaptureRequest.Builder requestBuilder)
{
if (mFlashSupported)
{
requestBuilder.Set(CaptureRequest.ControlAeMode, (int)ControlAEMode.OnAutoFlash);
}
}
}
如何从runnable()更新UI? This与我的问题类似。
链接到设备日志:deviceLog