heiyl

heiyl 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

heiyl 发布了文章 · 2020-12-04

Jetpack架构组件库-Lifecycle应用解析之知其然

原文连接:https://mp.weixin.qq.com/s/vF4v5ELjM6qYzU-MlXHhWw

前言

==本次主要讲解的内容:==

1、Lifecycle介绍,解决了什么问题

2、Lifecycle理解与运用

3、Lifecycle如何感知生命周期的状态?

一、Jetpack 介绍

1、什么是Lifecycle

Lifecycle 是具备宿主生命周期感知能力的组件。

它能持有组件(如 Activity 或 Fragment)生命周期状态的信息,并且允许其他观察者监听宿主的状态。

它也是 Jetpack 组件库的的核心基础,包括我们就后面会讲到的 LiveData , ViewModel 组件等也都是基于它来实现的。

2、Lifecycle解决什么问题?

Android开发中的主要问题之一是生命周期管理问题。

换句话说,如果不能正确处理应用程序生命周期,可能会导致一些问题,如内存泄漏和一些崩溃。

生命周期感知组件作为 Android 体系结构组件的一部分,可根据其他组件(如 Activity 或 Fragment)生命周期状态的变化完成任务。

因此,这些组件可帮助您创建组织更好的代码,并且通常重量更轻的代码更易于维护和测试。

一种常见的模式是在 Activity 和 Fragment 的生命周期方法中实现依赖组件的操作。但是,这种模式会导致代码条理性很差而且会扩散错误。

比如,我们通过Activity在屏幕上显示当前的定位,通常会做如下实现:

    class MyLocationListener {
        public MyLocationListener(Context context, Callback callback) {
            // ...
        }

        void start() {
            // 开启定位服务
        }

        void stop() {
            // 断开定位服务
        }
    }

    class MyActivity extends AppCompatActivity {
        private MyLocationListener myLocationListener;

        @Override
        public void onCreate(...) {
            myLocationListener = new MyL ocationListener(this, (location) -> {
                // update UI
            });
        }

        @Override
        public void onStart() {
            super.onStart();
            myLocationListener.start();
            //管理其他组件需要响应Activity生命周期
        }

        @Override
        public void onStop() {
            super.onStop();
            myLocationListener.stop();
            // 管理其他组件需要响应Activity生命周期
        }
    }
    

示例看起来没问题,但在真实的应用中,最终会有太多管理界面和其他组件的调用,以响应生命周期的当前状态。管理多个组件会在生命周期方法(如 onStart() 和 onStop())中放置大量的代码,这使得它们难以维护。

此外,无法保证组件会在 Activity 或 Fragment 停止之前启动。在我们需要执行长时间运行的操作(如 onStart() 中的某种配置检查)时尤其如此。这可能会导致出现一种竞争条件,在这种条件下,onStop() 方法会在 onStart() 之前结束,这使得组件留存的时间比所需的时间要长。

二、Lifecycle理解与运用

Lifecycle用法:

Lifecycle 有三种写法,接下来介绍它的写法:

使用 Lifecycle 前需要先添加依赖:

 //如果想单独使用,可引入下面这个依赖
api 'androidx.lifecycle:lifecycle-common:2.1.0'

以上面通过Activity在屏幕上显示当前的定位位列

1、LifecycleObserver 配合注解用法:

//1. 自定义的LifecycleObserver观察者,在对应方法上用注解声明想要观  察的宿主的生命周期事件即可
class LocationObserver extends LifecycleObserver{
    //宿主执行了onstart时,会分发该事件
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void onStart(@NotNull LifecycleOwner owner){
      //开启定位
    }
  
  //宿主执行了onstop时 会分发该事件
  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
  void onStop(@NotNull LifecycleOwner owner){
     //停止定位
  }
 }

//2. 注册观察者,观察宿主生命周期状态变化
class MyActivity extends AppCompatActivity{
  public void onCreate(...){
    LocationObserver observer =new LocationObserver()
    //应该在Activity的 onCreate() 方法中添加此行代码,以便通过使用  getLifecycle().addObserver() 将生命周期所有者Activity与LocationObserver关联。
    getLifecycle().addObserver(observer);
  }
 }

2、通过集成FullLifecyclerObserver 拥有宿主所有生命周期事件用法:

//1.查看FullLifecycleObserver源码
interface FullLifecycleObserver extends LifecycleObserver {

    void onCreate(LifecycleOwner owner);

    void onStart(LifecycleOwner owner);

    void onResume(LifecycleOwner owner);

    void onPause(LifecycleOwner owner);

    void onStop(LifecycleOwner owner);

    void onDestroy(LifecycleOwner owner);
}

//2.用法,复写需要监听的方法
class LocationObserver implements FullLifecycleObserver{
    void onStart(LifecycleOwner owner){
         //开启定位
    }
    void onStop(LifecycleOwner owner){
        //停止定位
    }
}

//3. 注册观察者,观察宿主生命周期状态变化
class MyActivity extends AppCompatActivity{
  public void onCreate(...){
    LocationObserver observer =new LocationObserver()
    //应该在Activity的 onCreate() 方法中添加此行代码,以便通过使用  getLifecycle().addObserver() 将生命周期所有者Activity与LocationObserver关联。
    getLifecycle().addObserver(observer);
  }
 }

3、通过LifecycleEventObserver宿主生命周期事件封装成 Lifecycle.Event的用法:

//1、LifecycleEventObserver源码
public interface LifecycleEventObserver extends LifecycleObserver {
    /**
     * Called when a state transition event happens.
     *
     * @param source The source of the event
     * @param event The event
     */
    void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event);
}

//2.用法 
class LocationObserver extends LifecycleEventObserver{
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
        //需要自行判断life-event是onstart, 还是onstop
       if(event == Lifecycle.Event.ON_START){
            //开启定位
        }else  if (event == Lifecycle.Event.ON_STOP) {
           //停止定位
       }
    }
}

//3. 注册观察者,观察宿主生命周期状态变化
class MyActivity extends AppCompatActivity{
  public void onCreate(...){
    LocationObserver observer =new LocationObserver()
    //应该在Activity的 onCreate() 方法中添加此行代码,以便通过使用  getLifecycle().addObserver() 将生命周期所有者Activity与LocationObserver关联。
    getLifecycle().addObserver(observer);
  }
 }

三、Lifecycle如何感知生命周期的状态?

我们知道了Lifecycle 是一个类,用于存储有关组件(如 Activity 或 Fragment)的生命周期状态的信息,并允许其他对象观察此状态。

我们先复习一下Activity&Fragment的生命周期方法:

1、Activity 生命周期:

onCreate()、onStart()、onResume()、onPause()、onStop() 和 onDestroy()。当 Activity 进入新状态时,系统会调用其中每个回调。

image

Activity A 启动 Activity B 时的操作发生顺序:

1、Activity A 的 onPause() 方法执行。

2、Activity B 的 onCreate()、onStart() 和 onResume() 方法依次执行(Activity B 现在具有用户焦点)。

3、然后,如果 Activity A 在屏幕上不再显示,其 onStop() 方法执行。

2、Fragment生命周期

Fragment和Activity还不同,它不属于四大组件范畴,但是它必须依托于Activity,其生命周期直接受宿主 Activity 生命周期的影响,

当 Activity 暂停时,Activity 的所有Fragment也会暂停;当 Activity 被销毁时,所有Fragment也会被销毁。不过,当 Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个Fragment,如添加或移除Fragment。

当其 Activity 运行时,Fragment的生命周期如下:

image

管理Fragment生命周期与管理 Activity 生命周期很相似。和 Activity 一样,片段也以三种状态存在:

已恢复

Fragment在运行中的 Activity 中可见。

已暂停

另一个 Activity 位于前台并具有焦点,但此片段所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。

已停止

片段不可见。宿主 Activity 已停止,或片段已从 Activity 中移除,但已添加到返回栈。已停止的片段仍处于活动状态(系统会保留所有状态和成员信息)。不过,它对用户不再可见,并随 Activity 的终止而终止。

如下图:Activity生命周期对应的Fragment的生命周期

image

Fragment所在 Activity 的生命周期会直接影响Fragment段的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个Fragment的类似回调。例如,当 Activity 收到 onPause() 时,Activity 中的每个Fragment也会收到 onPause()。

Fragment还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,从而执行构建和销毁Fragment界面等操作。这些额外的回调方法是:

  • [x] onAttach()

在片段已与 Activity 关联时进行调用(Activity 传递到此方法内)。

  • [x] onCreateView()

调用它可创建与片段关联的视图层次结构。

  • [x] onActivityCreated()

当 Activity 的 onCreate() 方法已返回时进行调用。

  • [x] onDestroyView()

在移除与片段关联的视图层次结构时进行调用。

  • [x] onDetach()

在取消片段与 Activity 的关联时进行调用。

3、Lifecycle如何感知生命周期的状态?

Lifecycle 使用两种主要枚举跟踪其关联组件的生命周期状态:

事件

从框架和 Lifecycle 类分派的生命周期事件。这些事件映射到 Activity 和 Fragment 中的回调事件。

状态

由 Lifecycle 对象跟踪的组件的当前状态。

构成 Android Activity 生命周期的状态和事件如下:

image

宿主生命周期与宿主状态的对应关系分别为:onCreate-Created、onStart-Started、onResume-Resumed、onpause-Started、onStop-Created、onDestroy-Destroyed

当生命周期所有者的状态发生更改时,分配的生命周期对象将更新为新状态。在任何给定时间,生命周期所有者将处于以下五种状态之一:

  • Lifecycle.State.INITIALIZED
  • Lifecycle.State.CREATED
  • Lifecycle.State.STARTED
  • Lifecycle.State.RESUMED
  • Lifecycle.State.DESTROYED

当组件通过不同状态转换时,生命周期对象将在已添加到列表中的任何观察器上触发事件。以下事件可用于在生命周期观察者中实现:

  • Lifecycle.Event.ON_CREATE
  • Lifecycle.Event.ON_START
  • Lifecycle.Event.ON_RESUME
  • Lifecycle.Event.ON_PAUSE
  • Lifecycle.Event.ON_STOP
  • Lifecycle.Event.ON_DESTROY
  • Lifecycle.Event.ON_ANY

添加 observer 时,完整的生命周期事件分发

如下图,生命周期所有者的状态更改顺序以及将在每个状态转换之间在观察者上触发的生命周期事件:

image

< END >

【Android进化之路】
image

微信扫描二维码,关注我的公众号。

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-12-04

Jetpack架构组件库-介绍与基本用法

原文连接:https://mp.weixin.qq.com/s/V2haCRugRYCGDZrA9iw7bQ

前言

==本次主要讲解的内容:==

1、Jetpack介绍

2、Jetpack架构组件库的相关用法

一、Jetpack 介绍

1、什么是Jetpack

Google 官方解释:

Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。

Jetpack 是 Google 为解决 Android 开发碎片化,打造成熟健康生态圈提出的战略规划,是 Google 对 Android 未来提出的发展方向,同时它也是众多优秀 ++Android 组件的集合++。

2、为何使用 Android Jetpack

Google 官方解释:

  • 遵循最佳做法:Android Jetpack 组件采用最新的设计方法构建,具有向后兼容性,可以减少崩溃和内存泄露。
  • 消除样板代码:Android Jetpack 可以管理各种繁琐的 Activity(如后台任务、导航和生命周期管理),以便您可以专注于打造出色的应用。
  • 减少不一致:这些库可在各种 Android 版本和设备中以一致的方式运作,助您降低复杂性。

