Android call camera and photo album

Call camera and photo album

Call camera

Create a new CameraAlbumTest project, and then modify the code in activity_main.xml, as shown below:

	
	<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    android:orientation="vertical"
	    >
	    <Button
	        android:id="@+id/take_photo"
	        android:layout_width="match_parent"
	        android:layout_height="wrap_content"
	        android:text="Take Phone"
	        />
	    <ImageView
	        android:id="@+id/picture"
	        android:layout_width="wrap_content"
	        android:layout_height="wrap_content"
	        android:layout_gravity="center_horizontal"
	        />
	</LinearLayout>

As you can see, there are only two controls in the layout file, a Button and an ImageView.
Button is used to open the camera to take pictures, while ImageView is used to display the captured pictures.

Then start to write the specific logic to call the camera, modify the code in MainActivity,

package net.nyist.lenovo.cameraalbumtest;

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    public static final int TAKE_PHOTO = 1;

    private ImageView picture;

    private Uri imageUri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = (Button) findViewById(R.id.picture);
        takePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建File对象,用于存储拍照后的图片
                //并将它存放在手机SD卡的应用关联缓存数据的位置
                //应用关联缓存目录就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()可以得到这个目录。
                //具体的路径是:/sdcard/Android/data/<package name>/cache
                File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
                try {
                if (outputImage.exists()) {
                    outputImage.delete();
                }
                    outputImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //如果运行设备的系统版本低于Android7.0,
                // 就调用Uri的fromFile()方法将File对象转换成Uri对象
                //这个Uri对象标识着output_image.jpg这张图片的本地真是路径。
                //否则就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。
                //getUriForFile()方法接收三个参数。
                //第一个参数要求传入Context对象
                //第二个参数可以是任意唯一字符串
                //第三个参数则是我们刚刚创建的File对象
                //之所以要进行这样一层转换,是因为从Android7.0系统开始,直接使用本地真是路径的Uri
                //被认为是不安全的,会抛出一个FileUriException异常。
                //而FileProvider则是一种特殊的内容提供器。
                //它使用了和内容提供器类似的机制来对数据进行保护,
                // 可以选择性的将封装过的Uri共享给外部,从而提高了应用的安全性。
                if (Build.VERSION.SDK_INT>= 24){
                    imageUri = FileProvider.getUriForFile(MainActivity.this,"net.nyist.lenovo.cameraalbumtest.fileprovider",outputImage);
                }else{
                    imageUri = Uri.fromFile(outputImage);
                }
                //启动相机程序
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
                startActivityForResult(intent,TAKE_PHOTO);
                //接下来构建出了一个Intent对象,并将这个Intent的action指定为android.media.action.IMAGE_CAPTURE
                //再调用Intent的putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象。
                //最后调用startActivityForResult()来启动活动。
                // 由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的活动去启动。
                //这样照相机程序就会被打开,拍下的照片会传输到output_image.jpg中。
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //刚才是使用startActivityForResult启动活动的,
        // 因此拍完照后有结果返回到onActivityResult()方法中。
        //如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法。
        //将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。
        switch (requestCode){
            case TAKE_PHOTO:
                if (requestCode == RESULT_OK){
                    //将拍摄的照片显示出来
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        picture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }
}

But it’s not over yet. Just mentioned the content provider, so we naturally have to register the content provider in AndroidManifest.xml, as shown below:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.nyist.lenovo.cameraalbumtest">

    <user-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        **<provider
            android:authorities="net.nyist.lenovo.cameraalbumtest.fileprovider"
            android:name="android.support.v4.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.File_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                />

        </provider>**
//其中android:name属性的值是固定的,
//android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致。
//另外这里还在<provider>标签的内部使用<meta-data>来指定Uri的共享路径,并引用了一个@xml/file_paths资源。
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Let's create this resource:
right-click the res directory→New→Directory, create an xml directory, then right-click the xml directory to create a new File, create a file_paths.xml file, and then modify the content in the file_paths.xml file as follows:

<?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:andoird="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path=""/>
</paths>

Among them, external-path is used to specify Uri sharing. The value of the name attribute can be filled in. The value of the path attribute indicates the specific path of the sharing. Setting vacant here means that the real SD card will be shared. Of course, you can also share only The path where we store the image output_image.jpg.
Another point to note is that before the Android 4.4 system, access to the SD card's application-related directory also requires a declaration of permissions. Starting from the 4.4 system, permission declarations are no longer required. So in order to be compatible with phones with older versions of the system, we also need to declare the permission to access the SD card in AndroidManifest.xml.

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

So the code is finished.

###Select photos from album

(This requirement can be said to be difficult to understand. On the one hand, the book is not so detailed. On the other hand, I have a weak foundation. It took two days to figure out the specifics.)

Still modify on the basis of the CameraAlbumTest project, edit the activity_main.xml file, add a button to the layout to select photos from the album, the code is as follows:

 <Button
        android:id="@+id/choose_from_album"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Choose From Album"
        />

Then modify the code in MainActivity to add the logic of selecting photos from the album, the code is as follows:

package net.nyist.lenovo.cameraalbumtest;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.ContactsContract;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    public static final int TAKE_PHOTO = 1;

    private ImageView picture;

    private Uri imageUri;

    public static final int CHOOSE_PHOTO = 2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button takePhoto = (Button) findViewById(R.id.picture);
        Button chooseFromAlbum = (Button) findViewById(R.id.choose_from_album);

        chooseFromAlbum.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else {
                    openAlbum();
                }
            }
        });

        takePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //创建File对象,用于存储拍照后的图片
                //并将它存放在手机SD卡的应用关联缓存数据的位置
                //应用关联缓存目录就是指SD卡中专门用于存放当前应用缓存数据的位置,调用getExternalCacheDir()可以得到这个目录。
                //具体的路径是:/sdcard/Android/data/<package name>/cache
                File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
                try {
                if (outputImage.exists()) {
                    outputImage.delete();
                }
                    outputImage.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //如果运行设备的系统版本低于Android7.0,
                // 就调用Uri的fromFile()方法将File对象转换成Uri对象
                //这个Uri对象标识着output_image.jpg这张图片的本地真是路径。
                //否则就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过的Uri对象。
                //getUriForFile()方法接收三个参数。
                //第一个参数要求传入Context对象
                //第二个参数可以是任意唯一字符串
                //第三个参数则是我们刚刚创建的File对象
                //之所以要进行这样一层转换,是因为从Android7.0系统开始,直接使用本地真是路径的Uri
                //被认为是不安全的,会抛出一个FileUriException异常。
                //而FileProvider则是一种特殊的内容提供器。
                //它使用了和内容提供器类似的机制来对数据进行保护,
                // 可以选择性的将封装过的Uri共享给外部,从而提高了应用的安全性。
                if (Build.VERSION.SDK_INT>= 24){
                    imageUri = FileProvider.getUriForFile(MainActivity.this,"net.nyist.lenovo.cameraalbumtest.fileprovider",outputImage);
                }else{
                    imageUri = Uri.fromFile(outputImage);
                }
                //启动相机程序
                Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
                intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
                startActivityForResult(intent,TAKE_PHOTO);
                //接下来构建出了一个Intent对象,并将这个Intent的action指定为android.media.action.IMAGE_CAPTURE
                //再调用Intent的putExtra()方法指定图片的输出地址,这里填入刚刚得到的Uri对象。
                //最后调用startActivityForResult()来启动活动。
                // 由于我们使用的是一个隐式Intent,系统会找出能够响应这个Intent的活动去启动。
                //这样照相机程序就会被打开,拍下的照片会传输到output_image.jpg中。
            }
        });
    }

    private void openAlbum(){
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        /*
        在这里书上写的不是很详细,我在这里说明一下
        Intent.ACTION_GET_CONTENT这个是调用系统程序用的,
        比如一个打开一个文件的时候会提示你用哪个软件打开,
        setType()就是设置默认用哪种格式打开,
        比如"video/*","audio/amr"
        intent.setType(“image/*”);//选择图片
        intent.setType(“audio/*”); //选择音频
        intent.setType(“video/*”); //选择视频 (mp4 3gp 是Android支持的视频格式)
        intent.setType(“video/;image/”);//同时选择视频和图片 */
        startActivityForResult(intent,CHOOSE_PHOTO);//打开相册
        //第二个参数传入的值为CHOOSE_PHOTO,这样当从相册选择完图片回到onActivityResult()方法时
        //就会进入CHOOSE_PHOTO的case来处理照片。
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if (grantResults.length > 0&&grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    openAlbum();
                }else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //刚才是使用startActivityForResult启动活动的,
        // 因此拍完照后有结果返回到onActivityResult()方法中。
        //如果发现拍照成功,就可以调用BitmapFactory的decodeStream()方法。
        //将output_image.jpg这张照片解析成Bitmap对象,然后把它设置到ImageView中显示出来。
        switch (requestCode){
            case TAKE_PHOTO:
                if (requestCode == RESULT_OK){
                    //将拍摄的照片显示出来
                    try {
                        Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                        picture.setImageBitmap(bitmap);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case CHOOSE_PHOTO:
                if (requestCode == RESULT_OK){
                    //判断系统版本号
                    if (Build.VERSION.SDK_INT >=19){
                        //4.4及以上系统使用这个方法处理图片
                        handleImageOnKitKat(data);

                    }else{
                        //4.4以下系统使用这个方法处理图片
                        handleImageBeforeKitKat(data);
                    }
                }
            default:
                break;
        }
    }
    @TargetApi(19)
    private void handleImageOnKitKat(Intent data){
        String imagePath = null;
        Uri uri = data.getData();
        if (DocumentsContract.isDocumentUri(this,uri)){
                //如果是document类型的uri,则通过documentid处理
            String docId = DocumentsContract.getDocumentId(uri);
            if ("com.android.providers.media.doocuments".equals(uri.getAuthority())){
                String id = docId.split(":")[1];//解析出数字格式的id
                //这里我还查了查这行代码什么意思,java没学好···
                //例如arr[0].split(":") 这句会得到一个数组[小明,87]后面的[1]取数组下标为1的元素 也就是说取出来是87。
                String selection = MediaStore.Images.Media._ID+"="+id;
                /*
                * android的Uri由以下三部分组成: "content://"、数据的路径、标示ID(可选)
                举些例子,如:
              所有联系人的Uri: content://contacts/people
              某个联系人的Uri: content://contacts/people/5
              所有图片Uri: content://media/external
              某个图片的Uri:content://media/external/images/media/4
                *
                * */
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
            }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                /*
                *在这里再解释一下这句代码的含义,由于书上没有写,所以在这里强调一下。
                * ContentUris类用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
                withAppendedId(uri, id)用于为路径加上ID部分:
                Uri uri = Uri.parse("content://cn.xxt.provider.personprovider/person")
                Uri resultUri = ContentUris.withAppendedId(uri, 10);
                //生成后的Uri为:content://cn.xxt.provider.personprovider/person/10
                *
                *
                * */
                imagePath = getImagePath(contentUri,null);
            }
        }else if ("content".equalsIgnoreCase(uri.getScheme())){
            //如果是content类型的Uri,则使用普通方式处理
            imagePath = getImagePath(uri,null);
        }else if ("file".equalsIgnoreCase(uri.getScheme())){
            //如果是file类型的Uri,直接获取图片路径即可
            imagePath = uri.getPath();
        }
        displayImage(imagePath);//根据图片路径显示图片
    }
    private void handleImageBeforeKitKat(Intent data){
        Uri uri = data.getData();
        String imagePath = getImagePath(uri,null);
        displayImage(imagePath);
    }
    private String getImagePath(Uri uri,String selection){
        String path = null;
        //通过Uri和selection来获取真实的图片路径
        Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
        if (cursor!=null){
            if (cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        return path;
    }
    private void displayImage(String imagePath){
        if (imagePath!=null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            //Bitmap在Android中指的是一张图片,可以是png,也可以是jpg等其他图片格式。
            /*
            *Bitmap的加载离不开BitmapFactory类
            *BitmapFactory类提供了四类方法用来加载Bitmap
            * 这里只写第一种方法
            * 一:decodeFile 从文件系统加载
                a. 通过Intent打开本地图片或照片
                b. 在onActivityResult中获取图片uri
                c. 根据uri获取图片的路径
                d. 根据路径解析bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path)
            * */
            picture.setImageBitmap(bitmap);
            //显示图片
        }else {
            Toast.makeText(this, "failed to get image", Toast.LENGTH_SHORT).show();
        }
    }
}

Review Uri again here

Universal Resource Identifier (Universal Resource Identifier, "URI" for short).
Uri represents the data to be manipulated, and every resource available on Android-images, video clips, etc., can be represented by Uri. For the Android platform, URI is mainly divided into three parts:

HATE

scheme
authority
path

The authority is divided into host and port. The format is as follows:

scheme://host:port/path

Practical example:
We often need to parse Uri and get data from Uri.
The Android system provides two tool classes for operating Uri, namely UriMatcher and ContentUris.
Mastering their use will facilitate our Android development work.

Guess you like

Origin blog.csdn.net/i_nclude/article/details/77745505