Android面试题-源码分析为什么service里面startActivity抛异常?activity不会

Android程序员面试宝典

自定义控件

  • 一分钟实现贴纸功能
  • 一分钟实现TextView高亮
  • 一分钟实现新手引导页
  • 一分钟实现ViewPager卡片
  • 一分钟实现轮播图
  • 一分钟实现GridView拖拽
  • 一分钟实现底部导航栏
  • 一分钟实现底部FragmentTabhost
  • 一分钟实现多张图片选择
  • 一分钟实现仿美拍直播的点赞动画
  • 一分钟实现高仿今日头条视频列表
  • 一分钟实现购物车加减控件
  • 一分钟实现省市县三级联动
  • 一分钟实现二维码生成和扫描
  • 一分钟实现沉浸式状态栏
  • 一分钟实现图片裁剪
  • 一分钟实现视频弹幕
  • 一分钟实现图片缩放
  • 一分钟实现旋转选择器
  • 一分钟实现ofo小黄车的引导界面
  • 一分钟实现自定义ImageView外貌
  • 一分钟实现向左拖拽跳转详情页
  • 一分钟实现QQ首页动画特效
  • 一分钟实现ViewPager上下滑动

联网

  • 一分钟学会Retrofit并且进行实战
  • 一分钟实现OkHttp3

工具

  • 一分钟实现RecyclerView
  • 一分钟实现H5和Android通信
  • 一分钟实现pinyin4j
  • 一分钟实现分析内存泄漏

数据库

  • 一分钟实现LitePal数据库
  • 一分钟实现ormlite数据库
  • 一分钟实现GreenDao数据库

源码分析相关面试题

  • Volley源码分析
  • 注解框架实现原理
  • okhttp3.0源码分析
  • onSaveInstanceState源码分析
  • 静默安装和源码编译

Activity相关面试题

  • 保存Activity的状态
  • 深刻剖析activity启动模式(一)
  • 深刻剖析activity启动模式(二)
  • 深刻剖析activity启动模式(三)
  • Activity Task和Process之间的关系
  • 源码分析service开启Activity抛异常?activity不会抛异常
  • Activity优雅退出
  • onCreate源码分析

Service相关面试题

  • IntentService源码分析
  • Service是否在main thread中执行, service里面是否能执行耗时的操作?
  • Service不死之身

与XMPP相关面试题

  • XMPP协议优缺点
  • 极光消息推送原理

与性能优化相关面试题

  • 内存泄漏和内存溢出区别
  • UI优化和线程池实现原理
  • 代码优化
  • 内存性能分析
  • 内存泄漏检测
  • App启动优化
  • 与IPC机制相关面试题

与登录相关面试题

  • oauth认证协议原理
  • token产生的意义
  • 微信扫一扫实现原理

与开发相关面试题

  • 迭代开发的时候如何向前兼容新旧接口
  • 手把手教你如何解决as jar包冲突
  • context的原理分析
  • 解决ViewPager.setCurrentItem中间很多页面切换方案
  • 字体适配
  • 软键盘适配
  • 机型适配,例如三星、小米、华为、魅族等
  • CardView 设置水波纹效果

与人事相关面试题

  • 人事面试宝典

我们有时候需要在service里面启动activity,但是会发现报如下异常:

Android面试题-源码分析为什么service里面startActivity抛异常?activity不会_第1张图片

必须添加FLAG_ACTIVITY_NEW_TASK这个标记就可以了,那么为什么在activity里面不需要呢?接下来通过从源码角度带大家分析。

启动activity有两种形式

1)直接调用Context类的startActivity方法;这种方式启动的Activity没有Activity栈,因此不能以standard方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个Flag,服务就是通过Context调用。

2)调用被Activity类重载过的startActivity方法,通常在我们的Activity中直接调用这个方法就是这种形式;

Context.startActivity源码分析

我们查看Context类的startActivity方法,发现这竟然是一个抽象类;查看Context的类继承关系图如下:


Android面试题-源码分析为什么service里面startActivity抛异常?activity不会_第2张图片

我们看到诸如Activity,Service等并没有直接继承Context,Activity继承了ContextThemeWrapper,Service而是继承了ContextWrapper;

现在从源码分析ContextWrapper的实现:

@Override
public void startActivity(Intent intent) {
        mBase.startActivity(intent);
}

这个mBase是什么呢?这里我先直接告诉你,它的真正实现是ContextImpl类;至于为什么,有一条思路:在任意mBase打一个断点就能看到实现。

Context.startActivity最终使用了ContextImpl里面的方法,代码如下:

 @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

源码分析:

1)大家看看抛出来的异常是不是还是熟悉的味道。

2)通过判断可知当前的intent.getFlags是否带有FLAG_ACTIVITY_NEW_TASK这个标记,如果没有抛出异常,因为源码使用了&运算符,只有两个位都是1,结果才是1,所以可知service没有带FLAG_ACTIVITY_NEW_TASK标记,才抛出异常。

3)真正的startActivity使用了Instrumentation类的execStartActivity方法;继续跟踪:

 public ActivityResult execStartActivity(
     Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {
      ......
try {
     ......
     int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                   intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;

源码分析:

1)到这里我们发现真正调用的是ActivityManagerNative的startActivity方法;

Activity.startActivity源码分析

@Override
public void startActivity(Intent intent) {
        this.startActivity(intent, null);
}

源码可知:

1)调用当前类的startActivity方法,代码如下:

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

源码可知

1)调用了startActivityForResult

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
   ......
   Instrumentation.ActivityResult ar =
          mInstrumentation.execStartActivity(
          this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
    ......                 

源码可知

1)可以看到,其实通过Activity和ContextImpl类启动Activity并无本质不同,他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。

2)只是Activity不会去检查标记,所以并不会抛出异常。

至此源码分析完毕。

总结:

原理上讲是因为系统知道要将这个被启动的act放在哪个task里面,默认是和调用startActivity的act同一个task中,比如A启动B,那么系统会将B放入A所在的task中,但如果B在AndroidManifest.xml的中申明了独特的android:taskAffinity属性,那么B将会出现在一个新的task里,而不是和A一样,默认一个act的taskAffinity值就是app的包名。

另外,当启动的act最终是被放在一个新task中,这个结论一般我们也可以看到打开效果上有所不同,系统默认如果切task的话会有一个明显的切换动画,如果是在同一个task里打开的act,是不会有明显的切换动画,感觉上有点从一个app跳到了另一个app。这是一个经验性的发现,可以很方便地帮助我们识别这一结果,供参考。

我们在开发过程中会遇到各种各样的问题、坑,特别是Android平台,不过庆幸的是我们有源码,这是一切的根本,也就是说绝大部分问题只要我们愿意都可以从中找到问题的答案,虽然有时比较花时间,但却是提高的途径。也告诉我们出现问题时不要慌张,冷静地去寻找问题的root cause,这才是正确的解决问题的思路。

  • 欢迎关注微信公众号,长期推荐技术文章和技术视频

  • 微信公众号名称:Android干货程序员

Android面试题-源码分析为什么service里面startActivity抛异常?activity不会_第3张图片

你可能感兴趣的:(Android面试题-源码分析为什么service里面startActivity抛异常?activity不会)