Jetpack 的优势:

  • [ ] Jetpack 拥有基于生命周期感知的能力,可以减少 NPE(空指针异常) 崩溃、内存泄漏,为开发出健壮且流畅的程序提供强力保障;
  • [ ] Jetpack 可以消除大量重复样板式的代码,可以加速 Android 的开发进程,组件可搭配工作,也可单独使用,同时配合 Kotlin 语言特性能够显著提高工作效率;
  • [ ] 统一开发模式,抛弃传统的 MVC, MVP;

3、JetPack 的构成

jetpack构成

如上图:Jetpack 主要包括 4 个部分,分别是【Architecture:架构】、【UI:界面】、【behavior:行为】和【foundation:基础】。

Architecture,架构组件
目的:帮助开发者设计稳健、可测试且易维护的应用;
  1. Lifecycle:具备宿主生命周期感知能力的组件

特性:持有组件(如 Activity 或 Fragment)生命周期状态的信息,并且允许其他对象观察此状态;

  1. LiveData:新一代具备生命周期感知能力的数据订阅、分发组件

特性:支持共享资源、支持黏性事件的分发、不再需要手动处理生命周期、确保界面符合数据状态;

  1. ViewModel:具备生命周期感知能力的数据存储组件

特性:页面因配置变更导致的重启,此时数据不丢失;可以实现跨页面(跨 Activity)的数据共享;

  1. SavedState 架构组件原理解析

特性:因内存不足,电量不足导致页面被回收时可以搭配 ViewModel 实现数据存储与恢复;

  1. Room:轻量级 orm 数据库,本质上是一个 SQLite 抽象层

特性:使用简单(类似于 Retrofit 库),通过注解的方式实现相关功能,编译时自动生成相关实现类

  1. DataBinding:只是一种工具,解决的是 View 和数据之间的双向绑定

特性:支持数据与视图双向绑定、数据绑定空安全、减少模板代码、释放 Activity/Fragment 压力;

  1. Paging: 列表分页组件,可以轻松完成分页预加载以达到无限滑动的效果

特性:巧妙融合 LiveData、提供多种数据源加载方式;
不足之处:不支持列表数据增删改,列表添加 HeaderView,FooterView 定位不准确;

  1. Navigation 组件原理分析:端内统一路由组件

特性:能够为 Activity,Fragment,Dialog,FloatWindow 提供统一的路由导航服务,可以传递参数,指定导航动画,还支持深度链接等主要能力;
不足:十分依赖 xml 配置文件不利于组件化,模块化

  1. WorkManager:新一代后台任务管理组件,service 能做的事情它都能做

特性:支持周期性任务调度、链式任务调度、丰富的任务约束条件、程序即便退出,依旧能保证任务的执行;

Foundationy,基础组件
目的:提供横向功能,例如向后兼容性、测试、安全、Kotlin 语言支持,并包括多个平台开发的组件;
  1. Android KTX:优化了供 Kotlin 使用的 Jetpack 和 Android 平台 API,帮助开发者以更简洁、更愉悦、更惯用的方式使用 Kotlin 进行 Android 开发;
  2. AppCompat:帮助较低版本的 Android 系统进行兼容;
  3. Auto:开发 Android Auto 应用的组件,提供了适用于所有车辆的标准化界面和用户交互;
  4. 检测:从 AndroidStudio 中快速检测基于 Kotlin 或 Java 的代码;
  5. 多 Dex 处理:为具有多个 Dex 文件应用提供支持;
  6. 安全:安全的读写加密文件和共享偏好设置;
  7. 测试:用于单元和运行时界面测试的 Android 测试框架;
  8. TV:构建可让用户在大屏幕上体验沉浸式内容的应用;
  9. Wear OS:开发 Wear 应用的组件;
Behavior,行为组件
目的:帮助开发者的应用与标准 Android 服务(如通知、权限、分享)相集成;
  1. CameraX:帮助开发简化相机应用的开发工作,提供一致且易于使用的界面,适用于大多数 Android 设备,并可向后兼容至 Android 5.0(API 21);
  2. DownloadManager:处理长时间运行的 HTTP 下载的系统服务;
  3. 媒体和播放:用于媒体播放和路由(包括 Google Cast)的向后兼容 API;
  4. 通知:提供向后兼容的通知 API,支持 Wear 和 Auto;
  5. 权限:用于检查和请求应用权限的兼容性 API;
  6. 设置:创建交互式设置,建议使用 AndroidX Preference Library 库将用户可配置设置集成到应用中;
  7. 分享操作:可以更轻松地实现友好的用户分享操作;
  8. 切片:切片是一种 UI 模板,创建可在应用外部显示应用数据的灵活界面元素;
UI,界面组件
  1. Animation and Transition:该框架包含用于常见效果的内置动画,并允许开发者创建自定义动画和生命周期回调;
  2. Emoji Compatibility:即便用户没有更新 Android 系统也可以获取最新的表情符号;
  3. Fragment:组件化界面的基本单位;
  4. 布局:用 XML 中声明 UI 元素或者在代码中实例化 UI 元素;
  5. 调色板:从调色板中提取出有用的信息;
注意:

上面的图之前在官网是存在的,现在官网已经找不到此图,只有jetpack架构组件的文档入口;

猜想google官方也是旨在强化Architecture架构组件,因为UI、Behavior、Foundation 这三部分大多是对已有内容的收集整理。

所以接下来的关注点主要是在Architecture 架构部分
jetpack核心组件

4、Jetpack 与 AndroidX

参考官网Jetpack使用

1、Jetpack 库可以单独使用,也可以组合使用,以满足应用的不同需求。

2、Jetpack 库在 androidx 命名空间中发布。

androidx介绍:

==androidx 命名空间中的工件包含 Android Jetpack 库。与支持库一样,androidx 命名空间中的库与 Android 平台分开提供,并向后兼容各个 Android 版本。==

==AndroidX 对原始 Android 支持库进行了重大改进,后者现在已不再维护。androidx 软件包完全取代了支持库,不仅提供同等的功能,而且提供了新的库。==

AndroidX 还包括以下功能:

++AndroidX 中的所有软件包都使用一致的命名空间,以字符串 androidx 开头。支持库软件包已映射到对应的 androidx.* 软件包。有关所有旧类到新类以及旧构建工件到新构建工件的完整映射,请参阅软件包重构页面。++

++与支持库不同,androidx 软件包会单独维护和更新。从版本 1.0.0 开始,androidx 软件包使用严格的语义版本控制。您可以单独更新项目中的各个 AndroidX 库。++

++版本 28.0.0 是支持库的最后一个版本。我们将不再发布 android.support 库版本。所有新功能都将在 androidx 命名空间中开发。++

所以,目前我们的项目【朴新网校】、【朴新助教】、【数学宝典】都已经迁移使用了androidx库。

androidx迁移参照官网

注意:Jetpack 是各种组件库的统称,AndroidX 是这些组件的统一包名。

二、Jetpack架构组件库

1、应用架构指南

官网推荐应用架构:

推荐应用架构

在项目中运用到的框架的搭建基本基于改图,运用到的jetpack组件库有ViewModelLiveDataDataBinding;接下来逐一介绍他们的特性和用法。

2、ViewModel & LiveData

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

LiveData , ViewModel 组件都是基于Lifecycle来实现的,LiveData&ViewModel也是结合使用的,接下来具体看下如何使用

声明依赖项

dependencies {
        def lifecycle_version = "2.2.0"

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
        // LiveData
        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
}

实现 ViewModel

架构组件为界面控制器提供了 ViewModel 辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 Activity 或 Fragment 实例使用。例如,如果您需要在应用中显示用户列表,请确保将获取和保留该用户列表的责任分配给 ViewModel,而不是 Activity 或 Fragment,如以下示例代码所示:

public class MyViewModel extends ViewModel {
        private MutableLiveData<List<User>> users;
        public LiveData<List<User>> getUsers() {
            if (users == null) {
                users = new MutableLiveData<List<User>>();
                loadUsers();
            }
            return users;
        }

        private void loadUsers() {
            // Do an asynchronous operation to fetch users.
        }
    }
    

然后,您可以从 Activity 访问该列表,如下所示:

public class MyActivity extends AppCompatActivity {
        public void onCreate(Bundle savedInstanceState) {
            // Create a ViewModel the first time the system calls an activity's onCreate() method.
            // Re-created activities receive the same MyViewModel instance created by the first activity.

            MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
            model.getUsers().observe(this, users -> {
                // update UI
            });
        }
    }
    

ViewModel在 Fragment 之间共享数据

使用 ViewModel 对象解决这一常见的难点。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信

public class SharedViewModel extends ViewModel {
        private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

        public void select(Item item) {
            selected.setValue(item);
        }

        public LiveData<Item> getSelected() {
            return selected;
        }
    }

    //获取 ViewModelProvider 时,会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
    public class MasterFragment extends Fragment {
        private SharedViewModel model;

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            //通过requireActivity() Fragment 会检索包含它们的 Activity
            model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            itemSelector.setOnClickListener(item -> {
                model.select(item);
            });
        }
    }

    //获取 ViewModelProvider 时,会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)
    public class DetailFragment extends Fragment {

        public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            //通过requireActivity() Fragment 会检索包含它们的 Activity
            SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
            model.getSelected().observe(getViewLifecycleOwner(), { item ->
               // Update the UI.
            });
        }
    }
    

该方法的优势:

  • Activity 不需要执行任何操作,也不需要对此通信有任何了解。
  • 除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
  • 每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。

使用 LiveData 对象

官网使用参考

1、创建 LiveData 对象

LiveData 是一种可用于任何数据的封装容器,其中包括可实现 Collections 的对象,如 List。LiveData 对象通常存储在 ViewModel 对象中,并可通过 getter 方法进行访问,如以下示例中所示:

public class NameViewModel extends ViewModel {

    // Create a LiveData with a String
    private MutableLiveData<String> currentName;

        public MutableLiveData<String> getCurrentName() {
            if (currentName == null) {
                currentName = new MutableLiveData<String>();
            }
            return currentName;
        }

    // Rest of the ViewModel...
    }

最初,LiveData 对象中的数据并未经过设置。

注意:请确保用于更新界面的 LiveData 对象存储在 ViewModel 对象中,而不是将其存储在 Activity 或 Fragment 中,原因如下:

1、避免 Activity 和 Fragment 过于庞大。现在,这些界面控制器负责显示数据,但不负责存储数据状态。

2、将 LiveData 实例与特定的 Activity 或 Fragment 实例分离开,并使 LiveData 对象在配置更改后继续存在。

2、观察 LiveData 对象

在大多数情况下,在应用组件的 onCreate() 方法是开始观察 LiveData 对象原因如下:

1、确保系统不会从 Activity 或 Fragment 的 onResume() 方法进行冗余调用。

2、确保 Activity 或 Fragment 变为活跃状态后具有可以立即显示的数据。一旦应用组件处于 STARTED 状态,就会从它正在观察的 LiveData 对象接收最新值。只有在设置了要观察的 LiveData 对象时,才会发生这种情况。

LiveData 仅在数据发生更改时才发送更新,并且仅发送给活跃观察者。此行为的一种例外情况是,观察者从非活跃状态更改为活跃状态时也会收到更新。此外,如果观察者第二次从非活跃状态更改为活跃状态,则只有在自上次变为活跃状态以来值发生了更改时,它才会收到更新。

以下示例代码说明了如何开始观察 LiveData 对象:

    public class NameActivity extends AppCompatActivity {

        private NameViewModel model;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // Other code to setup the activity...

            // Get the ViewModel.
            model = new ViewModelProvider(this).get(NameViewModel.class);

            // Create the observer which updates the UI.
            final Observer<String> nameObserver = new Observer<String>() {
                @Override
                public void onChanged(@Nullable final String newName) {
                    // Update the UI, in this case, a TextView.
                    nameTextView.setText(newName);
                }
            };

            // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
            model.getCurrentName().observe(this, nameObserver);
        }
    }
    

