跟Google学写代码:Interacting with Other Apps【Capture Photo from phone】

本文概述



翻译Interacting with Other Apps相关课程,并通过复习该文档的知识,完成如下功能:

与其他应用交互

我们开发的Android 应用一般具有若干个Activity。每个Activity显示一个用户界面,用户可通过该界面执行特定任务(比如,查看地图或拍照)。要将用户从一个Activity转至另一Activity,必须使用 Intent 定义当前应用做某事的“意向”。 当使用诸如 startActivity() 的方法将 Intent 传递至系统时,系统会使用 Intent 识别和启动相应的应用组件。使用意向甚至可以让当前应用启动另一个应用中包含的Activity。

Intent 可以为 显式 以便启动特定组件(特定的 Activity 实例)或隐式 以便启动处理意向操作(比如“拍摄照片”)的任何组件。

本文将展示如何使用 Intent 执行与其他应用的一些基本交互操作,比如启动另一个应用、接收来自该应用的结果以及使我们的应用能够响应来自其他应用的意向。

向其他应用发送信息


必须使用意向(Intent)在自己应用中的Activity之间进行导航。通常使用明确意向执行此操作,该意向定义开发者希望启动的组件的确切类名称。

但是,当我们希望另一应用执行操作时,比如“查看地图”,就必须使用隐含意向。

构建隐含意向



隐含意向就是不指定名称,而指定动作行为,比如拍照,查看地图,发送邮件等

  • 例如,此处显示如何使用指定电话号码的 Uri 数据创建发起电话呼叫的意向:

    Uri number = Uri.parse("tel:5551234");
    Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
  • 查看地图:
    // Map point based on address
    Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
    // Or map point based on latitude/longitude
    // Uri location = Uri.parse("geo:37.422219,-122.08364?z=14"); // z param is zoom level
    Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
  • 查看网页
    Uri webpage = Uri.parse("http://www.android.com");
    Intent webIntent = new Intent(Intent.ACTION_VIEW, webpage);

其他类型的隐含意向需要提供不同数据类型(比如,字符串)的“额外”数据。 我们可以使用各种 putExtra() 方法添加一条或多条额外数据。

默认情况下,系统基于所包含的 Uri 数据确定意向需要的相应 MIME 类型。如果未在意向中包含 Uri,通常应使用 setType() 指定与意向关联的数据的类型。 设置 MIME 类型可进一步指定哪些类型的Activity应接收意向。

  • 比如发送邮件:

    Intent emailIntent = new Intent(Intent.ACTION_SEND);
    // The intent does not have a URI, so declare the "text/plain" MIME type
    emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
    emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"[email protected]"}); // recipients
    emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Email subject");
    emailIntent.putExtra(Intent.EXTRA_TEXT, "Email message text");
    emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("content://path/to/email/attachment"));
    // You can also attach multiple items by passing an ArrayList of Uris
  • 日历事件
    Intent calendarIntent = new Intent(Intent.ACTION_INSERT, Events.CONTENT_URI);
    Calendar beginTime = Calendar.getInstance().set(2012, 0, 19, 7, 30);
    Calendar endTime = Calendar.getInstance().set(2012, 0, 19, 10, 30);
    calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis());
    calendarIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis());
    calendarIntent.putExtra(Events.TITLE, "Ninja class");
    calendarIntent.putExtra(Events.EVENT_LOCATION, "Secret dojo");

做点事情,总希望得到一个反馈对吧?那么构建完毕意向,现在我们需要确定是否有应用接收;

确认是否存在接收意向的应用

注意:如果调用了意向,但设备上没有可用于处理意向的应用,应用将崩溃

要确认是否存在可响应意向的可用Activity,请调用 queryIntentActivities() 来获取能够处理Intent 的Activity列表。 如果返回的 List 不为空,可以安全地使用该意向。例如

PackageManager packageManager = getPackageManager();
List activities = packageManager.queryIntentActivities(intent,
        PackageManager.MATCH_DEFAULT_ONLY);
boolean isIntentSafe = activities.size() > 0;

