《Android 应用案例开发大全(第二版)》——6.4节跨应用程序访问窗口

简介:

本节书摘来自异步社区《Android 应用案例开发大全(第二版)》一书中的第6章,第6.4节跨应用程序访问窗口,作者李宁,更多章节内容可以访问云栖社区“异步社区”公众号查看

6.4 跨应用程序访问窗口
Android开发权威指南(第二版)
在前面曾介绍过可以通过显式和隐式的方式访问窗口,显式方式只能访问应用程序内部的窗口,而隐式方式无论当前应用程序,还是其他应用程序中的窗口都可以访问。不过阅读了本节的内容后读者就会改变这种观念。因为Android足够强大,已经完全打通不同应用程序之间的界限了,也就是说不同应用程序之间的交互几乎和应用程序内部的交互完全一样。因此在一个应用程序中同样可以通过直接指定另外一个应用程序中窗口类的方式访问该窗口,也就是跨应用程序显式调用。所以从现在开始不要认为只有隐式方式才可以调用其他应用程序的窗口,显式方式一样可以。

要了解显式调用窗口精髓需要知道如下几点。

每一个Android应用的唯一标识就Package Name(包名),也就是AndroidManifest.xml文件中标签的package属性值。只要Package Name相同,就可以认为是同一个Android应用。
如果用Intent对象指定一个窗口类,除了需要指定窗口类的class外,还需要指定窗口类所在的应用程序的Context对象(或应用程序的Package Name),所以通过Package Name和Activity Class Name(窗口类名)可以定位当前Android系统中的任意窗口。
了解了这两点后,就可以很容易想到如何通过直接指定窗口类的方式调用另一个应用程序的窗口。在6.2.2小节介绍了通过Intent.setClass、Intent.setClassName和Intent.setComponent方法可以指定Package Name和Activity Class Name。只要Package Name(或与其对应的Context对象)指向了另一个应用程序,那么系统就会认为窗口类是这个应用程序的(当然窗口类必须在该应用程序中存在,否则访问该窗口会抛出异常)。

定位某个应用程序可以有如下3种方式。

直接指定Package Name。
另一个Android应用的Context对象。
ComponentName对象。
直接指定Package Name比较简单,Package Name就是一个字符串,只要知道目标应用程序的Package Name1,直接指定即可。而Context对象就需要使用Context.createPackageContext方法创建了,代码如下:

Context context = createPackageContext(  
           "mobile.android.web.browser", Context.CONTEXT_INCLUDE_CODE
                 | Context.CONTEXT_IGNORE_SECURITY);

创建Context对象时也需要指定Package Name,但最好还指定Context.CONTEXT_INCLUDE _CODE和Context.CONTEXT_IGNORE_SECURITY,其中Context.CONTEXT_INCLUDE_CODE允许代码被装载,即使这些代码可能不安全,而Context.CONTEXT_IGNORE_SECURITY则忽略任何安全限制。

至于ComponentName对象,实际上就是封装了Package Name(或Context对象)和Class Name(或窗口类的class)的对象,只要得到了Context和窗口类的class,就可以直接创建ComponentName对象,并通过ComponentName类的构造方法传入相应的数据即可。

本例演示了跨应用程序访问窗口的各种方法,程序的主界面如图6-4所示。读者可以通过不同的按钮测试相应的功能。


ce436f2fa73d09f56088fc049926a7b39c827bd7

为了测试本节的例子,需要编写一个辅助程序WebBrowser,该程序的功能是接收一个Uri,并在WebView控件2中显示Uri指向的页面。

WebBrowser的窗口类WebBrowserActivity(该程序中只有一个窗口类)的实现代码很简单,只是在onCreate中通过getData方法获取传入的Uri对象,然后用WebView控件显示Uri指向的页面,WebBrowserActivity类的实现代码如下:

public class WebBrowserActivity extends Activity
{  
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_web_browser);
    WebView webview = (WebView) findViewById(R.id.webview);
    // 获取Uri对象
        Uri uri = getIntent().getData();
    if (uri != null)
    {
      // 在WebView控件中显示Uri指向的页面
            webview.loadUrl(uri.toString());
            // 在窗口标题栏中显示Uri
            setTitle(uri.toString());
    }
  }
}

在声明WebBrowserActivity类时就有一定的说道了。既然是跨应用程序调用,通常认为指定一个Action即可。但为了说明显式和隐式跨应用程序调用的差别,WebBrowserActivity类需要指定与系统自带的浏览器相同的Action。这样在使用该Action显示窗口时就会显示一个如图6-5所示的选择列表,该列表中至少会显示两个候选的程序3。其中右侧的WebBrowser就是WebBrowserActivity窗口,左侧的Browser是系统自带的浏览器应用中的窗口。

