(Android数据传递)应用之间的交互

一个Android App 通常会有多个Activity。每个Activity的界面都扮演者用户入口的角色,允许用户执行一些特定的任务(例如:查看地图或者拍照等)。为了让用户能够从一个Activity跳转到另一个Activity,必须使用Intent来定义自己的意图。

使用Intent实现与其他App执行的交互,比如:启动另一个App,从其他App接收数据,以及使我们自己的App响应其他App发出的Intent。

主题一:将用户传送到另外一个App

比如:在一个应用中,用户想要接收“本地”应用的数据,并显示出指定数据的地理位置信息;这个时候并不适合在本地的Activity中创建地图数据,而是使用Intent跳转到其他的应用,比如:Map 应用。

Intent有两种:显式的和隐式的

相对于显式Intent,隐式Intent能够携带有指定的意图信息,并跳转到其他App中。how to create an implicit intent for a particular action, and how to use it to start an activity that performs the action in another app.

如何创建隐式Intent?

显然,如果想要使用到显式Intent,必须要知道对应的类名、Action名等信息。Implicit intents do not declare the class name of the component to start, but instead declare an action to perform.

包含有URI的Intent实例:If your data is a Uri, there's a simple Intent() constructor you can use define the action and data.

举一个栗子:

打电话:

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);

发送邮件:

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[] {"jon@example.com"}); // 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");

Note: It's important that you define your Intent to be as specific as possible.

验证是否有App接收这个Intent

Although the Android platform guarantees that certain intents will resolve to one of the built-in apps (such as the Phone, Email, or Calendar app), you should always include a verification step before invoking an intent. 在发送一个Intent之前,有必要验证是否有App能够接收该Intent。

必须注意的是:Caution: If you invoke an intent and there is no app available on the device that can handle the intent, your app will crash.

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

如果返回的activities集合长度不为0,即不为空时,表示存在Activity能够响应该Intent。

如何启动另一个Activity?

startActivity(intent);

从一个Activity跳转到另一个Activity的方式,系统会分析能够响应该Intent的App。

并以Dialog的形式显示出来。

下述示例程序能够启动地图浏览功能:

// 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);
}

假若有多个App能够响应指定的Intent,如何选择?若每次的选择都不确定,这种情况如何处理?

显示选择界面,供用户决策

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);
}

主题二:获取Activity的返回数据

Starting another activity doesn't have to be one-way. You can also start another activity and receive a result back. To receive a result, call startActivityForResult() (instead of startActivity()).

具体实现:When it does, it sends the result as another Intent object. Your activity receives it in the onActivityResult() callback. 返回Result时,仍然使用Intent。

Note: You can use explicit or implicit intents when you call startActivityForResult(). When starting one of your own activities to receive a result, you should use an explicit intent to ensure that you receive the expected result. 如果启动自己App的Activity并期望返回结果时,建议使用显式Intent,以确保接收期望的结果。

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);
}

The request code 意味:请求码,用于标识当次请求。When you receive the result Intent, the callback provides the same request code so that your app can properly identify the result and determine how to handle it. 结果返回时,Intent会附带有相同的请求码,以确保请求的正确性。

其中在另一个Activity中会对返回的Intent附加数据:result code

A result code specified by the second activity. This is either RESULT_OK if the operation was successful or RESULT_CANCELED if the user backed out or the operation failed for some reason.

result code 用于标识对Intent处理的结果:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Check which request we're responding to
    if (requestCode == PICK_CONTACT_REQUEST) {
        // Make sure the request was successful
        if (resultCode == RESULT_OK) {
            // The user picked a contact.
            // The Intent's data Uri identifies which contact was selected.
            // Do something with the contact here (bigger example below)
        }
    }
}

举个栗子:如何读取联系人信息

@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...
        }
    }
}

主题三:允许其他App启动自己的Activity

即:让自己的App响应其他App的Intent。

To allow other apps to start your activity, you need to add an <intent-filter> element in your manifest file for the corresponding <activity> element.

当app被安装到设备上时,系统可以识别intent filter并把这些信息记录下来。当其他app使用implicit intent执行 startActivity() 或者 startActivityForResult()时,系统会自动查找出那些可以响应该intent的Activity。

如何增加<intent-filter>?

为了尽可能确切的定义activity能够handle的intent,每一个intent filter都应该尽可能详尽的定义好action与data。

如果一个Activity的<intent-filter>具备以下特征,那么就是比较完善的:

Action:A string naming the action to perform. Usually one of the platform-defined values such as ACTION_SEND or ACTION_VIEW. Specify this in your intent filter with the <action> element. The value you specify in this element must be the full string name for the action, instead of the API constant (see the examples below).

Data:A description of the data associated with the intent. Specify this in your intent filter with the <data> element. Using one or more attributes in this element, you can specify just the MIME type, just a URI prefix, just a URI scheme, or a combination of these and others that indicate the data type accepted.

Category:Provides an additional way to characterize the activity handling the intent, usually related to the user gesture or location from which it's started. There are several different categories supported by the system, but most are rarely used. However, all implicit intents are defined with CATEGORY_DEFAULT by default. Specify this in your intent filter with the <category> element.

举个栗子:

<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>

每一个发送出来的intent只会包含一个action与data类型,但handle这个intent的activity的 <intent-filter>可以声明多个<action>, <category>与<data> 。

如果任何的两对action与data是互相矛盾的,就应该创建不同的intent filter来指定特定的action与type。

<activity android:name="ShareActivity">
    <!-- filter for sending text; accepts SENDTO action with sms URI schemes -->
    <intent-filter>
        <action android:name="android.intent.action.SENDTO"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="sms" />
        <data android:scheme="smsto" />
    </intent-filter>
    <!-- filter for sending text or images; accepts SEND action and text or image data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

需要注意是:Note: In order to receive implicit intents, you must include the CATEGORY_DEFAULT category in the intent filter. The methods startActivity() and startActivityForResult() treat all intents as if they declared the CATEGORY_DEFAULT category. If you do not declare it in your intent filter, no implicit intents will resolve to your activity. 为了接受implicit intents, 必须在我们的intent filter中包含 CATEGORY_DEFAULT 的category。startActivity()和startActivityForResult()方法将所有intent视为声明了CATEGORY_DEFAULT category。如果没有在的intent filter中声明CATEGORY_DEFAULT,activity将无法对implicit intent做出响应。

响应Intent,那如何获取该Intent呢?

最好是在onCreate()和onStart()中获取该Intent。

@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 ...
    }
}

如何返回结果? 

// 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();

在返回结果的Activity端,需要设置result code。Generally, it's either RESULT_OK or RESULT_CANCELED.

Note: The result is set to RESULT_CANCELED by default. So, if the user presses the Back button before completing the action and before you set the result, the original activity receives the "canceled" result.

posted @ 2017-02-05 23:45  jamesK4W  阅读(3396)  评论(0编辑  收藏  举报