如果 isIntentSafe 是 true,则至少有一个应用将响应该意向。 如果它是 false,则没有任何应用处理该意向。

启动目标Activity



一旦已创建 Intent 并设置附加信息,调用 startActivity() 将其发送给系统 。如果系统识别可处理意向的多个Activity,它会为用户显示对话框供其选择要使用的应用,如图 1 所示。 如果只有一个Activity处理意向,系统会立即开始这个Activity。

图1

此处显示完整的示例:如何创建查看地图的意向,确认是否存在处理意向的应用,然后启动它:

// Build the intent
Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);

// Verify it resolves
PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
boolean isIntentSafe = activities.size() > 0;

// Start an activity if it‘s safe
if (isIntentSafe) {
    startActivity(mapIntent);
}

显示应用选择器



注意,当通过将 Intent 传递至 startActivity() 而开始Activity时,有多个应用响应意向,用户可以选择默认使用哪个应用(通过选中对话框底部的复选框;见图 1。 当执行用户通常希望每次使用相同应用进行的操作时,比如当打开网页(用户可能只使用一个网页浏览器)或拍照(用户可能习惯使用一个照相机)时,这非常有用。

但是,如果要执行的操作可由多个应用处理并且用户可能习惯于每次选择不同的应用,—比如“共享”操作,用户有多个应用分享项目—,应明确显示选择器对话框如图 2 所示。 选择器对话框强制用户选择用于每次操作的应用(用户不能对此操作选择默认的应用)。

图2

要显示选择器,使用 createChooser() 创建Intent 并将其传递至 startActivity()。例如:

Intent intent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show chooser
Intent chooser = Intent.createChooser(intent, title);

// Verify the intent will resolve to at least one activity
if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

这将显示一个对话框,其中有响应传递给 createChooser() 方法的意向的应用列表,并且将提供的文本用作 对话框标题。

获得Activity的结果



要接收结果,请调用 startActivityForResult()(而不是 startActivity())。并在原Acitivity的 onActivityResult() 回调中接收它。

启动Activity

启动针对结果的Activity时,所使用的 Intent 对象并没有什么特别之处,但需要向 startActivityForResult() 方法传递额外的整数参数。

该整数参数是识别当前请求的“请求代码”。当、原Acitivity收到结果Intent 时,回调提供相同的请求代码,以便当前应用可以正确识别结果并确定如何处理它。

例如,此处显示如何开始允许用户选择联系人的Activity:

static final int PICK_CONTACT_REQUEST = 1;  // The request code
...
private void pickContact() {
    Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
    pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
    startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
}

接收结果

当用户完成后续Activity并且返回时,系统会调用原先Activity onActivityResult() 的方法。此方法包括三个参数:

  • 之前使用 startActivityForResult() 传递的请求代码。
  • 第二个是Activity指定的结果代码。如果操作成功,这是 RESULT_OK;如果用户退出或操作出于某种原因失败,则是 RESULT_CANCELED。
  • 传送结果数据的 Intent。

本例说明您可以如何处理“选择联系人”意向的结果。

代码注释很简洁,就不翻译了,具体关于 如何通过URL获取到联系人信息的,需要复习内容提供者的知识

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check which request it is that we‘re responding to
    if (requestCode == PICK_CONTACT_REQUEST) {
        // Make sure the request was successful
        if (resultCode == RESULT_OK) {
            // Get the URI that points to the selected contact
            Uri contactUri = data.getData();
            // We only need the NUMBER column, because there will be only one row in the result
            String[] projection = {Phone.NUMBER};

            // Perform the query on the contact to get the NUMBER column
            // We don‘t need a selection or sort order (there‘s only one result for the given URI)
            // CAUTION: The query() method should be called from a separate thread to avoid blocking
            // your app‘s UI thread. (For simplicity of the sample, this code doesn‘t do that.)
            // Consider using CursorLoader to perform the query.
            Cursor cursor = getContentResolver()
                    .query(contactUri, projection, null, null, null);
            cursor.moveToFirst();

            // Retrieve the phone number from the NUMBER column
            int column = cursor.getColumnIndex(Phone.NUMBER);
            String number = cursor.getString(column);

            // Do something with the phone number...
        }
    }
}

