Giter Site home page Giter Site logo

android-day-issues's People

Contributors

lvkang-insist avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

android-day-issues's Issues

如何开展优化类工作

明确的优化目标实际上就是一个从定性到定量的转变。首先要确定优化哪些东西,然后在去确定到底要优化多少。

例如耗电优化,定性就是 App 耗电量高,需要优化,而定量就是到底要优化成什么样子,例如从之前的每小时 10% 优化到 6%。也就是需要量化目标。

而优化的方面也有很多,例如下面:

  • 耗电量优化
  • 过度绘制的优化
  • 内存优化
  • CPU 占用率的优化
  • 算法策略的优化

定位关键问题

其实我们项目中可优化的地方有很多,首先需要确定的就是哪些问题占比最高,优化了之后的效果最大,然后就从这方面开始进行优化。

所以需要优先解决占比最高的问题。

二八定律

  • 80 % 的错误通常源自于 20% 的问题
    • 优化前期化 20 % 的精力就能解决 80 % 的问题。
    • 优化后期则相反

完善的指标监控

从开始优化的时间,到现在的时间做一个表,然后进行对比,看看优化的效果到底如何。

线上灰度,优化完一个版本之后,不要直接进行全量更新,因为你无法预知会出现哪些 bug,所以可以进行灰度测试,先 20% 的灰度看一下效果,如果优化的效果还不错就可以加直接更新了。

项目收益

  • 转换成有概念的指标
    • 页面加载时间减少 500ms
    • 内存消耗降低 50Mb
    • CPU 占用率从 12 %降低到 3%
    • 项目成本由 8元 / 单降低到 3元/ 单,(评价 10000 单/每天)

2023-2-15:data 类和 密封类

data 类

  • data 类不可被继承,不可以实现接口

  • 默认提供了 equals,toString,copy 等方法,如果类中继承了这些方法,则不会自动生成

  • 构造函数至少需要一个参数

  • 所有构造函数的参数必须使用 val 或者 var 标记

密封类

密封类是一种特殊的抽象类,不能被实例化,构造器私有,不能在外部文件中继承密封类。通过反编译就可以看到本质就是个抽象类,并且构造器是私有的

  • 概念:特殊的抽象类,子类可数,子类封闭
  • 构造器私有:无法在外部文件中继承密封类
  • 子类定义:必须定义在当前文件中
  • 与枚举对比:枚举 实例可数,密封类 子类可数

密封类一般用于表示各种状态,不同的状态可以携带不同的参数以及行为来记录各种信息。

2023-2-15:观察者模式

观察者模式又称之为发布-订阅模式,发布者负责发送,订阅者负责消费,它定义了对象之间的一种一对多的依赖关系,当一个对象发生改变后,所有依赖他的对象都会收到通知。

优点:解除观察者和发布者的耦合,让耦合的双方都依赖于抽象,而不是依赖于具体

​ 易于扩展,新增观察者非常方便,无需修改原有代码

缺点:依赖关系没有完全解除,双方依然依赖于抽象

​ 观察者如果太多,调试会比较复杂,并且一般通知观察者的时候是顺序执行的,如果一个观察者卡顿,会影响整体的效率,这种情况下一般可以采用异步来实现

​ 可能会引起多余的数据通知

2023-2-15:Kotlin reified 关键字

reified 通常和 inline 和 泛型一起出现,总所周知泛型的实现方式是通过类型擦除来实现的,在运行的时候不会带有任何的类型信息。

当调用了内联函数后,该函数就会在调用的地方被执行,如果在被调用的地方执行,会将泛型替换为具体的类型,在配合 reified 关键字,就可以在内联函数中获取的泛型的类型而且不会报错。

2023-2-13:LiveData 原理

