Develop -- Training(十三) -- 拍照

在富媒体流行以前,这个世界是令人沮丧的和没有特色的。还记得戈弗吗?我们从来都不会这样做的。对于应用程序来说,逐渐成为用户生活的一部分,应该有一种途径把他们的生活相关联起来。使用车载摄像头,能够增加用户看出他们周围的一切,制作独一无二的头像,寻找角落里的僵尸,或者分享他们的经验。

简单的拍照

假设你实现了一个人群来源的气象服务,制作一个全球的气象地图混合着照片和天气,通过在设备上运行应用程序客户端。集成的照片只是你应用程序的一个小部分。你想通过最简单的API去拍照,而不用去改造相机。令人高兴的是,大多数安卓装置已经安装至少一个的相机程序。在这节课,你要学习怎样拍照?

1.请求相机权限

如果一个应用程序的基本功能是拍照的话,然后在设备的Google Play的有一个相机里会限制它的出现。为了宣传应用程序依赖一个相机,要在manifiest文件中加入< uses-feature >标签。

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

如果应用程序被使用,但是不需要为了一个功能而请求相机,只要把android:required的值替换成false。这么做,Google Play将允许在没有相机的设备上可以下载你的应用程序。然而,你有责任在运行时检查相机的可用性通过hasSystemFeature(PackageManager.FEATURE_CAMERA)方法。如果相机不可用,你应该禁止相机功能。

2.使用相机应用程序拍照

安卓中有一种方法,动态委托给其他应用程序通过一个意图描述你想要做的事。这个过程由三个部分:意图本身,启动一个外部Activity,一些代码处理图片数据当Activity返回获取焦点时。

拍照的功能

static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

startActivityForResult()这个方法是包访问级别的,前提是要调用resolveActivity()方法,然后返回第一个Activity的组件能够处理这个Intent。当你调用startActivityForResult()使用了一个Intent而没有应用程序能用处理它,应用程序就会崩溃,这是和重要的。只要结果不是空的,这个Intent就是安全的。

3.获取缩略图

如果拍照这一壮举不是应用程序最大的成就的话,那么你可能想得到从相机应用程序返回的图片,然后用它来做一些事情。

Android 相机应用程序编码图片,通过onActivityResult()返回一个Intent,通过key的“data”,值是一个小位图。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        mImageView.setImageBitmap(imageBitmap);
    }
}

注意:这个缩略图“data”可能是一个很好的图标,但不是很多。处理全尺寸的图片可能需要更多一点的工作。

4.保存全部尺寸照片

安卓相机应用程序,如果你给一个文件保存全部尺寸。你必须提供一个高质量文件名称,相机应用程序应该保存照片。

一般的,用户用相机拍照的话,图片应该保存在额外的存储区域,所以的应用程序都可以访问到。getExternalStoragePublicDirectory() 方法和 DIRECTORY_PICTURES 参数提供了一个分享图片的目录,通过这个方法提供的目录可以在所以应用之间分享,读和写分别要 READ_EXTERNAL_STORAGERITE_EXTERNAL_STORAGE 权限,写的权限暗示着也可以读,所以,如果你只需要写入额外的存储,只需要申请一个权限:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

然而,你的照片只保存在你的应用程序之中,你能使用 getExternalFilesDir() 方法提供的目录。在安卓4.3或者更低,写入这个目录页需要申请 WRITE_EXTERNAL_STORAGE 权限。但是,从安卓4.4开始,这个权限不再需要申请,因为这个目录不能被其他程序所访问。所以,你在声明这个权限的时候,应该申请安卓的最低版本号:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
    ...
</manifest>

注意:getExternalFilesDir() 方法获取的目录,当应用被卸载的时候,目录也会被删除。

一旦你决定该文件的目录,你需要创建一个冲突的文件名。你可能希望在一个成员变量中保存路径以供以后使用。下面是一个示例解决方案,使用日期时间戳返回新照片的唯一的文件名:

String mCurrentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}

这个方法创建了一个照片文件,你现在可以通过意图来查看它:

