Android跨应用启动

Posted 第二人格—影

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android跨应用启动相关的知识,希望对你有一定的参考价值。

Android跨应用启动

序言:

相信大家,很多时候都是在自己的app中,启动Activity,Service、BroadcastReceiver、contentProvider
。其实,这些都只是 一个app中 组件间的启动。本文要讲解的是 两个app间 组件 的启动。即:跨应用启动——使用隐式Intent 启动appB的某个组件。显示Intent做不到。

一、在开始之前,先来梳理一下跨应用启动的2种情形:

假设我们现在有两个应用,appA和appB 。

第一种:在appA的任意一个Activity中,启动appB的任一组件。

一个app项目中,通常都不会只有一个Activity,所以任何一个Activity组件都是可以启动appB中的任一组件的。

第二种:在appA的任何一个Service中,启动appB的任一组件。

众所周知,android中有四大组件,为什么小编,只介绍Activity和Service组件启动另一个app的四大组件?

其实,发送广播也是可以启动另一个app的组件,只要该app监听了此广播,app接收到广播后,在onReceive()方法中,再启动目标组件即可。为什么可以这么做?这是因为onReceive()方法中会要求传入context实例,有了context实例,就能使用context的方法,启动其他组件。(当然,BroadcastReceiver本身就是四大组件之一,BroadcastReceiver接收到广播的时候,已经是第一个成功被启动的组件了)。广播的用法相对简单和常见,不打算讲了。

另外需要跟大家说一下,Context类是一个抽象类,传入的context实例是由其子类来实现的,这种——用父类声明变量,由子类来实现的思维方式,在Java中是很常见的。特别是接口和抽象类,经常用到这种方式。对于小编这种由C转Java的人来说,真是一大坑啊。

为什么Activity和Service都可以直接使上图中的四个方法呢,这是因为Activity和Service都是继承自ContextWrapper,所以子类拥有父类的方法。BroadcastReceiver和contentProvider则不是,具体大家可以看官方API。

所以在app项目开发中,我们经常会在Activity或者Service中去启动一个组件,或发送广播。在实际项目中,经常会在Activity或者service中发送广播,进而实现组件间松耦合。

至于contentProvider,我想大家还没见过,这娃自动去干过事情吧,都是被动的调用。

二、跨应用启动的实战

下面让我们正式进入今天的主题:跨应用启动实战

appA的Activity中,启动appB的Activity

Android提供了在一个App中启动另一个App中的Activity的能力,这使我们的程序很容易就可以调用其他程序的功能,从而就丰富了我们App的功能。比如在微信中发送一个位置信息,对方可以点击这个位置信息启动腾讯地图并导航。这个场景在现实中作用很大,尤其是朋友在陌生的环境找不到对方时,这个功能简直就是救星。

本来想把本文的名字叫启动另一个进程中的Activity,觉得这样才有逼格。因为每个App都会运行在自己的虚拟机中,每个虚拟机跑在一个进程中。但仔细一想,能够称为一个进程,前提是这个App必须要运行起来才行。而Android提供的能力,是不需要另一个App启动就可以将其特定的Activity启动起来的。

也就是说,appB是处于未启动的状态,即——appB还不是系统的一个进程,那么当使用appA启动appB的某个组件时,请问,appB是否成为系统的进程?答案是yes。怎么看呢,可以从Android Studio 的Android device monito 中结合虚拟机看。

那么我们要怎么启动另一个应用的组件呢?有两种办法可以启动另一个App中的Activity。

第一种———隐式Intent的action方式。

相信这种方式,大家都不会陌生。这里就不进行过多的解析。这里只贴一下AppB的manifest(文件清单):

从文件清单中,我们可以看到,appB中有两个Activity。其中SecondActivity就是要被appA启动的Activity。
那么我们只要在appA的任意一个组件(Activity或Service),做如下的调用:

Intent intent=new Intent("android.intent.action.SecondActivity");
startActivity(intent);

就可以成功在 appA中 启动 appB 的 组件。另外还要跟大家说一点,SecondActivity的category一定要在文件清单中添加上,否则启动的时候会报错的。至于调用的时候为什么没添加category,可以自己百度下。

不知道大家有没有思考过这三个事情:
1、当appA 启动 appB的SecondActivity,请问:appB的MainActivity会不会被启动呢?正常情况下,我们点击appB,进到的是MainActivity这个lunch活动,那么现在我们是通过跨应用启动的,会不会要先启动B的MainActivity呢?答案是不会。这就体现了组件的思想。

2、当我们在SecondActivity中点击Back回退键时,回到的是appA的mainActivity界面,这里时候大家有没有想过。
SecondActivity和appA的mainActivity是不是同处于一个栈中呢?想要了解这个问题,只好自己动手打印栈的ID了。