[LiveData 原理](https://github.com/LvKang-insist/note-instance/blob/master/android/%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93%E4%BD%BF%E7%94%A8/JetPack/LiveData%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90.md)

LiveData 是一个可被观察的数据持有类,和其他的不同的是,LiveData 具有感知生命周期的能力。

LiveData 通过添加 Observer 来进行观察,添加的时候会被LifecycleBoundObserver 进行包装,这样就可以感知生命周期了,接着判断是否添加过,如果添加过直接退出,最后给包装类注册 Lifecycle,使其可以感知生命周期。

在 LifecycleBoundObserver 中,监听生命周期改变,如果发生改变,则根据条件修改活跃数量,然后进行分发,分发的时候,遍历已注册的所有观察者,如果不是活跃的,或者数据不是最新的(和之前的版本进行对比),则不会分发,否则就进行回调,将数据发送出去。

LiveData 在发送数据的时候有两种方式,直接 set 和 post,分别对应同步和异步发送。异步的话就是扔到主线程在进行发送,在发送的时候首先是版本(version+1),然后同样也是遍历所有的观察者,判断活跃状态和版本,满足条件就会分发出去。

除此之外,LiveData 还有别的扩展方法,例如 map ,switchMap ,和并多个 livedata 等方法。

问题1,粘性数据:在没有观察着的时候发生数据,到添加观察者之后,如果生命周期活跃,这个数据就会重新分发,解决可通过反射来修改版本的值。问题2:数据倒灌,LiveData 在 ViewModel 中,当Activity 重建的时候,观察者会被 remove 掉,重建后会重新添加观察者,此时新的观察着会接收到之前的数据,可以通过反射解决。

使用 LiveData 的好处就是关联的生命周期,可以很大程度避免内存泄露,并且无需手动更新界面,直接在回调中更新即可,还有就是页面始终显示的是最新数据,应为会在活跃的时候接收到最新的数据,最后就是可以在单例中扩展 LiveData,方便在应用**享这个 livedata。

2022-8-31:Activity 生命周期

Activity 生命周期

  • onCreate:正在被创建
  • onRestart:正在重新启动
  • onStart:正在启动,这个时候已经可见,但是无法进行交互
  • onResume:已经可见,处于前台,可交互
  • onPasue:正在停止
  • onStop:即将停止,可以进行重量级的回收工作
  • onDestory:即将被销毁,可用于释放资源

其他问题:

  • 第一次启动:onCreate -> onStart -> onResume

  • 打开新的 Activity / 返回桌面:A : onCreate -> onStart -> onResume -> onPause ; B:onCreate ->onStart - onResume ; A: onStop ; 返回 A时 B:onPause ,A:onRestart -> onStart -> onResume ; B:onStop -> onDestory

  • 横竖屏切换:

    onPause -> onStop -> 调用 onSaveInstanceState 保存状态,接着 onDestory

    重建时 onCreate -> onStart-> onRestoreInstanceState -> onResulme

    可在 配置文件中进行 configChanges 配置,切屏后不会重建 Activity

  • 按下返回键 :onPause -> onStop -> onDestory

2023-2-13:ViewModel 原理

[ViewModel 原理](https://github.com/LvKang-insist/note-instance/blob/master/android/%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93%E4%BD%BF%E7%94%A8/JetPack/ViewModel%20%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90.md)

ViewModel 是实现 MVVM 不可缺少的一环,其主要就是注重生命周期的方式来管理页面相关数据,当页面因为配置改变而重建时,ViewModel 就可以在重建的过程中恢复数据。

ViewModel 的创建是通过 ViewModeProvider.get() 来完成的,在创建的时候,会传入 ViewModelStoreOwner 和 Frctory ,前者是 ViweModel 的宿主,内部维护了一个 ViewMode 的映射表,当创建 ViewModel 的时候,就会从映射表里面获取,获取到返回,获取不到则根据 Factory 反射创建一个ViewModel。

通过 by viewmodels 创建的流程和上面一样,也是通过 ViewModelProvider 创建的。

至于如何在页面重建时恢复数据,是因为在获取 ViewModelStore 时,会从配置文件中查找是否有 ViewModelStore,如果有,则拿出来直接使用,没有的话就会创建一个新的。

当页面重建的时候,ViewModelStore 会被进行包装,保存在 ActivityClientRecord 中,在重建完成后会被取出。

Fragment 内部也是维护了一个映射表,只不过是由 fragmentManager 管理的。

2022-9-19:MVC,MVP,MVVM,MVI 的区别

  • MVC

    M 表示数据层,V 表示 View ,C 表示控制层(Activity 和 Fragment) 的子类

    使用的时候,V 和 C 是写在一起的,在页面中直接调用 M 获取数据,然后再刷新 View

    这种模式好处就是 model 和 页面彻底解耦,但是 控制层和 VIew 并没有解耦,在页面的代码逻辑中就容易混淆。

  • MVP

    M:数据层,V:即 Activity / Fragment , P:中介(Presenter)

    相比于 MVC 多了一个 P 层,数据和View不在进行直接通讯,使用 P 进行分离,获取数据时通过 P 去获取数据,然后 P 层再转交给 V 。

    MVP 整个过程都是通过接口的调用来完成的,V 和 P 都必须实现对应的接口。

    好处就是实现了 Model 和 View 的完全解耦,彻底解决了 MVC 中 View 和 Controller 分不清楚的问题。

    坏处就是无法感知页面的生命周期,可能造成内存泄露等,需要使用弱引用 + 动态代理的方式来判断 V 是否被销毁

    还有就是随着业务的逻辑增加,页面就会非常复杂,UI 改变也多,这就会造成接口非常庞大。

  • MVVM

    M :数据层,包括数据,网络数据获取以及耗时操作的处理

    V:Activity/Fragment ,负责一些状态的初始化以及 UI 更新

    VM:负责完成 View 和 Model 之间的交互,需要继承 ViewModel ,可以保证页面的数据不会丢失

    相比于 MVVM ,取消了复杂的接口,取而代之的是 DataBinding,或者是 LiveData,通过 DataBinding 将数据绑定到 ui 上,数据发生变化时会自动更新,也可以采用 LiveData,在页面中监听数据,然后进行刷新

    使用 DataBindign 需要数据和视图的双向绑定,就会导致出现问题不好定位,布局很难进行复用等,以及别的问题,所以大多数都是采用 LiveData 来进行操作。

    不足之处,在于需要定义 livedata 的模版代码,一个可见,一个不可见,业务太多需要定义大量的 Livedata。

  • MVI

    和 MVVM 很相似,只不过更加强调数据的单向流动和唯一的数据源

    M:主要指的是 UI 的状态(State),例如页面加载状态,控件的显示状态等

    V:fragment / Activity,通过订阅 Model 的状态变化来进行页面的刷新

    I:Intent,用户的任何操作都可以包装为 Intent 发送给 Model 层进行获取。

    用户已 Inent 的方式通知 Model,Model 根据对应的 Intent 进行数据处理,然后更新 State,View 接收到 State 的变化后更新 UI

    相比于 MVVM ,就是对页面状态进行了统一管理,只需要订阅 ViewState 就可以获取获取所有状态,减少了不少的模版代码

    ViewModel 只需要根据 Intent 来进行对应的逻辑处理,然后再通过修改ViewState通知改变即可。

    缺点,所有的调用操作最终都活转换成 Intent 和 State,所以页面如果复杂就会导致容易膨胀,state 是不可变的,每次更新的时候都需要用新的对象来代替老的对象,会造成一定的内存开销。

2022-3-21:接口和抽象类的区别

抽象类与接口的区别

抽象类和接口都是为了抽取共性而存在的

  • 抽象类无法被实例化,只能被继承,抽象类是用来创建继承层级里子类的模板,抽象类可以对方法提供默认的实现
  • 抽象类除过不能被实例化和抽象方法以外,其他的和普通类都差不多
  • 接口也无法被实例化,如果一个类实现了一个接口,那么它就必须实现所有的方法,这就像是契约模式
  • 一个类可以实现多个接口,但是只能继承一个抽象类
  • 接口中的变量全部是 final 不可修改,即接口本身只能定义常量。接口是更高级更纯粹的抽象类,体现了对修改关闭,对扩展开放
  • 接口在 JDK8 之后可以有不用实现的方法

2020-3-8:如何停止一个线程

  • 官方停止线程的方法被废弃了,所以不能直接简单的停止线程。

  • 所以就需要设计一个可以随时被中断而取消的任务线程。

为什么不能简单的停止一个线程

栗子:两个线程,线程1去访问一个带锁的方法去读取文件,然后获取到了锁,这个时候线程2也要去读写文件,但是只能被阻塞了。

此时如果停止了线程1,那么就会导致锁无法被释放,从而导致线程2一直在阻塞。甚至说如果线程比较多,就可能会出现死锁的问题。

需要解决的:

  • Thread1 被停止后,必须立即释放锁
  • Thread2 如果拿到了锁,但是内存状态异常。因为之前的 Thread 1没有读完文件就停止了。

协作的任务执行模式

线程在设计的时候是和任务进行绑定的,任务执行完了,那么线程自然也就自己停止了。我们想让线程结束本质上就是让任务结束,而不是让线程结束。如果让任务结束了,那么线程自然有机会去释放锁等一系列的操作,自然也就不会引起其他问题了。所以我们需要:

  • 通知目标线程自行结束,而不是强制停止

  • 目标线程应当具备处理中断的能力

  • 中断方式

    • Interrupt 的原生支持

      //目标线程
      Thread thread = new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  Thread.sleep(3000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
                  //清理
              }
          }
      });
      //中断宣传
      thread.interrupt();

      在有些情况下,中断线程也是不好使的,如下:

      Thread thread = new Thread(new Runnable() {
          @Override
          public void run() {
              for (int i = 0; i < 10000; i++) {
                  System.out.println(i);
              }
          }
      });
      thread.start();
      thread.interrupt();//中断线程

      这种情况下中断线程是没有用的,因为 Thread 不支持这种。如果要可以进行中断,就只能是如下写法了:

      class InterruptThread extends Thread {
           @Override
           public void run() {
               for (int i = 0; i < 10000; i++) {
                   System.out.println(i);
                   if (interrupted()) {
                       break;
                   }
               }
           }
       }

      每次去判断一下线程 有没有中断,如果中断了就结束任务,清理资源就行了

      interrupted() 与 isInterrupted

      • interrupted() 是静态方法,获取当前线程的中断状态,并清空当前运行的线程到的状态
        • 中断状态调用后清空,重复调用后续返回false
      • isInterrupted() 是非静态方法,获取该线程的中断状态,不清空,只是一个简单的读取状态
        • 调用的线程对象对应的线程
        • 可重复调用,中断清空前一直返回true
    • boolean 标志位

      static class InterruptThread extends Thread {
          volatile boolean isStopped = false;
      
          @Override
          public void run() {
              for (int i = 0; i < 10000; i++) {
                  System.out.println(i);
                  if (isStopped) {
                      break;
                  }
              }
          }
      }

      需要注意的是标志位必须要使用 volatile 关键字,否则就会存在可见性的问题。

    • 两者对比

      interrupt boolean 标志位
      系统方法(selep)
      使用 JNI
      加锁
      触发方式 抛异常 布尔值判断,也可抛异常
      • 如果需要支持使用系统方法时进行中断,就可以使用 interrupt。(功能性)]
      • 其他情况使用 boolean 标志位(性能)

总结

  • 为什么线程不应该被直接 stop

    因为涉及到线程执行完后的资源清理工作。

  • 线程内置的中断机制的使用和原理

  • 通过 volatile boolean 标志位通知线程停止

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.