美文网首页
应用交互

应用交互

作者: mouekz | 来源:发表于2018-03-06 16:29 被阅读0次

    Intente启动

    Android中最重要的特征之一就是可以利用一个带有actionintent使当前app能够跳转到其他app。例如:如果我们的app有一个地址想要显示在地图上,我们并不需要在app里面创建一个activity用来显示地图,而是使用Intent来发出查看地址的请求。Android系统则会启动能够显示地图的程序来呈现该地址。

    正如在1.1章节:建立你的第一个App(Building Your First App)中所说的,我们必须使用intent来在同一个app的两个activity之间进行切换。通常是定义一个显式(explicit)的intent,它指定了需要启动组件的类名。然而,当想要唤起不同的app来执行某个动作(比如查看地图),则必须使用隐式(implicit)的intent。

    本课会介绍如何为特殊的动作创建一个implicit intent,并使用它来启动另一个app去执行intent中的action。

    建立隐式的Intent

    Implicit intents并不声明要启动组件的具体类名,而是声明一个需要执行的action。这个action指定了我们想做的事情,例如查看,编辑,发送或者是获取一些东西。Intents通常会在发送action的同时附带一些数据,例如你想要查看的地址或者是你想要发送的邮件信息。数据的具体类型取决于我们想要创建的Intent,比如Uri或其他规定的数据类型,或者甚至也可能根本不需要数据。

    如果数据是一个Uri,会有一个简单的[Intent()](http://developer.android.com/reference/android/content/Intent.html#Intent(java.lang.String, android.net.Uri)) constructor 用于定义action与data。

    例如,下面是一个带有指定电话号码的intent。

    Uri number = Uri.parse("tel:5551234");
    Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
    
    

    当app通过执行startActivity()启动这个intent时,Phone app会使用之前的电话号码来拨出这个电话。

    下面是一些其他intent的例子:

    • 查看地图:
    // 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);
    
    

    至于另外一些需要extra数据的implicit intent,我们可以使用 [putExtra()](http://developer.android.com/reference/android/content/Intent.html#putExtra(java.lang.String, java.lang.String)) 方法来添加那些数据。 默认的,系统会根据Uri数据类型来决定需要哪些合适的MIME type。如果我们没有在intent中包含一个Uri, 则通常需要使用 setType() 方法来指定intent附带的数据类型。设置MIME type 是为了指定应该接受这个intent的activity。例如:

    • 发送一个带附件的email:
    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: 这个intent for Calendar的例子只使用于>=API Level 14。

    Note: 请尽可能的将Intent定义的更加确切。例如,如果想要使用ACTION_VIEW 的intent来显示一张图片,则还应该指定 MIME type 为image/*.这样能够阻止其他能够 "查看" 其他数据类型的app(比如一个地图app) 被这个intent叫起。

    验证是否有App去接收这个Intent

    尽管Android系统会确保每一个确定的intent会被系统内置的app(such as the Phone, Email, or Calendar app)之一接收,但是我们还是应该在触发一个intent之前做验证是否有App接受这个intent的步骤。

    Caution: 如果触发了一个intent,而且没有任何一个app会去接收这个intent,则app会crash。

    为了验证是否有合适的activity会响应这个intent,需要执行[queryIntentActivities()](http://developer.android.com/reference/android/content/pm/PackageManager.html#queryIntentActivities(android.content.Intent, int)) 来获取到能够接收这个intent的所有activity的list。若返回的List非空,那么我们才可以安全的使用这个intent。例如:

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

    如果isIntentSafetrue, 那么至少有一个app可以响应这个intent。false则说明没有app可以handle这个intent。

    Note:我们必须在第一次使用之前做这个检查,若是不可行,则应该关闭这个功能。如果知道某个确切的app能够handle这个intent,我们也可以向用户提供下载该app的链接。(see how to link to your product on Google Play).

    使用Intent启动Activity

    当创建好了intent并且设置好了extra数据后,通过执行startActivity() 将intent发送到系统。若系统确定了多个activity可以handle这个intent,它会显示出一个dialog,让用户选择启动哪个app。如果系统发现只有一个app可以handle这个intent,则系统将直接启动该app。

    startActivity(intent);
    
    
    intents-choice.png

    下面是一个演示了如何创建一个intent来查看地图的完整例子,首先验证有app可以handle这个intent,然后启动它。

    // 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的选择界面

    请注意,当以startActivity()的形式传递一个intent,并且有多个app可以handle时,用户可以在弹出dialog的时候选择默认启动的app(通过勾选dialog下面的选择框,如上图所示)。该功能对于用户有特殊偏好的时候非常有用(例如用户总是喜欢启动某个app来查看网页,总是喜欢启动某个camera来拍照)。

    然而,如果用户希望每次都弹出选择界面,而且每次都不确定会选择哪个app启动,例如分享功能,用户选择分享到哪个app都是不确定的,这个时候,需要强制弹出选择的对话框。(这种情况下用户不能选择默认启动的app)。

    intent-chooser.png

    为了显示chooser, 需要使用[createChooser()](http://developer.android.com/reference/android/content/Intent.html#createChooser(android.content.Intent, java.lang.CharSequence))来创建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().getText(R.string.chooser_title);
    // Create and start the chooser
    Intent chooser = Intent.createChooser(intent, title);
    startActivity(chooser);
    
    

    这样就列出了可以响应createChooser()中Intent的app,并且指定了标题。

    Intent过滤

    如果我们的app的功能对别的app也有用,那么其应该做好响应的准备。例如,如果创建了一个social app,它可以分享messages 或者 photos 给好友,那么最好我们的app能够接收ACTION_SEND 的intent,这样当用户在其他app触发分享功能的时候,我们的app能够出现在待选对话框。

    通过在manifest文件中的<activity>标签下添加<intent-filter>的属性,使其他的app能够启动我们的activity。

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

    添加Intent Filter

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

    若activity中的intent filter满足以下intent对象的标准,系统就能够把特定的intent发送给activity:

    • Action:一个想要执行的动作的名称。通常是系统已经定义好的值,如ACTION_SENDACTION_VIEW。 在intent filter中通过<action>指定它的值,值的类型必须为字符串,而不是API中的常量(看下面的例子)

    • Data:Intent附带数据的描述。在intent filter中通过<data>指定它的值,可以使用一个或者多个属性,我们可以只定义MIME type或者是只指定URI prefix,也可以只定义一个URI scheme,或者是他们综合使用。

    Note: 如果不想handle Uri 类型的数据,那么应该指定 android:mimeType 属性。例如 text/plain or image/jpeg.

    • Category:提供一个附加的方法来标识这个activity能够handle的intent。通常与用户的手势或者是启动位置有关。系统有支持几种不同的categories,但是大多数都很少用到。而且,所有的implicit intents都默认是 CATEGORY_DEFAULT 类型的。在intent filter中用<category>指定它的值。

    在我们的intent filter中,可以在<intent-filter>元素中定义对应的XML元素来声明我们的activity使用何种标准。

    例如,这个有intent filter的activity,当数据类型为文本或图像时会处理ACTION_SEND的intent。

    <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可以handle 文本与图片,无论是ACTION_SEND还是ACTION_SENDTO 的intent。在这种情况下,就必须为两个action定义两个不同的intent filter。因为ACTION_SENDTO intent 必须使用 Uri 类型来指定接收者使用 send 或 sendto 的地址。例如:

    <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:为了接受implicit intents, 必须在我们的intent filter中包含 CATEGORY_DEFAULT 的category。startActivity()和startActivityForResult()方法将所有intent视为声明了CATEGORY_DEFAULT category。如果没有在的intent filter中声明CATEGORY_DEFAULT,activity将无法对implicit intent做出响应。

    更多sending 与 receiving ACTION_SEND intents执行social sharing行为的,请查看上一课:接收Activity返回的结果(Getting a Result from an Activity)

    在Activity中Handle发送过来的Intent

    为了决定采用哪个action,我们可以读取Intent的内容。

    可以执行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 ...
        }
    }
    
    

    返回Result

    如果想返回一个result给启动的那个activity,仅仅需要执行[setResult()](http://developer.android.com/reference/android/app/Activity.html#setResult(int, android.content.Intent)),通过指定一个result code与result 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 code。通常不是RESULT_OK就是RESULT_CANCELED。我们可以通过Intent 来添加需要返回的数据。

    Note:默认的result code是RESULT_CANCELED.因此,如果用户在没有完成操作之前点击了back key,那么之前的activity接受到的result code就是"canceled"。

    如果只是纯粹想要返回一个int来表示某些返回的result数据之一,则可以设置result code为任何大于0的数值。如果我们返回的result只是一个int,那么连intent都可以不需要返回了,可以调用setResult()然后只传递result code如下:

    setResult(RESULT_COLOR_RED);
    finish();
    
    

    Note:我们没有必要在意自己的activity是被用startActivity() 还是 startActivityForResult()方法所叫起的。系统会自动去判断该如何传递result。在不需要的result的case下,result会被自动忽略。

    Activity返回结果

    启动另外一个activity并不一定是单向的。我们也可以启动另外一个activity然后接受一个返回的result。为接受result,我们需要使用[startActivityForResult()](http://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int)) ,而不是startActivity()

    例如,我们的app可以启动一个camera程序并接受拍的照片作为result。或者可以启动联系人程序并获取其中联系的人的详情作为result。

    当然,被启动的activity需要指定返回的result。它需要把这个result作为另外一个intent对象返回,我们的activity需要在[onActivityResult()](http://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent))的回调方法里面去接收result。

    Note:在执行startActivityForResult()时,可以使用explicit 或者 implicit 的intent。当启动另外一个位于的程序中的activity时,我们应该使用explicit intent来确保可以接收到期待的结果。

    启动Activity

    对于startActivityForResult() 方法中的intent与之前介绍的并无太大差异,不过是需要在这个方法里面多添加一个int类型的参数。

    该integer参数称为"request code",用于标识请求。当我们接收到result Intent时,可从回调方法里面的参数去判断这个result是否是我们想要的。

    例如,下面是一个启动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);
    }
    
    

    接收Result

    当用户完成了启动之后activity操作之后,系统会调用我们activity中的onActivityResult() 回调方法。该方法有三个参数:

    • 通过startActivityForResult()传递的request code。
    • 第二个activity指定的result code。如果操作成功则是RESULT_OK ,如果用户没有操作成功,而是直接点击回退或者其他什么原因,那么则是RESULT_CANCELED
    • 包含了所返回result数据的intent。

    例如,下面显示了如何处理pick a contact的result:

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

    本例中被返回的Intent使用Uri的形式来表示返回的联系人。

    为正确处理这些result,我们必须了解那些result intent的格式。对于自己程序里面的返回result是比较简单的。Apps都会有一些自己的api来指定特定的数据。例如,People app (Contacts app on some older versions) 总是返回一个URI来指定选择的contact,Camera app 则是在data数据区返回一个 Bitmap (see the class about Capturing Photos).

    读取联系人数据

    上面的代码展示了如何获取联系人的返回结果,但没有说清楚如何从结果中读取数据,因为这需要更多关于content providers的知识。但如果想知道的话,下面是一段代码,展示如何从被选的联系人中读出电话号码。

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

    Note:在Android 2.3 (API level 9)之前对Contacts Provider的请求(比如上面的代码),需要声明READ_CONTACTS权限(更多详见Security and Permissions)。但如果是Android 2.3以上的系统就不需要这么做。但这种临时权限也仅限于特定的请求,所以仍无法获取除返回的Intent以外的联系人信息,除非声明了READ_CONTACTS权限。

    相关文章

      网友评论

          本文标题:应用交互

          本文链接:https://www.haomeiwen.com/subject/wvwhfftx.html