允许其他应用启动我们的Activity



前两节重点讲述一方面:从当前应用开始另一个应用的Activity。但如果当前应用可以执行对另一个应用可能有用的操作,但钱应用应准备好响应来自其他应用的操作请求。 例如,如果我们构建一款可与用户的好友分享消息或照片的社交应用,那么我们最关注的是支持 ACTION_SEND 意向以便用户可以从另一应用发起 “共享”操作并且启动您的应用执行该操作。

要允许其他应用启动我们的Activity,我们需要 在相应元素的宣示说明文件中添加一个 元素。

当应用安装在设备上时,系统会识别您的意向过滤器并添加信息至所有已安装应用支持的意向内部目录。当应用通过隐含意向调用 startActivity() 或 startActivityForResult() 时,系统会找到可以响应该意向的Activity

添加意向过滤器



为了正确定义Activity可处理的意向,添加的每个意向过滤器在操作类型和Activity接受的数据方面应尽可能具体。

如果Activity具有满足以下 Intent 对象条件的意向过滤器,系统可能向Activity发送给定的 Intent:

  • 操作

    对要执行的操作命名的字符串。通常是平台定义的值之一,比如 ACTION_SEND 或 ACTION_VIEW。

    使用 元素在意向过滤器(Intent filter)中指定此值。在此元素中指定的值必须是操作的完整字符串名称,而不是 API 常数(可以参阅以下示例)。

  • 数据

    与意向关联的数据描述。

    用 元素在您的意向过滤器中指定此内容。使用此元素中的一个或多个属性,可以只指定 MIME 类型、URI 前缀、URI 架构或这些的组合以及其他指示所接受数据类型的项。

    注意:如果无需声明关于数据的具体信息 Uri(比如,当前Activity处理其他类型的“额外”数据而不是 URI 的时),我们应只指定 android:mimeType 属性声明您的Activity处理的数据类型,比如 text/plain 或 image/jpeg。

  • 类别

    提供另外一种表征处理意向的Activity的方法,通常与用户手势或Activity开始的位置有关。 系统支持多种不同的类别,但大多数都很少使用。 ?但是,所有隐含意向默认使用 CATEGORY_DEFAULT 进行定义。

    用 元素在意向过滤器中指定此内容。

在意向过滤器中,可以通过声明嵌套在 元素中的具有相应 XML 元素的各项,来声明当前Activity接受的条件。

例如,此处有一个在数据类型为文本或图像时处理 ACTION_SEND 意向的意向过滤器:

<activity android:name="ShareActivity">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
        <data android:mimeType="image/*"/>
    </intent-filter>
</activity>

别的应用来启动了,接下来我们当然要响应操作咯!

处理Activity中的意向



当Activity开始时,调用 getIntent() 检索开始Activity的 Intent。可以在Activity生命周期的任何时间执行此操作,通常应在早期回调时(比如, onCreate() 或 onStart())执行。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    // Get the intent that started this activity
    Intent intent = getIntent();
    Uri data = intent.getData();

    // Figure out what to do based on the intent type
    if (intent.getType().indexOf("image/") != -1) {
        // Handle intents with image data ...
    } else if (intent.getType().equals("text/plain")) {
        // Handle intents with text ...
    }
}

从被启动的Acitivity中获取返回结果



想获取返回结果,只需调用 setResult() 指定结果代码和结果 Intent。当操作完成且用户应返回原始Activity时,调用 finish() 关闭(和销毁)的Activity。 例如:

// Create intent to deliver some kind of result data
Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
setResult(Activity.RESULT_OK, result);
finish();

我们必须始终为结果指定结果代码。通常,它为 RESULT_OK 或 RESULT_CANCELED。在这之后可以根据需要为 Intent 提供额外的数据。

结果默认设置为 RESULT_CANCELED。因此,如果用户在完成操作动作或设置结果之前按了返回按钮,原始Activity会收到“已取消”的结果。