要想让某个窗口可以接收Uri,除了指定相应的Action外,还需要指定Data(用标签设置)。对于浏览Web页面,Data只需要设置scheme即可,也就是Web协议,如http、https等。


066cbc41c446c357fcbbcb7678bd491aae578111

WebBrowserActivity类的声明代码如下:

第X问1 源代码文件:src/ch06/WebBrowser/AndroidManifest.xml

<activity
  android:name="mobile.android.web.browser.WebBro- wserActivity"
  android:label="@string/app_name" >
  <intent-filter>
     <action android:name="android.intent.action.MAIN" />
     <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
  <intent-filter>
  <!-- android.intent.action.VIEW是Web浏览器的标准Action -->
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <!-- 指定该窗口可以打开以“http://”开头的Uri -->
  <data android:scheme="http" />
  <!-- 指定该窗口可以打开以“http://”开头的Uri -->
  <data android:scheme="https" />
 </intent-filter>
</activity>

从6.2节的内容可知,窗口可以通过Action、Category和Data三种机制的组合来定位,而WebBrowserActivity在声明时这3种过滤机制都使用了,所以在通过Action显示WebBrowserActivity窗口时需要同时指定Action、Category和Data。不过由于Category是android.intent.category.DEFAULT,所以并不需要指定Category。

下面看一下本节的核心程序InvokeOtherActivity(该程序的窗口类也是InvokeOtherActivity)是如何使用各种方式调用WebBrowserActivity的。前面如图6-4所示的界面就是InvokeOtherActivity程序的主界面。除了显示WebBrowserActivity窗口外,最后一个按钮还可以使用显式方式调用系统的计算器程序。

InvokeOtherActivity窗口类的代码如下:

第X问1 源代码文件:src/ch06/InvokeOtherActivity/src/mobile/android/invoke/other/activity/InvokeOtherActivity.java

public class InvokeOtherActivity extends Activity
{  
    private Context mContext;
    private Class mClass;
  // 由于有多处需要Context和Class对象,所以在onCreate方法中提前创建了这两个对象
  @Override
  protected void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_invoke_other);
    try
    {
      // 创建指向WebBrowser程序的Context对象,其中mobile.android.web.browser就是
      // WebBrowser的Package Name
      mContext = createPackageContext(
                 "mobile.android.web.browser", Context.CONTEXT_INCLUDE_CODE
              | Context.CONTEXT_IGNORE_SECURITY);  
      // 动态装载WebBrowserActivity类
           mClass = mContext.getClassLoader().loadClass(
                 "mobile.android.web.browser.WebBrowserActivity");
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }
  // ①“浏览网页:Action”按钮的单击事件方法,使用Action方式浏览网页
  public void onClick_Action(View view)
  {
    // 指定了Action和要浏览的页面的Uri
    // Intent.ACTION_VIEW的值是android.intent.action.VIEW
    // Uri确定了Data(符合以http://开头的Uri的要求)
        Intent webIntent = new Intent(Intent.ACTION_VIEW,
               Uri.parse("http://blog.csdn.net/nokiaguy"));
    startActivity(webIntent);
    }
  // ②“浏览网页:setClassName”按钮单击事件方法,直接指定Package Name和Class Name
  public void onClick_SetClassName(View view)
  {
    Intent webIntent = new Intent();
    // 直接指定了WebBrowser应用的Package Name和WebBrowserActivity类的全名
        webIntent.setClassName("mobile.android.web.browser",
               "mobile.android.web.browser.WebBrowserActivity");
    // 指定Uri
    webIntent.setData(Uri.parse("http://nokiaguy.blogjava.net"));
    startActivity(webIntent);
  }
  // ③“浏览网页:setClassName_Context”按钮单击事件方法,指定Context对象和Class Name
  public void onClick_SetClassName_Context(View view)
  {
    if(mContext == null || mClass == null) return;
    Intent webIntent = new Intent();
    // 指定了Context对象和WebBrowserActivity类的全名
    webIntent.setClassName(mContext,
        "mobile.android.web.browser.WebBrowserActivity");
    // 指定Uri
    webIntent.setData(Uri.parse("http://nokiaguy.cnblogs.com"));
    startActivity(webIntent);
  }
  // ④“浏览网页:setClass“按钮单击事件方法,指定Context和Class对象
  public void onClick_SetClass(View view)
  {
    if(mContext == null || mClass == null) return;
    Intent webIntent = new Intent();
    // 指定Context和Class对象
        webIntent.setClass(mContext, mClass);
        // 指定Uri 
        webIntent.setData(Uri.parse("http://nokiaguy.cnblogs.com"));
    startActivity(webIntent);    
  }
  // ⑤“浏览网页:setComponentName”按钮单击事件,指定了ComponentName对象
  public void onClick_SetComponentName(View view)
  {
    if(mContext == null || mClass == null) return;
    // 通过ComponentName对象指定了Context和Class对象
        ComponentName cn = new ComponentName(mContext, mClass);
    Intent webIntent = new Intent();
    // 指定ComponentName对象
        webIntent.setComponent(cn);
    // 指定Uri
        webIntent.setData(Uri.parse("http://nokiaguy.cnblogs.com"));
    startActivity(webIntent);
  }
  // ⑥“显示计算器”按钮单击事件方法,通过指定Package Name和Class Name的方式显示系统计算器
  public void onClick_ShowCalculator(View view)
  {
    Intent intent = new Intent();
    // 指定计算器的Package Name和主窗口类的全名
        intent.setClassName("com.android.calculator2",
        "com.android.calculator2.Calculator");
    startActivity(intent);
  }
}