3、综上,不知道大家想起:Android对于Activity的管理,也就是framework层中有一个ActivityManager。也就是说,无论你手机上有多少个应用,他们的Activity都是由ActivityManager这娃来创建和管理的。应用本身并没有创建Activity的能力。当然这其中又涉及到了Ibinder的通讯。这里暂时不讲。

第二种——用intent设置className或component的办法启动。

举例如下。新建两个项目ProjectA和ProjectB,用B中的MainActivity启动A的MainActivitity。关键代码如下:

ProjectA MainActivity

@Override
    public void onClick(View v) {
             Intent intent = new Intent(Intent.ACTION_VIEW);
             String packageName = "com.example.mylife.anotherapp";
             String className = "com.example.mylife.anotherapp.MainActivity";
             intent.setClassName(packageName, className);
             //second method
            //intent.setComponent(new ComponentName("com.example.mylife.anotherapp","com.example.mylife.anotherapp.MainActivity"));
            Bundle bundle = new Bundle();
            bundle.putString("msg", "this message is from project B ");
            intent.putExtras(bundle);
            intent.putExtra("pid", android.os.Process.myPid());
            startActivityForResult(intent, 1);
        //startActivity(intent);
    }

  @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    switch (requestCode) {
    case 1:
    if(resultCode == RESULT_OK) {
    textView.setText(data.getStringExtra("result"));
    }
    break;
    }
    }

ProjectB MainActivity

     @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    textView = (TextView)findViewById(R.id.text);

    Intent intent = getIntent();
    if(intent != null) {
    textView.setText(intent.getStringExtra("msg"));
    }
    }

    public void OnClick(View view) {
    Intent intent = new Intent();
    intent.putExtra("result","OK! from project a.");
    this.setResult(RESULT_OK,intent);
    this.finish();//要清楚这里为什么要用finish()。
    }

注意:如果在应用B中,是通过按下Back键,回退到应用A的MainActivity活动,那么A的onActivityResult()方法是不会被回调的,这是因为ProjectB的MainActivity活动只是出栈而已,并没有销毁。而只有ProjectB的MainActivity活动被销毁的时候,才会回调A的onActivityResult()方法。那如果是按了Back键回退的话怎么处理呢?这时候只要重写appB的onBackPressed()方法就好了。

@Override
public void onBackPressed() {
    super.onBackPressed();
    Intent intent = new Intent();
    intent.putExtra("result","OK! from project a.");
    this.setResult(RESULT_OK,intent);
    this.finish();//要清楚这里为什么要用finish()。
}

三:进阶———在A应用的Activity中启动(停止)——B应用的Service

应用B的manifest

应用B的service的代码:

public class MyService extends Service {

    private static final String TAG = "MyService";
    public MyService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
    // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
    super.onCreate();
    Log.d(TAG, "onCreate: ");
    }

    @Override
    public int onStartCommand(Intent intent,int flags, int startId) {
    Log.d(TAG, "onStartCommand: ");
    if(intent != null) {
    Log.d(TAG, "onStartCommand: "+intent.getStringExtra("msg"));
    }
    return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
    super.onDestroy();
    Log.d(TAG, "onDestroy: ");
    }
    }

应用A的代码:

@Override
    public void onClick(View v) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    String packageName = "com.example.mylife.anotherapp";
    String className = "com.example.mylife.anotherapp.MyService";
    intent.setClassName(packageName, className);

    switch (v.getId()) {
    case R.id.btn_start:
    Bundle bundle = new Bundle();
    bundle.putString("msg", "this message is from project B ");
    intent.putExtras(bundle);
    intent.putExtra("pid", android.os.Process.myPid());
    startService(intent);
    break;
    case R.id.btn_stop:
    stopService(intent);
    break;
    }
    }

测试结果:A应用直接启动B应用的服务,而B应用并不会打开自己的MainActivity。

好了,相信大家看完,都能够成功的启动另一个应用的Activity或者Service了。
另外大家还可以手动做一些尝试:
1、在ProjectA的Service中启动另一个应用的四大组件
2、在ProjectA的发送广播,启动另一个应用的四大组件
3、ContentProvider组件只有被访问的份了。
本次代码参考:http://blog.csdn.net/lincyang/article/details/45503675

以上是关于Android跨应用启动的主要内容,如果未能解决你的问题,请参考以下文章

Android跨应用启动

android如何跨片段分离/附加保留视图状态

Android 应用程序不会启动,也不会列出错误/警告。使用片段和问题似乎与 var args = ViewFragmentArgs

Android:向活动添加片段

用于数据加载的 Android 活动/片段职责

片段隐藏在Android中不起作用