如果我们的需求是返回指示若干结果选项之一的整数,那么可以将结果代码设置为大于 0 的任何值。 如果我们使用结果代码传递整数,并且无需包含 Intent,可以调用 setResult() 并且仅传递结果代码。 例如:


setResult(1);
finish();

在这种情况下,只有几个可能的结果,因此结果代码是一个本地定义的整数(大于 0)。 当向自己应用中的Activity返回结果时,这将非常有效,因为接收结果的Activity可引用公共常数来确定结果代码的值。

无需检查Activity是使用 startActivity() 还是 startActivityForResult() 开始的。如果开始您的Activity的意向可能需要结果,只需调用 setResult()。 如果原始Activity已调用 startActivityForResult(),则系统将向其传递您提供给 setResult() 的结果;否则,会忽略结果。

项目实例



从主Activity跳转到相机或者相册,选中一张图片,或者拍摄一张图片返回,放在主Activity中展示

UML图:

MainActivity中有两个点击事件:photo()和camera(),分别代表:从相册获取图片,从相机获取图片

考虑有的图片太占内存,所以引入compressed()是来优化内存,通过调用PhotoUtils里的静态方法完成图片压缩

思路分析


  1. 从MainActivity 发送一个信息(去相册的信息或者去相机的信息)
  2. 启动新的应用:相机app 或者相册app
  3. 有两个分支:
    • 用户click,选择一张图片->

      MainActivity展示

    • 用户取消,没有选择图片->

      MainActivity不做任何改变

关键代码实现


  • Intent 作为信息的载体
  • startActivity() startActivityForResult()来完成启动操作
  • 在onActivityResult()完成 后续操作

代码分析



先介绍MainActivity.java的布局文件

很多朋友可能会喊f**k,第一个布局元素ConstraintLayout就不认识,莫急莫慌,详情请看我的另一篇博客 2016谷歌新技术

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.learn.chaofun.interactingwithotherapp.MainActivity">

    <Button
        android:id="@+id/photo"
        android:layout_width="150dp"
        android:layout_height="48dp"
        android:text="相册"
        android:onClick="photo"
        app:layout_constraintTop_toTopOf="@+id/activity_main"
        android:layout_marginTop="72dp"
        app:layout_constraintRight_toRightOf="@+id/activity_main"
        android:layout_marginRight="24dp"
        android:layout_marginEnd="24dp" />

    <Button
        android:id="@+id/camer"
        android:layout_width="150dp"
        android:layout_height="48dp"
        android:text="camera"
        android:onClick="camera"
        app:layout_constraintLeft_toLeftOf="@+id/activity_main"
        android:layout_marginLeft="40dp"
        android:layout_marginStart="40dp"
        app:layout_constraintTop_toTopOf="@+id/activity_main"
        android:layout_marginTop="72dp" />

    <ImageView
        android:id="@+id/main_show_pic"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#ff313131"
        app:layout_constraintLeft_toLeftOf="@+id/activity_main"
        android:layout_marginLeft="40dp"
        android:layout_marginStart="40dp"
        app:layout_constraintTop_toTopOf="@+id/activity_main"
        android:layout_marginTop="160dp"
        app:layout_constraintRight_toRightOf="@+id/activity_main"
        android:layout_marginRight="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintHorizontal_bias="0.38" />

</android.support.constraint.ConstraintLayout>

重点在于默认ImageView:大小为矩形,默认背景颜色为灰色,目的是为了看出图片裁剪的效果,达到内存优化的目的

下面我们来看MainActivity中是如何书写的吧:

public class MainActivity extends AppCompatActivity {
    /* 用来标识请求照相功能的activity */
    private static final int CAMERA_WITH_DATA = 3023;

    /* 用来标识请求相册的activity */
    private static final int PHOTO_PICKED_WITH_DATA = 3021;

    /* 照相机拍照得到的图片 */
    private File mCurrentPhotoFile;
    private String photoPath = null, tempPhotoPath, camera_path;