3、更新 LiveData 对象

LiveData 没有公开可用的方法来更新存储的数据。MutableLiveData 类将公开 setValue(T) 和 postValue(T) 方法,如果您需要修改存储在 LiveData 对象中的值,则必须使用这些方法。通常情况下会在 ViewModel 中使用 MutableLiveData,然后 ViewModel 只会向观察者公开不可变的 LiveData 对象。

设置观察者关系后,您可以更新 LiveData 对象的值(如以下示例中所示),这样当用户点按某个按钮时会触发所有观察者:

    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            String anotherName = "John Doe";
            model.getCurrentName().setValue(anotherName);
        }
    });
    
注意:

您必须调用 setValue(T) 方法以从主线程更新 LiveData 对象。

如果在 worker 线程中执行代码,则您可以改用 postValue(T) 方法来更新 LiveData 对象。

3、Data Binding

Data Binding是一种支持库,借助该库,您可以使用声明性格式(而非程序化地)将布局中的界面组件绑定到应用中的数据源。

布局通常是使用调用界面框架方法的代码在 Activity 中定义的。例如,以下代码调用 findViewById() 来查找 TextView 微件并将其绑定到 viewModel 变量的 userName 属性:

    TextView textView = findViewById(R.id.sample_text);
    textView.setText(viewModel.getUserName());

以下示例展示了如何在布局文件中使用Data Binding 将文本直接分配到TextView。这样就无需调用上述任何 Java 代码。请注意赋值表达式中 @{} 语法的使用:

<TextView
        android:text="@{viewmodel.userName}" />
    
注意

如果您使用Data Biding的主要目的是取代 findViewById() 调用,请考虑改用ViewBinding。

使用过ButterKnife的都知道,目前ButterKnife作者建议切换至ViewBindng使用;在许多情况下,ViewBinding可简化实现,提高性能,提供与DataBinding相同的好处。

Data Binding使用场景:

布局和绑定表达式

数据绑定的布局以根标记 layout 开头,后跟 data 元素和 view 根元素。如下:

注意:布局表达式应保持精简,因为它们无法进行单元测试,并且拥有的 IDE 支持也有限。为了简化布局表达式,可以使用自定义绑定适配器。
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        
        <!--data 中的 user 变量描述了可在此布局中使用的属性。    -->
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <!--布局中的表达式使用“@{}”语法给控件赋值。-->
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"/>
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.lastName}"/>
       </LinearLayout>
    </layout>
    

==系统会为每个布局文件生成一个绑定类==。

1、默认情况下,类名称基于布局文件的名称,它会转换为驼峰形式并在末尾添加 Binding 后缀。

2、以上布局文件名为 activity_main.xml,因此生成的对应类为 ActivityMainBinding,==且都是ViewDataBinding的子类,所有布局对应的生成的绑定类都可以是ViewDataBinding类==

3、此类包含从布局属性(例如,user 变量)到布局视图的所有绑定,并且知道如何为绑定表达式指定值。

4、建议的绑定创建方法是在扩充布局时创建,如以下示例所示:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       User user = new User("Test", "User");
       binding.setUser(user);
    }

    

a、Activity 数据绑定 ( DataBinding ) :

1、DataBindingUtil类方法:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

2、生成的布局绑定类的inflate()方法:

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

    @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       //DataBindingUtil类方法
       ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
       //生成的布局绑定类的inflate()方法
       //ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
       User user = new User("Test", "User");
       binding.setUser(user);
    }

    

b、 Fragment、ListView 或 RecyclerView 适配器中使用数据绑定 ( DataBinding )

DataBindingUtil 或 生成的布局绑定类deinflate() 方法,如以下代码示例所示:
    ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
    // or
    ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

    
Data Binding绑定表达式:

xml里支持使用以下表达式:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 <)
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

不支持以下表达式:

  • this
  • super
  • new
  • 显式泛型调用
a、变量
1、生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。

2、例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。

3、如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0。

<!--变量给控件赋值-->
android:text="@{user.name}"

<!--控件给变量赋值(双向绑定)-->
android:text="@={user.name}"
b、Null 合并运算符(空运算符)
如果左边运算数不是 null,则 Null 合并运算符 (??) 选择左边运算数,如果左边运算数为 null,则选择右边运算数。
android:text="@{user.displayName ?? user.lastName}"
//等效于如下三目表达式
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
c、视图引用
1、表达式可以通过以下语法按 ID 引用布局中的其他视图:

2、绑定类将 ID 转换为驼峰式大小写。

3、在以下示例中,TextView 视图引用同一布局中的 EditText 视图:android:text="@{exampleText.text}"

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
    
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>
    
d、显示隐藏控制
1、首先在 xml 的 data 节点中引用View

2、然后设置visibility

<data>
    <import type="android.view.View"/>
</data>

android:visibility="@{student.boy ? View.VISIBLE : View.INVISIBLE}"
e、事件处理

方法引用:

==android:onClick="@{handlers::onClickFriend}"==

绑定表达式可将视图的点击监听器分配给MyHandlers 类的 onClickFriend() 方法,如下所示:

    public class MyHandlers {
        public void onClickFriend(View view) { ... }
    }

    
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="handlers" type="com.example.MyHandlers"/>
           <variable name="user" type="com.example.User"/>
       </data>
       <LinearLayout
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
           <TextView android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="@{user.firstName}"
               android:onClick="@{handlers::onClickFriend}"/>
       </LinearLayout>
    </layout>
    
注意:

1、在表达式中,您可以引用符合监听器方法签名的方法。

2、当表达式求值结果为方法引用时,数据绑定会将方法引用和所有者对象封装到监听器中,并在目标视图上设置该监听器。

3、如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。

4、表达式中的方法签名必须与监听器对象中的方法签名完全一致。

监听器绑定:

==android:onClick="@{() -> presenter.onSaveClick(task)}"==

绑定表达式可将视图的点击事件绑定打给Presenter 类的 onSaveClick(Task task) 方法,如下所示:

    public class Presenter {
        public void onSaveClick(Task task){}
    }

    
<?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable name="task" type="com.android.example.Task" />
            <variable name="presenter" type="com.android.example.Presenter" />
        </data>
        <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
            <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
            android:onClick="@{() -> presenter.onSaveClick(task)}" />
        </LinearLayout>
    </layout>
    
以上,我们尚未定义传递给 onClick(View) 的 view 参数。

监听器绑定提供两个监听器参数选项:您可以忽略方法的所有参数,也可以命名所有参数。

如果您想命名参数,则可以在表达式中使用这些参数。

例如,上面的表达式可以写成如下形式:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"
    
或者,如果您想在表达式中使用参数,则采用如下形式:
    public class Presenter {
        public void onSaveClick(View view, Task task){}
    }

    
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
    
监听长按事件,表达式应返回一个布尔值。
    public class Presenter {
        public boolean onLongClick(View view, Task task) { }
    }

    
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
    
注意:

1、监听器绑定这些是在事件发生时进行求值的 lambda 表达式。

2、数据绑定始终会创建一个要在视图上设置的监听器。

3、事件被分派后,监听器会对 lambda 表达式进行求值。

< END >

【Android进化之路】
image

微信扫描二维码,关注我的公众号。

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-12-03

Android兼容性优化-Android 8.0设置Activity透明主题崩溃

原文连接:https://mp.weixin.qq.com/s/g6...

崩溃日志:

1 java.lang.RuntimeException:Unable to start activity ComponentInfo{com.pxwx.assistant/com.pxwx.main.ui.MainActivity}: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
2 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
3 ......
4 Caused by:
5 java.lang.IllegalStateException:Only fullscreen opaque activities can request orientation
6 android.app.Activity.onCreate(Activity.java:1038)
7 androidx.core.app.ComponentActivity.onCreate(Unknown Source:0)
8 androidx.activity.ComponentActivity.onCreate(Unknown Source:0)
9 androidx.fragment.app.FragmentActivity.onCreate(Unknown Source:99)
10 androidx.appcompat.app.AppCompatActivity.onCreate(Unknown Source:10)
11 com.pxwx.base.activity.BaseActivity.onCreate(Unknown Source:0)
12 com.pxwx.main.ui.MainActivity.onCreate(Native Method)
13 android.app.Activity.performCreate(Activity.java:7183)
14 android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1221)
15 android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2910)
16 android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
17 android.app.ActivityThread.-wrap11(Unknown Source:0)
18 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
19 android.os.Handler.dispatchMessage(Handler.java:105)
20 android.os.Looper.loop(Looper.java:164)
21 android.app.ActivityThread.main(ActivityThread.java:6942)
22 java.lang.reflect.Method.invoke(Native Method)
23 com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
24 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

抛出异常信息:

Only fullscreen opaque activities can request orientation

Android8.0源码Activity.java:

https://www.androidos.net.cn/...


protected void onCreate(@Nullable Bundle savedInstanceState) {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);

        if (getApplicationInfo().targetSdkVersion > O && mActivityInfo.isFixedOrientation()) {
            final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
            final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
            ta.recycle();
            //如果Activity方向固定并且是透明(或者浮动),则会抛异常。
            if (isTranslucentOrFloating) {
                throw new IllegalStateException(
                        "Only fullscreen opaque activities can request orientation");
            }
        }
        ...
    }

查看ActivityInfo.java的isTranslucentOrFloating()方法:
https://www.androidos.net.cn/...

//检查Activity方式是否是透明或者是浮动的
public static boolean isTranslucentOrFloating(TypedArray attributes) {
        final boolean isTranslucent =
                attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent,
                        false);
        final boolean isSwipeToDismiss = !attributes.hasValue(
                com.android.internal.R.styleable.Window_windowIsTranslucent)
                && attributes.getBoolean(
                        com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
        final boolean isFloating =
                attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,
                        false);

        return isFloating || isTranslucent || isSwipeToDismiss;
    }

解决方法:

1、降级targetSDKVersion到26以下(废话!!)

2、移除mainfest文件里的screenOrientation属性

3、取消Activity主题里的windowIsTranslucent属性或者windowSwipeToDismiss属性或者windowIsFloating属性(根据你设置了什么属性来具体分析)

==4、移除manifest文件里的screenOrientation属性,并在Activity的onCreate方法里设置屏幕方向==

if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-12-01

Android兼容性优化-8.0之后禁止在后台启动服务的兼容性优化

前言

==本次主要内容包括:==

1、Android8.0之后IntentService启动异常跟踪

2、JobIntentService替代IntentService方案

一、Android8.0之后IntentService启动异常跟踪

项目中在做启动优化时,在Application 通过IntentService启动第三方组件时,bugly时常会上报如下问题:

*android.app.RemoteServiceException
Context.startForegroundService() did not then call Service.startForeground()*


# main(2)
android.app.RemoteServiceException
Context.startForegroundService() did not then call Service.startForeground()

1 android.app.ActivityThread$H.handleMessage(ActivityThread.java:2056)
2 android.os.Handler.dispatchMessage(Handler.java:106)
3 android.os.Looper.loop(Looper.java:192)
4 android.app.ActivityThread.main(ActivityThread.java:6959)
5 java.lang.reflect.Method.invoke(Native Method)
6 com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:557)
7 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:875)
1、Service和IntentService使用场景以及区别

Service的使用场景:

a、Service是运行在主线程的,如果我们需要执行耗时操作,也是在Service创建Thread执行。

b、如果耗时操作无法在当前Activity生命周期执行完成的任务就需要在Service执行,比如异步下载等。

c、需要长时间在后台运行,跟随APP生命周期的任务需要在Service执行,比如Socket长连接。

d、Service是所有服务的基类,我们通常都是继承该类实现服务,如果使用该类,我们需要对Service的生命周期进行管理,在合适的地方停止Service。