在①号方法中使用了Action调用浏览器显示网页,如果事先安装了WebBrowser,就会显示如图6-5所示的选择列表。而②至⑤号方法都直接指定了WebBrowser的Package Name和WebBrowserActivity的全名,只是指定的方式不同。这些方式中分别使用了setClassName、setClass和setComponentName方法。尽管在当前的Android系统中至少存在两个程序可以浏览网页(系统内置的浏览器和WebBrowser),但②至⑤号方法只会调用WebBrowser中的WebBrowserActivity(不会出现图6-5所示的选择列表),因为在这些方法中直接指定了WebBrowserActivity类(显式方式调用)。在⑥号方法中仍然通过显式的方式调用系统内置的计算器程序。

通过本例我们可以知道,如果想调用其他应用程序中的窗口,但又不想出现图6-5所示的选择列表(调用之前谁也不知道当前系统中有多少个窗口满足过滤条件),就可以使用显式调用窗口的方式。

答疑解惑:为什么显式调用其他应用程序的窗口时会失败

在显式调用其他应用程序窗口时会发现并不是都好使,有一些情况无法成功调用这些窗口。这其中原因很多,比较常见的原因就是Package Name和Activity Class Name指定错误。不过这也不是故意的,而是不知情所致。例如,如果要调用系统内置的浏览器程序。通过查询Android源代码的Browser程序可知。Package Name是com.android.browser,Activity Class Name(主窗口类名)是com.android.browser.BrowserActivity。这对于Android模拟器是没错的,不过对于真机上的Android系统,可能就会有变化(有的ROM4内置的Browser仍然和Android模拟器中的Browser是一样的),至少在Google官方原生的Android系统已经将Package Name变成了com.google.android. browser,而Activity Class Name并未变化。

既然系统自带程序的Package Name和Activity Class Name有可能发生变化,那么直接访问这些程序的包名和类名就会带来一定的风险。所以在调用系统程序的窗口时应尽量使用Action,而不要直接指定包名和类名。这样即使包名和类名变化了,只要Action不变,仍然可以成功调用窗口。当然,如果调用者和被调用者都是自己编写的程序(有源代码),显式和隐式调用窗口都没什么问题,因为包名和类名的变化在于自己,这一点很容易控制。尤其是在制作可扩展的系统时,往往不希望调用自己的扩展窗口(另一个自己编写的程序中的窗口)时显示一个选择列表(如果使用Action,有可能系统的其他程序会与当前程序的Action、Category和Data相同),而是想直接就显示指定的窗口,这种情况下使用显式调用的方式就比较好。在本例②至⑤号方法中尽管系统中有多个窗口可以打开网页,但由于使用了显式调用,所以就直接显示了WebBrowserActivity窗口。

那么除了包名和类名错误外,还有没有其他原因导致调用其他程序中窗口时失败呢?答案是肯定的。如果调用的窗口不在当前的应用程序中,在AndroidManifest.xml文件中声明窗口时必须允许该窗口被其他应用程序调用,也就是标签的exported属性值为“true”。现在比较下面两段窗口类(MyActivity)的声明代码。

不可以被其他应用程序通过显式方式5调用。

<activity android:name=".MyActivity"/>
可以被其他应用程序通过显式方式调用。

<activity android:name=".MyActivity"android:exported="true"/>