    //首先使用butterkniff  拿到view对象
    @InjectView(R.id.photo)
    Button photo;
    @InjectView(R.id.camer)
    Button camer;
    @InjectView(R.id.main_show_pic)
    ImageView mImageView;
    @InjectView(R.id.activity_main)
    ConstraintLayout activityMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.inject(this);

    }

    //定时器来完成任务
    Timer timer = new Timer();
    TimerTask task = new TimerTask() {
        public void run() {
            Message message = new Message();
            message.what = 1;
            myHandler.sendMessage(message);
        }
    };

    //重点演示Activity之间跳转,接收返回的数据
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.i("result", "result");

        //判断用户是否操作了,没有选择的话,图片为空,为了防止空指针异常,这里判断结果码来避免异常
        if (resultCode == RESULT_OK) {

            switch (requestCode) {
                case CAMERA_WITH_DATA:

                    photoPath = tempPhotoPath;
                    if (mImageView.getWidth() == 0) {
                        timer.schedule(task, 10, 1000);
                    } else {
                        compressed();
                    }

                    break;

                case PHOTO_PICKED_WITH_DATA:

                    Uri originalUri = data.getData();

                    String[] filePathColumn = {MediaStore.MediaColumns.DATA};
                    Cursor cursor = MainActivity.this.getContentResolver().query(
                            originalUri, filePathColumn, null, null, null);
                    cursor.moveToFirst();
                    int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                    photoPath = cursor.getString(columnIndex);
                    // 延迟每次延迟10 毫秒 隔1秒执行一次
                    if (mImageView.getWidth() == 0) {
                        timer.schedule(task, 10, 1000);
                    } else {
                        compressed();
                    }

                    break;

                default:
                    break;
            }
        }
    }

    /* 从相机中获取照片 */
    public void camera(View view) throws IOException {

        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");

        tempPhotoPath = PhotoUtils.DCIMCamera_PATH + getNewFileName()
                + ".jpg";
        mCurrentPhotoFile = new File(tempPhotoPath);
        if (!mCurrentPhotoFile.exists()) {
            try {
                mCurrentPhotoFile.createNewFile();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        intent.putExtra(MediaStore.EXTRA_OUTPUT,
                Uri.fromFile(mCurrentPhotoFile));
        startActivityForResult(intent, CAMERA_WITH_DATA);
    }

    /* 从相册中获取照片 */
    public void photo(View view) {
        Intent openphotoIntent = new Intent(Intent.ACTION_PICK);
        openphotoIntent.setType("image/*");
        startActivityForResult(openphotoIntent, PHOTO_PICKED_WITH_DATA);
    }

    public static String getNewFileName() {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
        Date curDate = new Date(System.currentTimeMillis());
        return formatter.format(curDate);
    }

    //重点是调用了PhotoUtils的接口
    private void compressed() {
        Bitmap resizeBmp = PhotoUtils.decodeBitmapFromPath(photoPath, 540, 540);
        mImageView.setImageBitmap(resizeBmp);
        camera_path = PhotoUtils.SaveBitmap(resizeBmp, "saveTemp");
    }

    //handler 负责刷新UI
    final Handler myHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                if (mImageView.getWidth() != 0) {
                    // 取消定时器
                    timer.cancel();
                    compressed();
                }
            }
        }
    };

}

MainActivity.java有很多细节值得复习

  1. Activity中的点击事件 是在XML中以属性的方式配置的 比如 相机Button的 android:onClick=”camera”,之后在Activity中以(自己加一个camera函数)同名函数被系统回调的方式,避免setOnclickListener的繁琐,或者onClick中switch判断每一个View的id导致性能过低的情况。
  2. 引入定时器的技术,这里是我刻意加上去的,为的的是介绍Timer和TimerTask的概念,它们用途很广泛,比如闪屏页定时展示几秒后跳转到主页,或者定时完成一些任务,跟Handler的sendMessageDelay,postDelay类似,还可以实现AdapterViewFliper的定时更换页面类似效果,不过定时器的方式更加简洁。
  3. Sending the User to Another App的做法:

    比如

        /* 从相册中获取照片 */
        public void photo(View view) {
            Intent openphotoIntent = new Intent(Intent.ACTION_PICK);
            openphotoIntent.setType("image/*");
            startActivityForResult(openphotoIntent, PHOTO_PICKED_WITH_DATA);
        }

    显而易见:

    1. 用Intent封装数据信息
    2. startActivityForResult()来执行跳转,当然还有另一种启动方式,startActivity()
  4. onActivityResult中的处理,用RESULT_OK 来判断用户是进行了“在相机应用中或者图库应用中选中图片,系统默认返回跳转原MainActivity”操作或是进行了“用户点击Back按钮返回原MainActivity”操作
  5. 压缩图片的尺寸,调用了PhotoUtils的接口