static final int REQUEST_TAKE_PHOTO = 1;

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there‘s a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            // Error occurred while creating the File
            ...
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                    Uri.fromFile(photoFile));
            startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
        }
    }
}

5.添加照片到图库

当你通过意图创建了一张照片,你应该知道照片的位置,因为你设置了它保存的第一个地方。 对于其他人来说,也许最简单的方法是使你的照片的访问是使其从系统的媒体供应商访问。

注意:如果你保存照片的目录是通过这个 getExternalFilesDir() 方法创建的,media 不能扫描访问到这个文件,因为这个目录是应用程序私有的。

下面的示例演示了如何调用系统的媒体扫描仪向媒体提供程序的数据库中添加照片,让其他的安卓程序也可以访问到这些图片。

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

6.解码缩放图片

管理很多的高分频的图片是很复杂的,因为内存有限。如果你发现应用程序在运行时超出了内存,只是在显示一些图片的额时候。你可以大大减少动态的内存堆通过扩大JPEG为存储阵列,已经缩放到指定View的适合大小。

private void setPic() {
    // Get the dimensions of the View
    int targetW = mImageView.getWidth();
    int targetH = mImageView.getHeight();

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
    mImageView.setImageBitmap(bitmap);
}

录制视频很简单

你的应用程序有一个工作要做,整合视频只是其中的一小部分。你想做拍视频这样的小事,而不是重塑摄像机。令人高兴的是,大多数安卓设备已经有记录的视频摄像头的应用程序。

1.请求相机权限

为了宣传应用程序依赖于有一个摄像头,注册清单文件:

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

如果应用程序使用,但并不需要一个摄像头功能,这个 android:required 标签的值可以设置为 false。这样做,Google Play 将允许没有相机的设备来下载你的应用程序。你可以通过 hasSystemFeature(PackageManager.FEATURE_CAMERA) 方法来检查相机的可用性。如果摄像机无法使用,则应禁用摄像功能。

2.通过相机应用程序录制视频

动作委托给其他应用程序调用一个意图,描述你想要做的事情,是安卓的一个方法途径。这包括三个部分:意图本身,被启动的 Activity,并当焦点返回到你的 Activity 的一些代码来处理视频。

// 录制视频
static final int REQUEST_VIDEO_CAPTURE = 1;

private void dispatchTakeVideoIntent() {
    Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
    if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takeVideoIntent, REQUEST_VIDEO_CAPTURE);
    }
}

注意:调用 startActivityForResult() 方法的前提是要调用 resolveActivity() 方法,它返回能够处理这个 Intent 的第一个 Activity 组件。如果没有应用程序能够处理这个 Intent,调用 startActivityForResult() 方法就会使应用程序崩溃。所以,如果这个结果不是空,使用这个 Intent 就是安全的。

3.查看视频

Android相机应用程序返回的视频,通过 onActivityResult() 方法中的 Intent ,得到一个 Uri 指向存储视频的位置。下面的代码检索该视频并将其显示在一个VideoView上。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_VIDEO_CAPTURE && resultCode == RESULT_OK) {
        Uri videoUri = intent.getData();
        mVideoView.setVideoURI(videoUri);
    }
}

控制摄像机

在这节课,我们将讨论怎样直接使用 framework API 来控制摄像机硬件。

直接控制一个设备的摄像机的代码要求比拍照和录制视频更多,从已经存在的摄像机应用。然而,如果你想建立一个专业的摄像机应用或者集成一些其他的东西在你应用程序的 UI 上,这节课叫你怎么做。

1.打开相机对象

得到相机对象的实例是你直接控制相机的第一步。推荐你打开相机的方式是在一条子线程中的 onCreate() 方法中打开相机,因为安卓本身的相机程序也是这样做的。这是一个很好的方法,因为打开相机需要一点时间,它可能导致 UI 线程阻塞。一个基本的实现方法,打开相机在 onResume() 方法中,因为它能是代码重用,并且流程十分简单。