IntentService特点

a、IntentService继承于Service,通过源码可以看到,是在Service的基础上增加了Handler、Looper、HandlerThread的支持;

b、只需要重写onHandleIntent(Intentintent)实现异步任务,这个方法已经是非UI线程,可以执行耗时操作;

c、一旦这个方法执行完毕,就会立刻执行stopSelf()停止服务,无需手动停止服务。

==Android 8.0新增了startForegroundService方法,用于启动前台服务,前台服务是指带有通知栏的服务,如果我们使用startForegroundService启动服务,那么必须在5秒内调用startForeground()显示一个通知栏,否则就会报错==

2、明明调用startforeground了为什么还会报Context.startForegroundService() did not then call Service.startForeground()

首先看下启动IntentService的调用:

private void startInitService() {
    Intent intent = new Intent(this, InitIntentService.class);
    intent.setAction("com.pxwx.student.action");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForegroundService(intent);
    } else {
        startService(intent);
    }
}

在IntentService中的处理:

在onCreate方法中调用了startForeground方法

public class InitIntentService extends IntentService {

    public InitIntentService() {
        super("InitIntentService");
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private Notification getNotification() {
        NotificationChannel channel = new NotificationChannel("init", "", NotificationManager.IMPORTANCE_LOW);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.createNotificationChannel(channel);
        }
        return new Notification.Builder(this, "init")
                .setContentTitle("")
                .setContentText("")
                .setAutoCancel(true)
                .setSmallIcon(com.pxwx.student.core.R.mipmap.small_icon)
                .build();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            //notification ID must not be 0 
            startForeground(1,getNotification());
        }
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        initThirdSDK();
    }

onCreate方法中明明调用startforeground了为什么还会报Context.startForegroundService() did not then call Service.startForeground()?

分析:

1、启动IntentService服务在Application中执行了多次,IntentService在第二次启动时还未停止的话不知执行onCreate方法,但会走onStart()方法,所以在onStart()方法中也执行startforeground

@Override
public void onStart(@Nullable Intent intent, int startId) {
    super.onStart(intent, startId);
    //主要是针对后台保活的服务,如果在服务A运行期间,保活机制又startForegroundService启动了一次服务A,那么这样不会调用服务A的onCreate方法,只会调用onStart方法
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForeground(1,getNotification());
    }
}

2、但是问题依旧存在,只是减少了该问题的发生,然后我又尝试了讲starttForeground方法放在了onHandleIntent中执行

@Override
protected void onHandleIntent(@Nullable Intent intent) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        startForeground(1,getNotification());
    }
    initThirdSDK();
}

3、但是问题依旧存在,只是又减少了该问题的发生,不能完全杜绝该问题的发生,具体分析下IntentService的特性:

a、IntentService如果第一次启动后,onhandleIntent没处理完,继续startService,不会再重新实例化这个Service了,而是将请求放到请求队列里,等待第一个处理完再处理第二个。这种情况下,只有一个线程在运行

b、、IntentService处理任务时是按请求顺序处理的,也就是一个接一个处理

3、 IntentService源码分析

1、首先看下IntentService的继承关系等声明信息:

public abstract class IntentService extends Service {
    ...
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

可以看出IntentService是继承自Service的抽象类,有个抽象方法onHandleIntent需要子类覆写,通过注解我们知道该方法的执行是在子线程中的。

2、其次看下IntentService中声明的字段

//volatile修饰,保证其可见性

//Service中子线程中的Looper对象
private volatile Looper mServiceLooper;
//与子线程中Looper关联的Hander对象
private volatile ServiceHandler mServiceHandler;
//与子线程HandlerThread相关的一个标识
private String mName;
//设置Service的标志位,根据它的值来设置onStartCommand的返回值
private boolean mRedelivery;

/**
 * You should not override this method for your IntentService. Instead,
 * override {@link #onHandleIntent}, which the system calls when the IntentService
 * receives a start request.
 * @see android.app.Service#onStartCommand
 */
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

==mRedelivery是来处理onStartCommand返回值的一个标志位参数,着重看下onStartCommand的返回值在Service中定义的几个类型:==

public static final int START_CONTINUATION_MASK = 0xf;
public static final int START_STICKY_COMPATIBILITY = 0;
public static final int START_STICKY = 1;
public static final int START_NOT_STICKY = 2;
public static final int START_REDELIVER_INTENT = 3;

根据这几个类型的注释,可以翻译解释:

START_STICKY_COMPATIBILITY:兼容模式,如果Service在创建后,被系统杀死,此时不能保证onStartCommand方法会被执行(可能会被执行,也可能不会被执行);

START_STICKY:如果Service进程被kill掉,保留Service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建Service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到Service,那么参数Intent将为null;

START_NOT_STICKY:如果Service在启动后(从onStartCommand返回了)被系统杀掉了,在下一次调用Context.startService()之前,不会再创建Service。期间,也不接受空Intent参数的onStartCommand方法调用,因为空的Intent无法进行Service的创建;

START_REDELIVER_INTENT:在Service启动后,被系统杀掉了,将会重传最近传入的Intent到onStartCommand方法中对Service进行重建;

3、上边关于mRedelivery值控制onStartCommand的返回值的问题:

1、如果为true,则返回START_REDELIVER_INTENT,表示如果Service被系统杀死,可以进行重建并重传最近传入的Intent;

2、如果为false,则返回START_NOT_STICKY,表示如果Service被系统杀死,除非再次调用Context.startService(),不会对Servcie进行重建;

3、在Service被销毁的时候,会停止子线程的消息队列;

4、ntentService中还有一个设置mRedelivery的setter方法

4、总结分析:

==从IntentService的源码分析看,导致android.app.RemoteServiceException Context.startForegroundService() did not then call Service.startForeground():异常发生的原因:==

1、onStartCommand返回值的一个标志位参数默认是START_NOT_STICKY

2、如果Service在启动后(从onStartCommand返回了)被系统杀掉了,在下一次调用Context.startService()之前,不会再创建Service。期间,也不接受空Intent参数的onStartCommand方法调用,因为空的Intent无法进行Service的创建;

3、导致Context.startForegroundService() did not then call Service.startForeground()的原因可能是被系统杀掉了,未执行IntentService的onCreate、onStart、onHandleIntent方法中的startForeground方法

二、JobIntentService替代IntentService方案

综合上面的分析,没有能完全解决上面的异常情况,该如何解决呢?

通过IntentService源码中针对IntentService的部分注释如下:

 * <p class="note"><b>Note:</b> IntentService is subject to all the
 * <a href="/preview/features/background.html">background execution limits</a>
 * imposed with Android 8.0 (API level 26). In most cases, you are better off
 * using {@link android.support.v4.app.JobIntentService}, which uses jobs
 * instead of services when running on Android 8.0 or higher.
 * </p>

翻译一下:

IntentService受Android 8.0(API级别26)的所有后台执行限制的约束。在大多数情况下,在Android 8.0或更高版本上运行时您最好使用android.support.v4.app.JobIntentService而不是服务。
1、JobIntentService介绍

JobIntentService是Android 8.0 新加入的类,它也是继承自Service,根据官方的解释:

Helper for processing work that has been enqueued for a job/service. When running on Android O or later, the work will be dispatched as a job via JobScheduler.enqueue. When running on older versions of the platform, it will use Context.startService.

大概翻译一下:

JobIntentService用于执行加入到队列中的任务。对Android 8.0及以上的系统,JobIntentService的任务将被分发到JobScheduler.enqueue执行,对于8.0以下的系统,任务仍旧会使用Context.startService执行。
2、JobIntentService使用

1、在Manifest中声名Permission:

<uses-permission android:name="android.permission.WAKE_LOCK" />

2、在Manifest中声名Service:

<service android:name=".InitIntentService" android:permission="android.permission.BIND_JOB_SERVICE" />

3、实现JobIntentService类:

public class InitIntentService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, InitIntentService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // 具体逻辑
    }

}

4、调用启动实现JobIntentService

InitIntentService.enqueueWork(context, new Intent());

JobIntentService不需要关心JobIntentService的生命周期,不需要startService()方法,也就避免了开头中的crash问题,通过静态方法就可以启动,还是非常不错的。


关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-11-27

h5唤起app技术deeplink方案总结

前言

唤醒方式:

1、URL Schemes

2、android appLink

3、chrome intent

1、DeepLink实践URL Schemes方式

a、需要在AndroidManifest.xml文件进行配置
<activity
    android:name=".ui.activity.SplashActivity"
    android:exported="true"
    android:screenOrientation="portrait"
    android:theme="@style/NormalSplash">
    
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    
    <!--DeepLink h5唤醒app配置-->
    <intent-filter>
        <!--ACTION_VIEW:支持被检索-->
        <action android:name="android.intent.action.VIEW" />
        <!--CATEGORY_DEFAULT:响应隐式Intent-->
        <category android:name="android.intent.category.DEFAULT" />
        <!--CATEGORY_BROWSABLE:可被Web浏览器唤起-->
        <category android:name="android.intent.category.BROWSABLE" />
        <!--data:一个或多个,必须含有scheme标签,决定被唤起的URL格式-->
        <data
            android:host="app.puxinwangxiao.com"
            android:scheme="pxwxstudent" />
        <!--    
        <data
            android:host="app.puxinwangxiao.com"
            android:scheme="pxwxstudent" 
            android:pathPrefix="/pxwx"/>
        <data
            android:host="app.puxinwangxiao.com"
            android:scheme="pxwxstudent" 
            android:path="/pxwx/user"/>
        -->
    </intent-filter>
</activity>
注意:

App可以配置多个支持唤起的Activity

Activity可以支持被多个URL唤起

若一个App配置了多个支持唤起的Activity,它们的scheme和host一般一致,然后通过path、pathPrefix等进行定向区分

b、被唤起后解析URL数据

Uri数据的解析可以在Activity中通过getIntent().getData()实现

@Override 
public void onCreate(Bundle savesInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash);
    
    // 尝试获取WebApp页面上过来的URL
    Uri uri = getIntent().getData();
    if (uri != null) {
        // scheme部分
        String scheme=data.getScheme();
        // host部分
        String host=data.getHost();
        // 访问路径
        String path=data.getPath();
        //参数
        Set<String> paramKeySet=data.getQueryParameterNames();
    }
}
c、在h5页面上,通过如下方式使用:
<!--1.通过a标签打开,点击标签是启动-->
<!-- 注意这里的href格式 -- >
<a href="pxwxstudent://app.puxinwangxiao.com">open android app</a>

<!--2.通过iframe打开,设置iframe.src即会启动-->
<iframe data-original="pxwxstudent://app.puxinwangxiao.com"></iframe>

<!--3.直接通过window.location 进行跳转-->
window.location.href= "pxwxstudent://app.puxinwangxiao.com";
d、在原生App中唤起通过Intent方式
Intent intent = new Intent();
intent.setData(Uri.parse("pxwxstudent://app.puxinwangxiao.com/"));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

2、DeepLink实践Android AppLink方式

a、Android AppLink介绍
Android M以上版本可以通过AppLinks,让用户在点击一个链接时跳转到App的指定页面;

前提是这个App已经安装并经过验证。

App Links的最大的作用,就是可以避免从页面唤醒App时出现的选择浏览器选项框;

前提是必须注册相应的Scheme,就可以实现直接打开关联的App。

Android App Links有以下几点好处:

安全性/特殊性:由于Android App Links使用了HTTP/HTTPS URL的方式向开发者的服务器进行连接认证,所以其他应用无法使用我们的链接

无缝的用户体验:当用户未安装我们的应用时,由于使用的是HTTP/HTTPS URL,会直接打开一个网页,我们可以在这个网页中展示应用介绍等,而不是显示404或者是其他错误页面