不过我们看到有很多窗口类在声明时并没有设置android:exported属性(该属性的默认值是true),但仍然可以使用显式的方式调用这些窗口。不过好像还不对。既然android:exported属性的默认值为true,那么不设置android:exported属性不也是允许窗口被外部程序调用吗(按第1段代码声明的窗口调用失败)?这对于使用Action调用的方式是没问题的,但对于显式方式必须将android:exported属性设为true才可以。当然,如果为该窗口指定了至少一个Action,就可以不用设置android:exported属性了(当然也不能将该属性设为false,那样外部程序使用任何方式都无法访问该窗口了)。所以显式和隐式跨应用程序调用窗口必须满足如下规则才能成功调用。

显式调用:如果声明窗口时未指定任何Action,android:exported属性必须设置,而且属性值必须为true。如果指定了Action,则并不需要设置android:exported属性,或设置android:exported属性值true也可。
隐式调用:必须指定Action,而且android:exported属性值不需要设置,或设置该属性值为true。

扩展学习:如何得知系统程序的包名和类名

如果要显式调用其他程序的窗口,必须要知道应用的包名和类名。但如果没有APK程序6或无法获得APK程序该如何做呢?例如,如果从Google Play安装某个程序,但自己的手机又没有root权限7,无法提取该程序的APK文件。但我们还想查看该程序中是否有可以处理某一资源的窗口,如可以浏览网页。现在就拿系统内置的Browser程序为例。

Browser的包名很容易获取,只要在系统的正在运行的应用程序中找到Browser(或浏览器),单击进入如图6-6所示(白框中的就是Browser的包名)。如果没有找到浏览器,可以单击右上角的“显示缓存进程”,如图6-7所示。

image

获取Android应用中声明某个Action的窗口的类名也可以使用PackageManager.queryIntent Activities方法,代码如下:

// 查询是否有窗口指定了叫Intent.ACTION_VIEW的Action
Intent intent = new Intent(Intent.ACTION_VIEW);
// 任意指定一个Uri,如果窗口指定了叫http的scheme,系统就会匹配该窗口
intent.setData(Uri.parse("http://blog.csdn.net/nokiaguy"));
// 列出系统中所有这样的窗口信息
List<ResolveInfo> resolveInfos = packageManager
    .queryIntentActivities(intent,PackageManager.GET_INTENT_ FILTERS);
// 通常第一个程序就是Browser,当然,也可以枚举所有的窗口信息
// 在LogCat视图中输出第一个满足条件的窗口的类名(<activity>标签的android:name属性值)
Log.d("Activity Action", String.valueOf(resolveInfos.get(0).activityInfo.name));

1 Package Name在AndroidManifest.xml文件中定义,如果没有AndroidManifest.xml文件源代码,可以将相应的APK文件解压,并反编译AndroidManifest.xml文件即可,或安装APK,查看/data/data目录中系统为该应用创建的目录名(就是Package Name)即可。
2 WebView就是Android SDK中的浏览器控件,用于显示Web页面。在后面的控件详解部分会详细介绍该控件。
3如果系统中没安装其他浏览器程序的话,就只有系统自带的浏览器和WebBrowser有相同的Action,并且符合浏览网页的过滤条件。
4 ROM就是Android系统的安装包,可以将特定版本的Android系统安装在Android硬件设备上,这一安装过程通常称为刷机。
5当然更不能通过隐式方式调用了,因为没用指定Action。
6有APK程序的情况会在本章后面的部分详细讨论。
7 root权限相当于Windows的Administrator用户。没有root权限会使得很多事都无法做,例如,无法访问手机内存中的系统目录。

相关文章
|
5天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
2天前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
2天前
android-agent-web中js-bridge案例
android-agent-web中js-bridge案例
10 2
|
2天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。
|
2天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【4月更文挑战第24天】随着移动开发技术的不断演进,提升应用性能和用户体验已成为开发者的核心任务。在Android平台上,Kotlin语言凭借其简洁性和功能性成为主流选择之一。特别是Kotlin的协程功能,它为异步编程提供了一种轻量级的解决方案,使得处理并发任务更加高效和简洁。本文将深入探讨Kotlin协程在Android开发中的应用,通过实际案例分析协程如何优化应用性能,以及如何在项目中实现协程。
|
3天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
35 1
|
3天前
|
运维 网络协议 Linux
Android 双网卡配置为连接到Android主机的PC提供外网访问(1)
Android 双网卡配置为连接到Android主机的PC提供外网访问(1)
16 0
|
3天前
|
存储 缓存 安全
Android系统 应用存储路径与权限
Android系统 应用存储路径与权限
6 0
Android系统 应用存储路径与权限
|
3天前
|
存储 安全 Android开发
Android系统 自定义系统和应用权限
Android系统 自定义系统和应用权限
18 0
|
8天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
8 0