下面是工具类PhotoUtils:

public class PhotoUtils {

    public static String SDCARD_PAHT = Environment
            .getExternalStorageDirectory().getPath();

    public static String DCIMCamera_PATH = Environment
            .getExternalStorageDirectory() + "/DCIM/Camera/";

    // 将生成的图片保存到内存中
    public static String SaveBitmap(Bitmap bitmap, String name) {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) {
            File dir = new File(SDCARD_PAHT);
            if (!dir.exists())
                dir.mkdir();
            File file = new File(SDCARD_PAHT + "/" + name + ".jpg");
            FileOutputStream out;
            try {
                out = new FileOutputStream(file);
                if (bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)) {
                    out.flush();
                    out.close();
                }
                return file.getAbsolutePath();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // 获得内存中图片的宽高
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // 计算出一个数值,必须符合为2的幂(1,2,4,8,tec),赋值给inSampleSize
            // 图片宽高应大于期望的宽高的时候,才进行计算
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
    public static Bitmap decodeBitmapFromPath(String path ,
                                                         int reqWidth, int reqHeight) {

        // 第一次解析 inJustDecodeBounds=true 只是用来获取bitmap在内存中的尺寸和类型,系统并不会为其分配内存,
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        // 计算出一个数值
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // 根据inSampleSize 数值来解析bitmap
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(path, options);
    }
}

关于图片压缩,可以通过我之前两篇博文Google 优化内存史诗巨著图片压缩看我的博客就够了! 来复习为什么要压缩图片

参考感谢