支持Instant Apps:可以使用App Links直接打开一个未安装的Instant App

支持Google Search或其他浏览器:用户可以直接在Google Search/Google Assistant/手机浏览器/屏幕搜索中直接通过点击一个URL来打开我们的指定页面

b、Android AppLink集成

https://developer.android.com...

++创建intent filter++

我们在此处先假设用户是通过==http://resource.puxinwangxiao...

==Android Studio 2.3==以后提供了==App Links Assistant==来帮助开发者快速在==AndroidManifest.xml==中创建需要配置的==intent filter==,使用App Links Assistant有以下几个步骤:

点击Android Studio的菜单栏中的Tools > App Links Assistant

点击Open URL Mapping Editor,然后在对话框底部点击+去添加一个新的URL mapping

在弹出的Add URL Mapping对话框中输入对应的内容,包括Host、Path、Activity, 输入完成后点击OK

image

注:App Link 支持多个域名

使用App Links Assistant在manifest文件中自动生成的内容如下:

<activity
            android:name=".ui.activity.SplashActivity"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@style/NormalSplash">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                
            </intent-filter>
            
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:scheme="http"
                    android:host="resource.puxinwangxiao.com"
                    android:path="/pxwx" />
            </intent-filter>
        </activity>

++检查URL Mapping是否配置正确++

App Links Assistant提供了检查URL Mapping是否配置正确的快捷方式,操作如下:

点击Open URL Mapping Editor,然后在Check URL Mapping对话框中输入URL,当输入一个能够成功匹配到Acitivty的URL后,输入框下方会显示This URL maps to xxxxx(app)

image

++处理App Links进入应用的场景++

通过App Links Assistant -> Select Activity选择之前配置的URL对应的Activity, 点击Insert Code即可在onCreate方法中插入获取从App Links跳转而来的URL的代码,生成代码如下

// ATTENTION: This was auto-generated to handle app links.
Intent appLinkIntent = getIntent();
String appLinkAction = appLinkIntent.getAction();
Uri appLinkData = appLinkIntent.getData();

++检查assetlinks.json是否上传成功++

  • 为了在应用安装成功后,系统能自动验证该应用是否有权使用对应的域名,系统会向==http://resource.puxinwangxiao...,根据获取到的文件内容,验证应用域名的合法性。
  • 通过App Links Assistant -> Open Digital Asset Links File Generator -> Generate Digital Asset Links file的方式生成assetlinks.json文件,然后将该文件放置到正确的域名地址中。
  • 通过App Links Assistant -> Open Digital Asset Links File Generator -> Generate Digital Asset Links file -> Link and Verify可以检测是否正确的在服务器中放置配置文件,检测成功的话,显示如下图:

image

我这里检测后提示Network error.不影响
主要是http://resource.puxinwangxiao...

如果要让网站和不同的应用关联起来

网站可以在同一个assetlinks.json文件里声明和不同的app的关系。下面这个文件列出了两个声明,这两个声明声明了网站和两个应用之间的关联,这个文件位于https://app-pre.puxinwangxiao...

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.pxwx.student",
    "sha256_cert_fingerprints":
    ["BD:EF:57:3D:01:D0:32:79:6E:32:73:18:32:E2:36:B9:35:1B:9C:7D:0F:F0:B0:A9:BE:91:18:CE:27:1A:D8:4C"]
  }
},
{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.pxwx.assistant",
    "sha256_cert_fingerprints":
    ["BD:EF:57:3D:01:D0:32:79:6E:32:73:18:32:E2:36:B9:35:1B:9C:7D:0F:F0:B0:A9:BE:91:18:CE:27:1A:D8:4C"]
  }
}]

++注意: path、 pathPrefix、 pathPattern 之间的区别++

例如:https://app-pre.puxinwangxiao...
  • path 用来匹配完整的路径,这里将 path 设置为 /assistant/download.html 才能够进行匹配;
  • pathPrefix 用来匹配路径的开头部分,拿上面的 Uri 来说,这里将 pathPrefix 设置为 /assistant 就能进行匹配了;
  • pathPattern 用表达式来匹配整个路径,这里需要说下匹配符号与转义。

3、Chrome Intent方式实现从浏览器启动应用

在很多应用中需要我们从浏览器中直接启动应用,大多数采用的是上面提到的第一种scheme的方式,问题是如果手机中没有应用,该url会跳转到一个错误的界面。

==google官方在chrome中推出了一种Android Intents的方式来实现应用启动,通过在iframe中设置src为==

intent:HOST/URI-path // Optional host
#Intent;
package=[string];
action=[string];
category=[string];
component=[string]; 
scheme=[string];
end;

mainfest文件中定义要启动的activity

<activity
            android:name=".ui.activity.SplashActivity"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@style/NormalSplash">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data
                    android:host="app.puxinwangxiao.com"
                    android:scheme="pxwxstudent" />
            </intent-filter>
        </activity>

定义一个a标签为

<a href="intent://app.puxinwangxiao.com/#Intent;scheme=pxwxstudent;package=com.xxx.xxx;end">open Android App</a>

在浏览器中点击a标签,就可以启动应用程序的对应activity了.

如果手机中没有相应的应用,防止跳转到错误页面,将a标签设置为

<a href="intent://app.puxinwangxiao.com/#Intent;scheme=pxwxstudent;package=com.xxx.xxx;S.browser_fallback_url=https://www.puxinwangxiao.com;end">open Android App</a>

这样如果没有对应应用,该链接就会跳转到==S.browser_fallback_url==指定的url上。

4、总结:

1、URL Scheme兼容性

URL Scheme只需要原生App开发时注册Scheme即可,用户点击此类链接时,会自动唤醒App,并借助URL Router机制跳转到指定页面。

URL Scheme兼容性高,但却存在许多限制:

  • [x] 国内各个厂商浏览器差异很大,当要被唤醒的目标App未安装时,这个链接很容易出错。
  • [x] 当注册有多个Scheme相同的时候,目前是没有办法区分的。
  • [x] 不支持从其他App中的UIWebView中跳转到目标App。
  • [x] 被部分主流平台禁止,微信、微博、QQ浏览器、手机百度中都已经被禁止使用。

由于这些限制的存在,安卓发布了自己的第二套方案:Android的App Links。

2、App Links兼容性
  • [x] App links在国内的支持还不够,部分安卓浏览器并不支持跳转至App,而是直接在浏览器上打开对应页面。
  • [x] 系统询问是否打开对应App时,假如用户选择“取消”并且选中了“记住此操作”,那么用户以后就无法再跳转App。
3、chrome intent兼容性
  • [x] google通过chrome浏览器启动的优化方案;
  • [x] 很多第三方浏览器会拦截掉chrome intent启动应用的请求

三种方案都有各自的兼容性,这几项技术是基于系统平台的,每个系统版本的迭代后,配置方式都会有新的变化,国内的第三方平台openinstall也提供了专项功能,毕竟是专门做这个的,兼容性也都经受过市场严重,可以参考下。


关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-11-26

App启动流程-源码分析

前言

==本次主要内容包括:==

1、App的启动源码分析

2、启动过程关键节点

一、App启动源码流程分析

App启动流程的入口是通过Launcher的startActivity,通过手机桌面图标点击跳转,如下类:

\frameworks\base\core\java\android\app\LauncherActivity.java

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        Intent intent = intentForPosition(position);
        startActivity(intent);
    }

startActivity是如何实现的?

\frameworks\base\core\java\android\app\Activity.java

该类最终执行到Instrumentation的execStartActivity方法,查看注释分析如下:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
    //1、首先执行该方法    
    @Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
    //2、跳转至该方法
    @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.
            //3、最终判断执行该方法,继续分析startActivityForResult
            startActivityForResult(intent, -1);
        }
    }
    //4、跳转至该方法
    public void startActivityForResult(Intent intent, int requestCode) {
        startActivityForResult(intent, requestCode, null);
    }
    //5、跳转至此,该方法有点长,只分析重点
    public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
        if (mParent == null) {
            //6、执行这里execStartActivity,继续看Instrumentation的execStartActivity方法
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }
}

分析:

1、mInstrumentation的成员变量调用了它的execStartActivity方法

2、这里还看到有一个mMainThread的变量,猜测是APP的主线程

3、这两个变量在哪里实例化的呢?继续查看Activity.java有如下方法:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        //执行该方法    
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        mUiThread = Thread.currentThread();
        //1、mMainThread变量的定义
        mMainThread = aThread;
        //2、mInstrumentation变量的定义
        mInstrumentation = instr;
        mToken = token;
        mIdent = ident;
        mApplication = application;
        mIntent = intent;
        mReferrer = referrer;
        mComponent = intent.getComponent();
        mActivityInfo = info;
        mTitle = title;
        mParent = parent;
        mEmbeddedID = id;
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        if (voiceInteractor != null) {
            if (lastNonConfigurationInstances != null) {
                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
            } else {
                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                        Looper.myLooper());
            }
        }

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
    }

分析:

1、如上,这几个变量是传进来的,但是在Activity中没有任何地方调用到这段代码

2、猜测,它的实例化一定是在别的类中完成的。

3、这里收住,按既定的流程走,不然很容易绕进去,这里确实我绕进去出不来过。

==继续分析如上Activity.java类最终执行到Instrumentation的execStartActivity方法==

\frameworks\base\core\java\android\app\Instrumentation.java

public class Instrumentation {

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        //1、看重点,这里又出现了startActivity调用者ActivityManagerNative.getDefault()是什么呢?
        //2、通过源码分析getDefault 的定义会发现getDefault 方法返回的是一个IActivityManager类型的对象
        //3、IActivityManager是一个接口,真实返回的其实是一个ActivityManagerService
        //4、该类继承自ActivityManagerNative,而ActivityManagerNative 则实现了IActivityManager 接口
        //5、同时还继承了Binder ,很显然ActivityManagerService 是一个Binder 对象
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess();
            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、看重点,这里又出现了startActivity调用者ActivityManagerNative.getDefault()是什么呢?

2、通过源码分析getDefault 的定义会发现getDefault 方法返回的是一个IActivityManager类型的对象

3、IActivityManager是一个接口,真实返回的其实是一个ActivityManagerService

4、该类继承自ActivityManagerNative,而ActivityManagerNative 则实现了IActivityManager 接口

5、同时还继承了Binder ,很显然ActivityManagerService 是一个Binder 对象

6、综上分析,我们进入ActivityManagerService 的startActivity 方法。

\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

进入该方法之后又会调用该类的startActivityAsUser 方法。

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

    @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
            resultWho, requestCode, startFlags, profilerInfo, options,
            UserHandle.getCallingUserId());
    }

    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
                false, ALLOW_FULL_ONLY, "startActivity", null);
        // TODO: Switch to user app stacks here.
        return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, options, false, userId, null, null);
    }
}
1、注释:Switch to user app stacks here. 要切换到用户的APP栈。

2、接着返回执行了ActivityStackSupervisor的startActivityMayWait 这个方法。

3、startActivityMayWait这里面还会接着调用到startActivityLocked方法,该方法是ActivityStack.java中的方法:

\frameworks\base\services\core\java\com\android\server\am\ActivityStack.java

final class ActivityStack {
    