调用 Camera.open() 方法打开相机,如果相机正在被另一个程序使用,会抛出异常,所以我们要捕获:

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        mCamera = Camera.open(id);
        qOpened = (mCamera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    mPreview.setCamera(null);
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

自动 API 9以上,framework 支持多个相机,如果你使用老的 API,调用的 open() 无参数的方法,得到的第一个是后置摄像头。

2.创建相机预览

拍摄照片通常要求用户能够看见他们当前的场景,在他们按下快门之前。为了能够这样做,你可以使用 SurfaceView 绘制出预览场景,通过相机传感器捕捉出当前的场景。

PreView 类

为了开始显示预览,你需要 PreView 类。这个 PreView类要求实现 android.view.SurfaceHolder.Callback 接口,用来从相机硬件应用程序传递图像数据。

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView mSurfaceView;
    SurfaceHolder mHolder;

    Preview(Context context) {
        super(context);

        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = mSurfaceView.getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

这个预览类必须要传递一个相机对象,在开始预览图片之前。

设置并开始预览

照相机实例和其相关预览必须按照特定的顺序创建,相机对象是第一个。在下面的代码段中,初始化照相机的过程被封装在 setCamera() 方法中,使得 Camera.startPreview() 被称为 setCamera() 方法,每当用户做某些改变相机,预览也必须在预览类重启 surfaceChanged() 回调方法。

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        mSupportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

修改相机设置

相机设置改变相机拍摄的照片,从缩放级别到曝光补偿的方式。

// 修改预览大小
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

设置预览方向

大多数相机应用程序锁定显示为横屏模式,这是因为相机传感器的自然方向。这个设置不会阻止你采取纵向模式的照片,因为设备的方向被记录在 EXIF 的头中。该 setCameraDisplayOrientation() 方法可以让你改变预览的方式,而不影响图像的方式显示记录。然而,安卓的 API 14级之前,在改变方向之前必须停止预览,然后重新启动相机。

拍照

使用 Camera.takePicture() 方法拍照,在预览已经开始的时候。你可以创建 Camera.PictureCallback 和 Camera.ShutterCallback 对象并将其传递到 Camera.takePicture() 。

如果你想不间断的抓拍图像,你可以创建一个 Camera.PreviewCallback实现 onPreviewFrame() 方法 。在这两者之间的一些东西,你可以捕捉选择的预览画面,或设置延迟作用调用 takePicture() 。

重启预览

拍摄照片后,必须重启动预览,在用户可以拍摄另一张照片之前。

@Override
public void onClick(View v) {
    switch(mPreviewState) {
    case K_STATE_FROZEN:
        mCamera.startPreview();
        mPreviewState = K_STATE_PREVIEW;
        break;

    default:
        mCamera.takePicture( null, rawCallback, null);
        mPreviewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

3.停止预览并释放摄像头

一旦你的应用程序使用完摄像机,是时候开始清理了。特别是,你必须要释放摄像机对象,或者你的应用程序有崩溃的风险包括你应用程序自己的的新实例。

什么时候应用停止预览,释放摄像机?

public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}
时间: 2024-10-11 04:23:57

Develop -- Training(十三) -- 拍照的相关文章

Develop -- Training(十五) -- 显示高效位图

学习怎样使用常见的技术处理并加载一个 Bitmap 对象,保持用户界面(UI)组件的响应,并且避免超过应用程序的内存限制.如果你不小心的话,Bitmap 可以快速的消耗内存预算,主要会导致应用程序崩溃由于一个可怕的异常: java.lang.OutofMemoryError: bitmap size exceeds VM budget. 下面有很多原因,为什么在你的应用程序加载 Bitmap 的时候会很复杂: 1.移动设备通常具有有限的系统资源.Android 设备的单个应用程序最少有16M的内

Develop -- Training(十四) -- 打印内容

Android 用户经常在他们的设备上查看完整的内容,但是有时候在一个屏幕上不能完全地显示某个人的一些信息.能够打印信息从你的 Android 应用程序给用户看见较大的内容从你的应用程序或者分享其他人的应用程序,但不使用你的应用程序.打印也允许他们创建一个快照信息,而不依赖于有一个设备.足够的电池电量.或者一个无线网连接. 在 Android 4.4 或者更高,该框架提供了打印图片和文档的服务,直接从 Android 应用程序调用.这次培训描述如何打印应用程序,包括印刷图像.HTML页面和创建自

Android设计和开发系列第二篇:Action Bar(Develop—Training)

Adding the Action Bar GET STARTED DEPENDENCIES AND PREREQUISITES Android 2.1 or higher YOU SHOULD ALSO READ Action Bar Implementing Effective Navigation DESIGN GUIDE Action Bar The action bar is one of the most important design elements you can imple

Develop -- Training(十六) -- 显示绘图和OpenGL ES

Android framework提供了许多标准的工具,来创建有吸引力的.功能丰富的用户图形界面.但是,如果你想要更多的控制权,比如在应用程序的屏幕上绘图,或者冒险进入三维图形,你需要使用不同的工具.通过Android framework提供的OpenGL ES的API提供了一套显示高端的工具,动画图像超出你的想象,许多Android设备的图像处理单元得到了加速(GPUs). 这节课主要开发一个OpenGL应用程序.包括设置.画对象.移动对象元素.响应触摸输入事件. 这节课的示例代码使用的是Op

宋宝华- Docker 背后的故事之名称空间(1)

名称空间是在OS之上实现容器与主机隔离,以及容器之间互相隔离的Linux内核核心技术.根据<Docker 最初的2小时(Docker从入门到入门)>一文,名称空间本质上就是在不同的工作组里面封官许愿,让大家在各自的部门里面都是manager,而且彼此不冲突.本文接下来从细节做一些讨论. 由于本文敲的命令既有可能位于主机,又有可能位于新的名称空间(模拟容器),为了避免搞乱你的脑子,下面主机命令一概采用本颜色,而模拟容器类的命令一概采用本颜色.色盲读者,敬请谅解. 名称空间是什么? 名称空间(Na

湖南省第十三届大学生计算机程序设计竞赛 Football Training Camp 贪心

2007: Football Training Camp[原创-转载请说明] Submit Page   Summary   Time Limit: 1 Sec     Memory Limit: 128 Mb     Submitted: 228     Solved: 30 Description 在一次足球联合训练中一共有n支队伍相互进行了若干场比赛. 对于每场比赛,赢了的队伍得3分,输了的队伍不得分,如果为平局则两支队伍各得1分. Input 输入包含不超过1000组数据. 每组数据的第

Training的第十三天

今天在网上浏览查找了资料大概知道任务三的改进怎么做了.我觉得可行的方法有一:利用 switch args[0]的方法,把要实现的三个功能的代码放到三个case里面,但是在做的时候出现了一个错误"索引超出了数组界限". 接着我想到了第二个方法:在一个解决方案里面创建三个项目,分别放进实现三个不同功能的代码,但是在这里我又产生了新的疑问:如果三个项目放在同一个解决方案里面,假如我想实现文件复制的功能,那我在控制台里面输入什么样的代码才能进行文件复制呢?(本人的C#基础较为薄弱,至今只做过只

Training的二十三天

变量及其命名方法有几种,可是我以前在写程序的时候一直没有用到.今天看了之后知道了自己存在这样的不足,所以在以后的基本知识学习中还会继续注意,纠正自己的不足.另外今天还重新复习了一下数据类型,知道了其大概的用法. Training的二十三天

Training的第三十三天(回校的第七周)

在这近一个月的时间里,在C++的课堂上我们学习了C++独有的控制格式的setw()这些语句,还学习了while:do-while:for:这三大循环:switch 这个分支结构:学习了循环当然也少不了跳出循环的break,continue语句.除此之外,还学习了一个C语言里面的prinft和scanf这两个超级难搞的语句.总之,这个月月来学习C++感觉难度还可以适应,但是也感觉到有点难了. 在学习c#上面,今天学习了C#里面的三元表达式和break,continue语句,算是对近两周C++的学习