本文概述
翻译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里的静态方法完成图片压缩
思路分析
- 从MainActivity 发送一个信息(去相册的信息或者去相机的信息)
- 启动新的应用:相机app 或者相册app
- 有两个分支:
- 用户click,选择一张图片->
MainActivity展示
- 用户取消,没有选择图片->
MainActivity不做任何改变
- 用户click,选择一张图片->
关键代码实现
- 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有很多细节值得复习:
- Activity中的点击事件 是在XML中以属性的方式配置的 比如 相机Button的 android:onClick=”camera”,之后在Activity中以(自己加一个camera函数)同名函数被系统回调的方式,避免setOnclickListener的繁琐,或者onClick中switch判断每一个View的id导致性能过低的情况。
- 引入定时器的技术,这里是我刻意加上去的,为的的是介绍Timer和TimerTask的概念,它们用途很广泛,比如闪屏页定时展示几秒后跳转到主页,或者定时完成一些任务,跟Handler的sendMessageDelay,postDelay类似,还可以实现AdapterViewFliper的定时更换页面类似效果,不过定时器的方式更加简洁。
- 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); }
显而易见:
- 用Intent封装数据信息
- startActivityForResult()来执行跳转,当然还有另一种启动方式,startActivity()
- onActivityResult中的处理,用RESULT_OK 来判断用户是进行了“在相机应用中或者图库应用中选中图片,系统默认返回跳转原MainActivity”操作或是进行了“用户点击Back按钮返回原MainActivity”操作
- 压缩图片的尺寸,调用了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 优化内存史诗巨著 和 图片压缩看我的博客就够了! 来复习为什么要压缩图片
参考感谢
自制demo
Interacting with Other Apps by chaofan