    final void startActivityLocked(ActivityRecord r, boolean newTask,
            boolean doResume, boolean keepCurTransition, Bundle options) {
            
        //省略...
        
        if (!isHomeStack() || numActivities() > 0) {
            //1、注意:这里有个开始启动前启动了一个预览的window
            // We want to show the starting preview window if we are
            // switching to a new task, or the next activity's process is
            // not currently running.
            boolean showStartingIcon = newTask;
            ProcessRecord proc = r.app;
            if (proc == null) {
                proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);
            }
            if (proc == null || proc.thread == null) {
                showStartingIcon = true;
            }
            if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION,
                    "Prepare open transition: starting " + r);
            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
                mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);
                mNoAnimActivities.add(r);
            } else {
                mWindowManager.prepareAppTransition(newTask
                        ? r.mLaunchTaskBehind
                                ? AppTransition.TRANSIT_TASK_OPEN_BEHIND
                                : AppTransition.TRANSIT_TASK_OPEN
                        : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
                mNoAnimActivities.remove(r);
            }
            mWindowManager.addAppToken(task.mActivities.indexOf(r),
                    r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                    (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
                    r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
            boolean doShow = true;
            
            //省略...
            
            if (r.mLaunchTaskBehind) {
                // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
                // tell WindowManager that r is visible even though it is at the back of the stack.
                mWindowManager.setAppVisibility(r.appToken, true);
                ensureActivitiesVisibleLocked(null, 0);
            } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
                // Figure out if we are transitioning from another activity that is
                // "has the same starting icon" as the next one.  This allows the
                // window manager to keep the previous window it had previously
                // created, if it still had one.
                ActivityRecord prev = mResumedActivity;
                if (prev != null) {
                    // We don't want to reuse the previous starting preview if:
                    // (1) The current activity is in a different task.
                    if (prev.task != r.task) {
                        prev = null;
                    }
                    // (2) The current activity is already displayed.
                    else if (prev.nowVisible) {
                        prev = null;
                    }
                }
                //2、mWindowManager.setAppStartingWindow();
                mWindowManager.setAppStartingWindow(
                        r.appToken, r.packageName, r.theme,
                        mService.compatibilityInfoForPackageLocked(
                                r.info.applicationInfo), r.nonLocalizedLabel,
                        r.labelRes, r.icon, r.logo, r.windowFlags,
                        prev != null ? prev.appToken : null, showStartingIcon);
                r.mStartingWindowShown = true;
            }
        } else {
            // If this is the first activity, don't do any fancy animations,
            // because there is nothing for it to animate on top of.
            mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
                    r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
                    (r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,
                    r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);
            ActivityOptions.abort(options);
            options = null;
        }
        if (VALIDATE_TOKENS) {
            validateAppTokensLocked();
        }
        //代码又从ActivityStack回到了mStackSupervisor中
        if (doResume) {
            mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
        }
    }

}

分析:

1、执行前这里想要开始启动前启动一个预览的window

2、通过mWindowManager.setAppStartingWindow();

3、WindowManagerService执行了addStartingWindow();

4、展示Starting Window

==接着继续走启动流程业务:==

1、在方法最后,代码又从ActivityStack回到了mStackSupervisor中,

2、在resumeTopActivitiesLocked中,首先判断了当前栈是否是Top栈,如果没有问题,就回到ActivityStack中,

3、resumeTopActivityLocked()又调用了resumeTopActivityInnerLocked(),这个方法里重点就来了:

4、resumeTopActivityInnerLocked()这里直接看到里面有一句是通过ActivityStackSupervisor 去调用的

\frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java

==新进程的创建:==

这个startSpecificActivityLocked()进行了一波进程是否存在的判断,接着会调用ActivityManagerService的startProcessLocked()开始进程的创建

public final class ActivityStackSupervisor implements DisplayListener {
    void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
                r.info.applicationInfo.uid, true);

        r.task.stack.setLaunchTime(r);
        //进程是否存在的判断
        if (app != null && app.thread != null) {
            try {
                if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                        || !"android".equals(r.info.packageName)) {
                    // Don't add this if it is a platform component that is marked
                    // to run in multiple processes, because this is actually
                    // part of the framework so doesn't make sense to track as a
                    // separate apk in the process.
                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                            mService.mProcessStats);
                }
                realStartActivityLocked(r, app, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }

            // If a dead object exception was thrown -- fall through to
            // restart the application.
        }
        //APP还未创建才会走到这里,通过ActivityManagerService开启进程
        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);
    }
}

ActivityManagerService的startProcessLocked()这个进程创建的过程也凹凸起伏的:

\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

    // Start the process.  It will either succeed and return a result containing
    // the PID of the new process, or else throw a RuntimeException.
    boolean isActivityProcess = (entryPoint == null);
    if (entryPoint == null) entryPoint = "android.app.ActivityThread";
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
            app.processName);
    checkTime(startTime, "startProcess: asking zygote to start proc");
    //Process.start()完成了ActivityThread的创建
    Process.ProcessStartResult startResult = Process.start(entryPoint,
            app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
            app.info.dataDir, entryPointArgs);
    checkTime(startTime, "startProcess: returned from zygote!");
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

Process.start()完成了ActivityThread的创建,之后就会执行ActivityThread的main()方法

Process.start()方法:

public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        Log.e(LOG_TAG,
                "Starting VM process through Zygote failed");
        throw new RuntimeException(
                "Starting VM process through Zygote failed", ex);
    }
}
1、在Process.start 方法中,实际调用的是 startViaZygote方法

2、在这个方法里通过 openZygoteSocketIfNeeded打开 Zygote 的 socket,并通过 zygoteSendArgsAndGetResult进行交互。

3、接着继续往下就会通过JNI调用到系统底层的内核代码了。

4、其实在Android里面的每一个App进程都是通过Zygote 进程fork出来的。

5、而且Zygote是属于系统进程来的,所以这里需要建立socket与其进行交互,为App启动创建新的进程。

6、新的进程创建了将会进入ActivityThread 的 main 方法,代码如下:

\frameworks\base\core\java\android\app\ActivityThread.java

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        AndroidKeyStoreProvider.install();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
        
        //1、Looper终于在此闪亮登场
        Looper.prepareMainLooper();
        
        //2、thread.attach(false);方法执行
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
    
    private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                //mgr是IActivityManager,也就是说这里通过AIDL调用了ActivityManagerService的attachApplication(mAppThread)
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
    }

分析main函数整体流程:

1、首先调用Looper.prepareMainLooper 去创建一个主线程的Looper

2、这个主线程的Looper是很重要的,系统对于App的很多管理都需要通过这个Looper去将消息传递到ActivityThread 的 Handler 里面去执行。

3、这里面当然包括了Acticity的工作流程。

4、接着,为整个应用创建了一个ActivityThread 对象。

5、attach 方法之后, 通过thread.getHandler方法去获取到主线程的Handler,

6、然后就调用Looper.loop() 让主线程的Looper进入轮询状态,等待消息过来处理。至此UI线程启动完毕。

分析attach流程:

1、调用这个attach 的时候传入的是false

2、所以执行会通过ActivityManagerService 调用到attachApplication;

3、里面会调用attachApplicationLocked,

4、然后就是在这个方法里面,又通过ApplicationThread 调用了bindApplication

代码如下:

public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        int callingPid = Binder.getCallingPid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid);
        Binder.restoreCallingIdentity(origId);
    }
}

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {
    ...
    try {
        ...
        thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                app.instrumentationUiAutomationConnection, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(mConfiguration), app.compat,
                getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked());
        updateLruProcessLocked(app, false, null);
        app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
    } catch (Exception e) {
        ...
    }

    ...
    
    // See if the top visible activity is waiting to run in this process...
    if (normalMode) {
        try {
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
            badApp = true;
        }
    }
    ...
    return true;
}

\frameworks\base\core\java\android\app\ActivityThread.java

public final void bindApplication(String processName, ApplicationInfo appInfo,
        List<ProviderInfo> providers, ComponentName instrumentationName,
        ProfilerInfo profilerInfo, Bundle instrumentationArgs,
        IInstrumentationWatcher instrumentationWatcher,
        IUiAutomationConnection instrumentationUiConnection, int debugMode,
        boolean enableBinderTracking, boolean trackAllocation,
        boolean isRestrictedBackupMode, boolean persistent, Configuration config,
        CompatibilityInfo compatInfo, Map<String, IBinder> services, Bundle coreSettings) {

    if (services != null) {
        // Setup the service cache in the ServiceManager
        ServiceManager.initServiceCache(services);
    }

    setCoreSettings(coreSettings);

    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providers;
    data.instrumentationName = instrumentationName;
    data.instrumentationArgs = instrumentationArgs;
    data.instrumentationWatcher = instrumentationWatcher;
    data.instrumentationUiAutomationConnection = instrumentationUiConnection;
    data.debugMode = debugMode;
    data.enableBinderTracking = enableBinderTracking;
    data.trackAllocation = trackAllocation;
    data.restrictedBackupMode = isRestrictedBackupMode;
    data.persistent = persistent;
    data.config = config;
    data.compatInfo = compatInfo;
    data.initProfilerInfo = profilerInfo;
    sendMessage(H.BIND_APPLICATION, data);
}

bindApplication分析:

1、数据传递封装通过一个叫AppBindData的方式包装起来,

2、然后通过Handler方式,发送给了一个叫H的Handler实例,

3、这个实例在ActivityThread中,它在handleMessage中对BIND_APPLICATION这个消息的处理:

case BIND_APPLICATION:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
    AppBindData data = (AppBindData)msg.obj;
    handleBindApplication(data);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

private void handleBindApplication(AppBindData data) {
    ...
    // Continue loading instrumentation.
    if (ii != null) {
        ...
        mInstrumentation.init(this, instrContext, appContext, component,
                data.instrumentationWatcher, data.instrumentationUiAutomationConnection);

        if (mProfiler.profileFile != null && !ii.handleProfiling
                && mProfiler.profileFd == null) {
            mProfiler.handlingProfiling = true;
            final File file = new File(mProfiler.profileFile);
            file.getParentFile().mkdirs();
            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
        }
    } else {
        mInstrumentation = new Instrumentation();
    }

    ...

    try {
        // If the app is being launched for full backup or restore, bring it up in
        // a restricted environment with the base application class.
        //1、这里makeApplication
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        mInitialApplication = app;

        ...

        // Do this after providers, since instrumentation tests generally start their
        // test thread at this point, and we don't want that racing.
        try {
            mInstrumentation.onCreate(data.instrumentationArgs);
        }
        catch (Exception e) {
            ...
        }
        //2、Application的onCreate()执行
        try {
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            ...
        }
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
}
1、在handleBindApplication 这个方法里面,为这个应用创建了唯一的Application对象,和唯一的Instrumentation 对象

2、Application app = data.info.makeApplication(data.restrictedBackupMode, null);这里最终调用了Application的attachBaseContext(context);

3、 mInstrumentation.callApplicationOnCreate(app);的调用执行了Application 的onCreate方法。到这里,Application创建了。

Application中在onCreate()方法里去初始化各种全局的变量数据是推荐的做法,但是如果你想把初始化的时间点提前到极致,也可以去重写attachBaseContext()方法

public class CustomApplication extends Application {  

    @Override  
    protected void attachBaseContext(Context base) {  
        // 在这里调用Context的方法会崩溃  
        super.attachBaseContext(base);  
        // 在这里可以正常调用Context的方法  
    }  

}

image

继续分析:

1、后面通过ActivityStackSupervisor 调用attachApplicationLocked,并且传入了一个ProcessRecord 对象。

2、里面会调用realStartActivityLocked 这个方法

3、然后这个方法里面又会通过ApplicationThread 调用scheduleLaunchActivity。

4、大致过程和上面创建Application 的时候流程一直,最终还是会调用到scheduleLaunchActivity 的真正实现

5、完后,这个方法里面最好会发送一条消息到H 里面去处理:sendMessage(H.LAUNCH_ACTIVITY, r) 。

LAUNCH_ACTIVITY接收如下分支:

case LAUNCH_ACTIVITY: {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

    r.packageInfo = getPackageInfoNoCheck(
            r.activityInfo.applicationInfo, r.compatInfo);
    //1、调用handleLaunchActivity()方法
    handleLaunchActivity(r, null);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    if (r.profilerInfo != null) {
        mProfiler.setProfiler(r.profilerInfo);
        mProfiler.startProfiling();
    }

    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    if (localLOGV) Slog.v(
        TAG, "Handling launch of " + r);

    // Initialize before creating the activity
    WindowManagerGlobal.initialize();
    //2、Activity 终于被创建了,调用了performLaunchActivity方法创建Activity
    Activity a = performLaunchActivity(r, customIntent);

    //如果Activity 被创建成功的话,会调用handleResumeActivity 
    //然后会调用到performResumeActivity,
    //然后会调用到Activity 的performResume方法
    //最终调用到activity.onResume()生命周期方法
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);

        if (!r.activity.mFinished && r.startsNotResumed) {
            // The activity manager actually wants this one to start out
            // paused, because it needs to be visible but isn't in the
            // foreground.  We accomplish this by going through the
            // normal startup (because activities expect to go through
            // onResume() the first time they run, before their window
            // is displayed), and then pausing it.  However, in this case
            // we do -not- need to do the full pause cycle (of freezing
            // and such) because the activity manager assumes it can just
            // retain the current state it has.
            try {
                r.activity.mCalled = false;
                mInstrumentation.callActivityOnPause(r.activity);
                // We need to keep around the original state, in case
                // we need to be created again.  But we only do this
                // for pre-Honeycomb apps, which always save their state
                // when pausing, so we can not have them save their state
                // when restarting from a paused state.  For HC and later,
                // we want to (and can) let the state be saved as the normal
                // part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
                }
                if (!r.activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onPause()");
                }

            } catch (SuperNotCalledException e) {
                throw e;

            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                            "Unable to pause activity "
                            + r.intent.getComponent().toShortString()
                            + ": " + e.toString(), e);
                }
            }
            r.paused = true;
        }
    } else {
        // If there was an error, for any reason, tell the activity
        // manager to stop us.
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
        } catch (RemoteException ex) {
            // Ignore
        }
    }
}