  1. Google 关于 Intent 的官方文档
  2. Google 关于 内容提供者的课程
  3. Google 关于 捕获照片的课程

自制demo

Interacting with Other Apps by chaofan

时间: 2024-10-05 23:49:53

跟Google学写代码:Interacting with Other Apps【Capture Photo from phone】的相关文章

小说脑洞:《你跟我在一起,就为了让我教你写代码?》

简介:一个其貌不扬的女程序员(很厉害),被一个帅哥疯狂追求,原因竟然是为了学写代码? 风格:教学类. 篇幅:中篇. 开篇:一个帅哥偶遇一个女程序员(很厉害!),为了学习技术,出卖色相,疯狂追她! 中篇:女程序员教学的搞笑日常,帅哥渐渐喜欢上她. 后篇:帅哥成为一个略逊女主的程序员. 看点:学习写代码. 难点:作者必须是程序员. 小说脑洞系列 记录一些我没时间写的小说的灵感. 本文为原创文章,转载请保留原出处,方便溯源,如有错误地方,谢谢指正. 本文地址 :http://www.cnblogs.c

跟Google 学代码 :Building Apps with Content Sharing(跟女神去表白)

本篇博客都讲了些什么? 1. Sharing Simple Data 共享简单的数据,如文本,图片,URI 2. Sharing Files 共享文件 3. Sharing Files with NFC无线传输 在写博客之前,假设 "我"是服务端App,我的"女神"是客户端App 情书是"我"想传递的数据 那么我该如何做呢? 向客户端App发送数据(暗送秋波篇) 对女神爱在心中口难开怎么办?如何示爱这是穷学生最纠结的心病 在Android 开发中

跟Google 学代码:Building Apps with Graphics &amp; Animation(一)

引言 可以右键保存我做的思维导图: Google这一章的课程共分为四大部分: 我做的思维导图: 高效展示篇 OpenGl使用篇 View动画 使用动画集 考虑精力和博客篇幅,本篇仅仅涉及Building Apps with Graphics & Animation 第一节 Displaying Bitmaps Efficientlty Displaying Bitmaps Efficientlty (高效展示图片) 加载大图 图片有各种各样的形状和尺寸,很多情况下,一款热门的App需要展示很多大

关爱码农成长:关于写代码二三事

工作这么多年以来,一直从事软件相关领域,即使担任主管职务,也一直对技术充满热情.写代码写了这么多年,多少有些体会.我把自己对写代码这份工作的心得写下来,希望能给从事相关领域或有志于写代码的人参考. 一.你适合当程序员吗? 程序员,也叫软件工程师.程序设计师,我觉得「程序员」三个字简洁有力,是一种身份的象征. 如果你正从事这份工作,恭喜你!这是个热门行业,在可预见的将来,也不会消失.不过也别高兴太早,这一行的技术汰旧换新非常快,必须不断努力学习才行. 一点天赋 打开一个空白文档,必须创造出代码.与

【腾讯Bugly干货分享】深入理解 ButterKnife,让你的程序学会写代码

本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578753c0c9da73584b025875 0.引子 话说我们做程序员的,都应该多少是个懒人,我们总是想办法驱使我们的电脑帮我们干活,所以我们学会了各式各样的语言来告诉电脑该做什么--尽管,他们有时候也会误会我们的意思. 突然有一天,我觉得有些代码其实,可以按照某种规则生成,但你又不能不写--不是所有的重复代码都可以通过重构并采用高端技术比如泛型来消除的--比如我最痛恨的代

转:我,一个写代码的

转自: http://blog.csdn.net/cenwenchu79/article/details/3978554 我,一个写代码的 写blog已经快两年了,起初仅仅是为了自己“备个案”,结果慢慢演变成为了“分享成瘾”.前几天一个朋友给我的blog留言,谈到希望在新年里能够看到的不仅仅是我对技术的分享,更希望能够看到对于技术学习.职业发展的规划.因此想到了写一点什么分享一下自己这些年的一点点“收获”,周星驰的喜剧之王里面说到他是一个演员(虽然被叫做跑龙套的),我想我,就一个写代码的. 爱这

想靠写代码吃饭 这些你一定要会

想靠写代码吃饭 这些你一定要会 美国知名求职网站 Indeed 中对于 231 份并不要求计算机系学位的工作中,整理出了你必须会的一些科技技术,让你即使不是计算机系出身,但试着学会这些技术,就可以助你成为一名初级软件工程师. 想靠写代码吃饭,这些你一定要会 Javascript 第一名的是 Javascript ,有 42.4 % 的职缺中都要求必须会这项技能.Javascript 为什么这么厉害?最主要在于网络软件开发的盛行,Javascript 是很多现有的开发框架的基础除了用在前端的开发(

微信商城开发系列第四篇 不写代码玩转微信公众号

本系列文章属作者原创文章,请尊重作者的劳动成果,转载请注明出处:walkingmanc的专栏 , 谢谢! 同时欢迎大家加入微信商城开发QQ群:364072602,共同探讨进步.  为什么叫不写代码玩转微信公众号呢? 我们大家都知道,微信公众号有两种模式,一种是编辑模式,一种是开发模式.所谓的不写代码玩转微信公众号,其实就是在编辑模式下如何使用微信公众号的意思,呵呵,是不是有种恍然大悟的感觉. 其实,如果你关注的微信公众号比较多的话,你会发现有很多有名的公众号,它们没有菜单,每天都会发布4到5篇文

Python:10分钟搞定不写代码的爬虫

代码自己敲 使用 Chrome 浏览器插件 Web Scraper 可以轻松实现网页数据的爬取,不写代码,鼠标操作,点哪爬哪,还不用考虑爬虫中的登陆.验证码.异步加载等复杂问题. Web Scraper插件 Web Scraper 官网中的简介: Web Scraper Extension (Free!)Using our extension you can create a plan (sitemap) how a web site should be traversed and what s