接着看一下performLaunchActivity做了什么?

里面调用了Activity 的onCreate 和 onStart 等生命周期方法,包括Theme等的设置

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");

        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            //1、mInstrumentation通过newActivity调用了ActivityActivity 的onCreate 和 onStart等生命周期方法
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

            if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                activity.mStartedActivity = false;
                //Activity的Theme主题的设置
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onCreate()");
                }
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
                if (!r.activity.mFinished) {
                    if (r.isPersistable()) {
                        if (r.state != null || r.persistentState != null) {
                            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                    r.persistentState);
                        }
                    } else if (r.state != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                    }
                }
                if (!r.activity.mFinished) {
                    activity.mCalled = false;
                    if (r.isPersistable()) {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state,
                                r.persistentState);
                    } else {
                        mInstrumentation.callActivityOnPostCreate(activity, r.state);
                    }
                    if (!activity.mCalled) {
                        throw new SuperNotCalledException(
                            "Activity " + r.intent.getComponent().toShortString() +
                            " did not call through to super.onPostCreate()");
                    }
                }
            }
            r.paused = true;

            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to start activity " + component
                    + ": " + e.toString(), e);
            }
        }

        return activity;
    }

Application以及Activity的启动流程图如下:

image


关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-11-25

App启动优化-Google官方指导

前言

==本次主要内容包括:==

1、App的启动方式

2、启动过程分析以及优化方案

3、启动耗时统计

一、App的启动方式

谷歌官方文档

App启动有三种状态,每种状态都会影响App对用户可感知的时间:冷启动,热启动和温启动。

在冷启动中,应用从头开始启动。在其他状态下,系统需要将后台运行中的应用带入前台。建议您始终在假定冷启动的基础上进行优化。这样做也可以提升温启动和热启动的性能。

要优化应用以实现快速启动,了解系统和应用层面的情况以及它们在各个状态中的互动方式很有帮助。

1、冷启动

冷启动是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用自设备启动后或系统终止应用后首次启动。这种启动给最大限度地减少启动时间带来了最大的挑战,因为系统和应用要做的工作比在其他启动状态下更多。

特点:

冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化启动(SplishActivity)类(包括一系列的测量、布局、绘制),最后显示在界面上。

2、热启动

应用的热启动比冷启动简单得多,开销也更低。在热启动中,系统的所有工作就是将您的 Activity 带到前台。如果应用的所有 Activity 都还驻留在内存中,则应用可以无须重复对象初始化、布局扩充和呈现。

但是,如果一些内存为响应内存整理事件(如 onTrimMemory())而被完全清除,将需要重新创建这些对象,以响应热启动事件。

热启动显示的屏幕上行为和冷启动场景相同:系统进程显示空白屏幕,直到应用完成 Activity 呈现。

特点:

热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个启动类(SplishActivity)就行了,而不必创建和初始化Application

3、温启动

温启动涵盖在冷启动期间发生的操作的一些子集;同时,它的开销比热启动多。有许多潜在状态可视为温启动。例如:

  • 用户退出您的应用,但之后又重新启动。进程可能已继续运行,但应用必须通过调用 onCreate() 从头开始重新创建 Activity。
  • 系统将您的应用从内存中逐出,然后用户又重新启动它。进程和 Activity 需要重启,但传递到 onCreate() 的已保存实例状态包对于完成此任务有一定助益。

二、启动过程分析以及优化方案

冷启动包含了整个启动流程所有环节,需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等.

在冷启动开始时,系统有三个任务。这三个任务是:

  1. 加载并启动应用。
  2. 在启动后立即显示应用的空白启动窗口。
  3. 创建应用进程。

系统一创建应用进程,应用进程就负责后续阶段:

  1. 创建应用对象。
  2. 启动主线程。
  3. 创建主 Activity。
  4. 扩充视图。
  5. 布局屏幕。
  6. 执行初始绘制。

一旦应用进程完成第一次绘制,系统进程就会换掉当前显示的后台窗口,替换为主 Activity。此时,用户可以开始使用应用。

如下图:应用冷启动的重要部分的可视表示

image

在创建应用和创建 Activity 的过程中可能会出现性能问题。

Application用创建

当应用启动时,空白启动窗口将保留在屏幕上,直到系统首次完成应用绘制。完成后,系统进程会换掉应用的启动窗口,允许用户开始与应用互动。

如果您在自己的应用中使 Application.onCreate() 过载,系统将在应用对象上调用 onCreate() 方法。之后,应用衍生主线程,也称为界面线程,让其执行创建主 Activity 的任务。

从此时开始,系统级和应用级进程根据应用生命周期阶段继续运行。

Activity 创建

在应用进程创建 Activity 后,Activity 将执行以下操作:

  1. 初始化值。
  2. 调用构造函数。
  3. 根据 Activity 的当前生命周期状态,相应地调用回调方法,如 Activity.onCreate()。

通常,onCreate() 方法对加载时间的影响最大,因为它执行工作的开销最高:加载和扩充视图,以及初始化运行 Activity 所需的对象。

含主题背景的启动屏幕

您可能希望为应用的加载体验设置主题背景,从而使应用的启动屏幕在主题背景上与应用的其余部分保持一致,而不是与系统主题背景一致。这样做可以隐藏缓慢的 Activity 启动。

实现含主题背景的启动屏幕的常见方式是使用 windowDisablePreview 主题背景属性关闭启用应用时系统进程绘制的初始空白屏幕。但是,此方法可能导致启动时间比不抑制预览窗口的应用更长。此外,它还迫使用户在没有反馈的情况下等待 Activity 启动完成,使其不确定应用是否正常运行。

提供的解决方案:可以使用 Activity 的 windowBackground 主题背景属性,为启动 Activity 提供简单的自定义可绘制资源。

例如,您可能创建新的可绘制文件,并从布局 XML 和应用清单文件中引用它,如下所示:

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
      <!-- The background color, preferably the same as your normal theme -->
      <item android:drawable="@android:color/white"/>
      <!-- Your product logo - 144dp color version of your app icon -->
      <item>
        <bitmap
          android:data-original="@drawable/product_logo_144dp"
          android:gravity="center"/>
      </item>
    </layer-list>

清单文件:

<activity ...
    android:theme="@style/AppTheme.Launcher" />

为什么出现白屏

冷启动白屏持续时间可能会很长,它的启动速度是由于以下引起的:

1、Application的onCreate流程,对于APP来说,通常会在这里做大量的通用组件的初始化操作;
建议:很多第三方SDK都放在Application初始化,我们可以放到用到的地方才进行初始化操作。

2、Activity的onCreate流程,特别是UI的布局与渲染操作,如果布局过于复杂很可能导致严重的启动性能问题;
建议:Activity仅初始化那些立即需要的对象,xml布局减少冗余或嵌套布局。

优化APP启动速度意义重大,启动时间过长,可能会使用户直接卸载APP。

总结:

关于启动加速方案,Google给出的建议是:

1.利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;

2.避免在启动时做密集沉重的初始化;

3.定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

三、启动耗时统计

要正确诊断启动时间性能,您可以跟踪一些显示应用启动所需时间的指标。

统计App启动初步显示时间

在 Android 4.4(API 级别 19)及更高版本中,logcat 包括一个输出行,其中包含名为 Displayed 的值。此值代表从启动进程到在屏幕上完成对应 Activity 绘制所经过的时间。经过的时间包括以下事件序列:

  1. 启动进程。
  2. 初始化对象。
  3. 创建并初始化 Activity。
  4. 扩充布局。
  5. 首次绘制应用。

项目中日志行类似于以下示例:

2019-12-21 19:20:53.327 1458-1552/? I/ActivityManager: Displayed com.pxwx.student/.ui.activity.SplashActivity: +281ms

image

注意:

这种方式在加载并显示所有资源之前,logcat 输出中的 Displayed 指标不一定会捕获时间:它会省去布局文件中未引用的资源或应用作为对象初始化一部分创建的资源。它排除这些资源的原因是加载它们属于一个内嵌进程,并且不会阻止应用的初步显示。

可以使用 ADB Shell Activity Manager 命令运行应用来测量初步显示所用时间。
以我们项目为例:

adb shell am start -S -W com.pxwx.student/.ui.activity.SplashActivity

这是优化前显示所用的时间

image

adb shell am start -S -W com.pxwx.student/.ui.activity.SplashActivity
Stopping: com.pxwx.student
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.pxwx.student/.ui.activity.SplashActivity }
Status: ok
Activity: com.pxwx.student/.ui.activity.SplashActivity
ThisTime: 2298
TotalTime: 2298
WaitTime: 2359
Complete
  • ==ThisTime== 表示一连串启动 Activity 的最后一个 Activity 的启动耗时,一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。
  • ==TotalTime==:应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示
  • ==WaitTime== 返回从 startActivity 到应用第一帧完全显示这段时间. 就是总的耗时,一般比TotalTime大点,包括系统影响的耗时

所以我们只需要以TotalTime为准就可以。

从上面看出优化前app启动时长约为2.3s

对比一下优化后app启动时长如下:

image

C:\Users\heiyulong>adb shell am start -S -W com.pxwx.student/.ui.activity.SplashActivity
Stopping: com.pxwx.student
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.pxwx.student/.ui.activity.SplashActivity }
Status: ok
Activity: com.pxwx.student/.ui.activity.SplashActivity
ThisTime: 858
TotalTime: 858
WaitTime: 981
Complete

优化后TotalTime为858ms不到1s,有了大幅的提升,启动优化提升60%。

App启动时间分析

以6.0.1源码看

命令“adb shell am start -S -W” 的实现是在"frameworks\base\cmds\am\src\com\android\commands\am\Am.java"文件中

am脚本

adb shell am命令会执行am脚本:

\frameworks\base\cmds\am\am

#!/system/bin/sh
#
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/am.jar
exec app_process $base/bin com.android.commands.am.Am "$@"

脚本会调用\frameworks\base\cmds\am\src\com\android\commands\am\Am.java的main方法:

public class Am extends BaseCommand {
    private IActivityManager mAm;

    /**
     * Command-line entry point.
     *
     * @param args The command-line arguments
     */
    public static void main(String[] args) {
        (new Am()).run(args);
    }

    @Override
    public void onRun() throws Exception {

        mAm = ActivityManagerNative.getDefault();
        if (mAm == null) {
            System.err.println(NO_SYSTEM_ERROR_CODE);
            throw new AndroidException("Can't connect to activity manager; is the system running?");
        }

        String op = nextArgRequired();

        if (op.equals("start")) {//走这里 start
            runStart();
        } else if (op.equals("startservice")) {
            runStartService();
        } else if (op.equals("stopservice")) {
            runStopService();
        } else if (op.equals("force-stop")) {
            runForceStop();
        } else if (op.equals("kill")) {
            runKill();
            //...
        } else {
            showError("Error: unknown command '" + op + "'");
        }
    }

    private void runStart() throws Exception {
        //...
        IActivityManager.WaitResult result = null;
            int res;
            final long startTime = SystemClock.uptimeMillis();
            if (mWaitOption) {
                result = mAm.startActivityAndWait(null, null, intent, mimeType,
                            null, null, 0, mStartFlags, profilerInfo, null, mUserId);
                res = result.result;
            } else {
                res = mAm.startActivityAsUser(null, null, intent, mimeType,
                        null, null, 0, mStartFlags, profilerInfo, null, mUserId);
            }
            final long endTime = SystemClock.uptimeMillis();
            //...
             if (mWaitOption && launched) {
                if (result == null) {
                    result = new IActivityManager.WaitResult();
                    result.who = intent.getComponent();
                }
                System.out.println("Status: " + (result.timeout ? "timeout" : "ok"));
                if (result.who != null) {
                    System.out.println("Activity: " + result.who.flattenToShortString());
                }
                //下面代码得知result中包含thisTime、totalTime、WaitTime
                if (result.thisTime >= 0) {
                    System.out.println("ThisTime: " + result.thisTime);
                }
                if (result.totalTime >= 0) {
                    System.out.println("TotalTime: " + result.totalTime);
                }
                System.out.println("WaitTime: " + (endTime-startTime));
                System.out.println("Complete");
            }
    }

发现最终跨Binder调用ActivityManagerService.startActivityAndWait() 接口

\frameworks\base\services\core\java\com\android\server\am\ActivityManagerService.java

public final class ActivityManagerService extends ActivityManagerNative
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    @Override
    public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
        enforceNotIsolatedCaller("startActivityAndWait");
        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
                false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
        WaitResult res = new WaitResult();
        // TODO: Switch to user app stacks here.
        mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, res, null,
                options, false, userId, null, null);
        return res;
    }
}

这个接口返回的结果包含上面打印的ThisTime、TotalTime时间.

可参考:
Android 中如何计算 App 的启动时间?


关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 发布了文章 · 2020-11-25

App启动优化-一顿操作猛如虎

前言

++一个应用App的启动速度能够影响用户的首次体验,用户希望应用能够及时响应并快速加载。启动时间过长的应用不能满足这个期望,并且可能会令用户失望。这种糟糕的体验可能会导致用户在应用商店针对您的应用给出很低的评分,甚至完全弃用您的应用。++

==本次主要内容包括:==

针对App启动优化我们做了哪些工作?

1、App启动优化方向:视觉体验优化

2、App启动优化方向:代码逻辑优化

一、App启动优化方向:视觉体验优化

App启动时白屏问题

App启动阶段 :

  1. 加载并启动应用程序。
  2. 启动后立即显示应用程序空白的启动窗口。
  3. 创建应用程序进程。

启动白屏的问题就是在1~2阶段,因为App应用启动都会先进入一个闪屏页(SplashActivity) 来展示应用信息。我们可以通过设置启动窗口的主题来优化视觉上出现的启动白屏的问题。

1、默认主题

默认情况对App不做处理既设置了默认主题,App启动初始化时会出现如下启动时显示白屏的情况,如下图:

image

2、透明主题

为了解决启动窗口白屏问题,通过设置启动页为透明主题来解,,虽然白屏没了,但是我们的App似乎是变迟钝了,仔细观察一下,点击App启动图标后,App似乎是顿了一下,然后加载了我们的欢迎页面,有点像ANR,只不过很短暂,所以用户体验还是不佳,现象如下图:

<style name="NormalSplash" parent="AppTheme">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowIsTranslucent">true</item>
</style>

image

3、设置闪屏图片主题
<style name="NormalSplash" parent="AppTheme">
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
        <item name="android:windowBackground">@drawable/welcome_layler_drawable</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowFullscreen">true</item>
        <!--显示虚拟按键,并腾出空间-->
        <item name="android:windowDrawsSystemBarBackgrounds">false</item>
    </style>

welcome_layler_drawable.xml源码:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/welcome_background"
        android:drawable="@drawable/icon_splash_bg" />
    <item
        android:bottom="@dimen/dp_16"
        android:gravity="center">
        <bitmap
            android:gravity="center_horizontal"
            android:data-original="@drawable/icon_splash_word" />
    </item>
    <item
        android:bottom="@dimen/dp_41"
        android:gravity="bottom">
        <bitmap
            android:gravity="center_horizontal|bottom"
            android:data-original="@drawable/icon_splash" />
    </item>
</layer-list>

image

二、App启动优化方向:代码逻辑优化

1、Application优化:

Application作为应用程序的整个初始化配置入口,有很多第三方组件(包括App应用本身)都在 Application 中做初始化操作,在Application中完成各种初始化操作和复杂的逻辑就会影响到应用的启动性能

过多的初始化任务,考虑以下优化方案:

  1. 考虑异步初始化三方组件,不阻塞主线程;
  2. 延迟部分三方组件的初始化;

优化方案如下:

组件放到子线程中初始化:

new Thread(new Runnable() {
            @Override
            public void run() {
                setThreadPriority(THREAD_PRIORITY_BACKGROUND);
                initARouter();
                CacheManager.getInstance().initialize(getInstance());
                ConnectionManager.getInstance().initialize();
                initImageFactory();
                initBJY();
                initGrowingIO();
                initUmeng();
                initBugly();
                initOkHttp();
                initSobot();
                setRxJavaErrorHandler();
            }
        }).start();

将需要在主线程中初始化但是可以不用立使用的控件功能延迟加载:

handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //延迟初始化组件
            }
        }, 3000);
==注意:==
并不是每一个组件的初始化以及操作都可以异步或延迟;是否可以取决组件的调用关系以及自己项目具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。
//子线程初始化第三方组件
//建议延迟初始化,可以发现是否影响其它功能,或者是崩溃!
Thread.sleep(5000);

2、闪屏Activity优化:

Activity的UI层级优化:

优化前UI布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/icon_splash_bg">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:data-original="@mipmap/icon_splash_word"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:paddingBottom="160dp"
        />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:data-original="@mipmap/icon_splash"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="@dimen/dp_41"
        />

    <com.pxwx.student.modulecore.widget.TouchRelativeLayout
        android:id="@+id/rl_adsRl"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal|top"
        android:orientation="vertical" >

        <ImageView
            android:id="@+id/iv_SplashAd"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@null"
            android:contentDescription="@null"
            android:scaleType="fitXY"
            android:visibility="gone" />
    </com.pxwx.student.modulecore.widget.TouchRelativeLayout>
    <TextView
        android:id="@+id/tv_adjump"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ad_jump_selector"
        android:gravity="center_vertical|center_horizontal"
        android:layout_alignParentRight="true"
        android:layout_marginRight="@dimen/dp_18"
        android:layout_marginTop="@dimen/dp_30"
        android:paddingBottom="@dimen/dp_5"
        android:paddingLeft="@dimen/dp_11"
        android:paddingRight="@dimen/dp_11"
        android:paddingTop="@dimen/dp_5"
        android:text="跳过 3"
        android:textColor="@color/white"
        android:textSize="@dimen/font_15"
        android:visibility="gone"
        />
</RelativeLayout>

简化后:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/welcome_layler_drawable">

    <ViewStub
        android:id="@+id/vs"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/layout_stub_avd" />

</FrameLayout>

ViewStub 初始化延迟

针对项目中的启屏广告业务,通过ViewStub延后他们的初始化,在需要显示的时候通过ViewStub的inflate显示真正的view,优化如下

<ViewStub
    android:id="@+id/vs"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout="@layout/layout_stub_avd" />

开屏广告业务布局抽取

layout_stub_avd.xml

<?xml version="1.0" encoding="utf-8"?>
<!--启屏页广告视图-->
<com.pxwx.student.modulecore.widget.TouchRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_adsRl"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_SplashAd"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@null"
        android:contentDescription="@null"
        android:scaleType="fitXY" />

    <TextView
        android:id="@+id/tv_adjump"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginTop="@dimen/dp_30"
        android:layout_marginRight="@dimen/dp_18"
        android:background="@drawable/ad_jump_selector"
        android:gravity="center"
        android:paddingLeft="@dimen/dp_11"
        android:paddingTop="@dimen/dp_5"
        android:paddingRight="@dimen/dp_11"
        android:paddingBottom="@dimen/dp_5"
        android:text="跳过 3"
        android:textColor="@color/white"
        android:textSize="@dimen/font_15" />
</com.pxwx.student.modulecore.widget.TouchRelativeLayout>

然后在代码中需要显示webview时进行inflate:

/**
 * 懒加载广告视图
 */
private void showAvd() {
    viewStub = findViewById(R.id.vs);
    if (viewStub != null) {
        viewStub.inflate();
        mAdRl = findViewById(R.id.rl_adsRl);
        mAdImage = findViewById(R.id.iv_SplashAd);
        mAdJump = findViewById(R.id.tv_adjump);
    }
}

优化点:

  1. 废弃之前的启屏页UI布局,直接使用先前自定义好的welcome_layler_drawable作为启屏页背景
  2. 将开屏广告Ui抽取分离
  3. 懒加载广告视图

onCreate业务逻辑优化:

  1. 减少广告等业务逻辑时间这里属于业务逻辑的优化。
  2. onCreate中针对广告业务的初始化业务优化,异步下载图片,等下次启动控制展示

总结

通用应用启动加速套路

  1. 利用主题快速显示界面;
  2. 异步初始化组件;
  3. 梳理业务逻辑,延迟初始化组件、操作;
  4. 正确使用线程;
  5. 去掉无用代码、重复逻辑等。

问题:

1、启动速度的衡量指标启动时间如何计算?

2、为什么启动会有白屏?

3、为什么这样优化是有效的?


关注我的技术公众号

image

查看原文

赞 0 收藏 0 评论 0

heiyl 关注了专栏 · 2020-11-24

2020淘宝天猫双11背后的技术总结

阿里巴巴淘系技术部打造了全球领先的线上新零售技术平台。2020年双11,作为核心技术团队,我们又一次完成第 12 次购物狂欢节的大考。淘系技术部将和大家分享一系列双11大战背后的技术实践与思考沉淀,内容涵盖研发提效、前端性能、云原生系统、全链路压测、语音识别、3D技术等多方技术栈&热门业务单元,来自淘系各技术团队的精华输出,请大家持续关注。

关注 1368

heiyl 关注了专栏 · 2020-11-24

大前端

学习 分享

关注 7307

认证与成就

  • 获得 0 次点赞
  • 获得 0 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 0 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-11-24
个人主页被 397 人浏览