答应我,我踩过的坑你别再踩了好嘛,那些年社招的坑坑洼洼

有些话、适合烂在心里 提交于 2019-12-04 21:06:30

回想起前年左右,自己去社招的时候,一连串下来问了好多现在都是历历在目。回想起以前才觉得纸上得来终觉浅,绝知此事要躬行

所有的面试题答案并不是百分百的标准,要靠你自己的感悟和有自己的想法,才能独树一帜脱颖而出的。所有仅供参考
答应我,我踩过的坑你别再踩了好嘛,那些年社招的坑坑洼洼
所有的都在这个PDF中有所汇总,983页花了几十个小时整理出来的。还是比较全面的有Android,Java小知识,到性能优化.线程.View.OpenCV.NDK.大厂面试,算法等等,大家可以联系我看看对自身有没有用

(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
可以联系我获取完整PDF
(VX:mm14525201314)

1丶如何进行单元测试,如何保证 App 稳定 ?

要测试 Android 应用程序,通常会创建以下类型自动单元测试
  • 本地测试: 只在本地机器 JVM 上运行,以最小化执行时间,这种单元测试不依赖于 Android 框架,或者即使有依赖,也很方便使用模拟框架来模拟依赖,以达到隔离 Android 依赖的目的,模拟框架如Google 推荐的 Mockito;
  • 检测测试: 真机或模拟器上运行的单元测试,由于需要跑到设备上,比较慢,这些测试可以访问仪器(Android 系统)信息,比如被测应用程序的上下文,一般地,依赖不太方便通过模拟框架模拟时采用这种方式;

注意: 单元测试不适合测试复杂的 UI 交互事件

  • App 的稳定主要决定于整体的系统架构设计,同时也不可忽略代码编程的细节规范,正所谓“千里之堤,溃于蚁穴”,一旦考虑不周,看似无关紧要的代码片段可能会带来整体软件系统的崩溃,所以上线之前除了自己 本地化测试之外还需要进行 y Monkey 压力测试
  • 少部分面试官可能会延伸,如 Gradle自动化测试、机型适配测试等

    2 、 Android 中如何查看一个对象的回收情况 ?

    首先要了解 Java 四种引用类型的场景和使用(强引用、软引用、弱引用、虛引用)

  • 举个场景例子: SoftReference 对象是用来保存软引用的,但它同时也是一个 Java 对象,所以当软引用对象被回收之后,虽然这个 SoftReference 对象的 get 方法返回null,但 SoftReference 对象本身并不是 null,而此时这个 SoftReference 对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference 对象带来的内存泄露
  • 因此,Java 提供 ReferenceQueue 来处理引用对象的回收情况。当 SoftReference 所引用的对象被 GC 后,JVM 会先将 softReference 对象添加到ReferenceQueue 这个队列中。当我们调用 ReferenceQueue 的 的 poll() 方法,如果这个队列中不是空队列,那么将返回并移除前面添加的那个Reference 对象

    public static void main(String[] args) throws InterruptedException {
    
          //假设当前JVM内存只有8m
         Person person = new Person( "/K=" );
         ReferenceQueue Person> queue = new ReferenceQueue>( );
         Sof tReference Person> softReference = new SoftReference<Person>(person, queue );
    
         реrѕоn = null;// 去掉强引用, new Person ( "/K=" );的这个对象只有软引用了
    
         Person anotherPerson = new Person( "/L=" )//没有足够的空间同事保留两个Person对象,所以触发GC机制
         Thread.sleep(1000);
    
         Sys tem. err . println( "软引用的对象 ------" > softReference.get( ));
    
         Reference softPollRef = queue . poll( );
         if (softPollRef != null) f
              System.err.println( " SoftReference对象中保存的软引用对象已经被GC,准备清理SoftReference对象");
                //清理softReference
         }
    }

    3丶压缩APK大小

    一个完整 APK 包含以下目录(将 APK 文件拖到 Android Studio):
  • META-INF/: 包含 CERT.SFCERT.RSA 签名文件以及 MANIFEST.MF 清单文件。
  • assets/: 包含应用可以使用 AssetManager 对象检索的应用资源。
  • res/: 包含未编译到的资源 resources.arsc
  • lib/: 包含特定于处理器软件层的编译代码。该目录包含了每种平台的子目录,像 armeabi armeabi-v7aarm64-v8ax86x86_64 ,和mips
  • resources.arsc 包含已编译的资源。该文件包含res/values/ 文件夹所有配置中的 XML 内容。打包工具提取此 XML 内容,将其编译为二进制格式,并将内容归档。此内容包括语言字符串和样式,以及直接包含在resources.arsc8文件中的内容路径 ,例如布局文件和图像。
  • classes.dex:包含以 Dalvik / ART 虚拟机可理解的X DEX 文件格式编译的类。
  • AndroidManifest.xml 包含核心 Android 清单文件。该文件列出应用程序的名称,版本,访问权限和引用的库文件.该文件使用 Android 的二进制XML 格式。
    答应我,我踩过的坑你别再踩了好嘛,那些年社招的坑坑洼洼
  • libclass.dex 和 res 占用了超过 90%的空间,所以这三块是优化 Apk 大小的重点(实际情况不唯一)
减少 res ,压缩图文文件

图片文件压缩是针对 jpgpng 格式的图片。我们通常会放置多套不同分辨率的图片以适配不同的屏幕,这里可以进行适当的删减。在实际使用中,只保留一到两套就足够了(保留一套的话建议保留xxhdpi,两套的话就加上 hdpi),然后再对剩余的图片进行压缩(jpg 采用优图压缩,png 尝试采用pngquant 压缩)

减少 dex
  • 添加资源混淆
    buildTypes {
      release  {
            shrinkResources true
            minifyEnabled  true
            proguardFiles  getDefaultProguardFile("proguard-android. txt"),’proguard-rules.pro’
      }
    }
  • shrinkResources 为 true 表示移除未引用资源,和代码压缩协同工作。
  • minifyEnabled 为 true 表示通过 ProGuard 启用代码压缩,配合 proguardFiles 的配置对代码进行混淆并移除未使用的代码。
  • 代码混淆在压缩 apk 的同时,也提升了安全性。
    减少 lib
  • 由于引用了很多第三方库,lib 文件夹占用的空间通常都很大,特别是有 so 库的情况下。很多 so 库会同时引入 armeabiarmeabi-v7ax86 这几种类型,这里可以只保留 armeabiarmeabi-v7a 的其中一个就可以了,实际上微信等主流 app 都是这么做的。
  • 只需在 build.gradle 直接配置即可,NDK 配置同理
    defaultConfig {
      ndk  {
             abiFilters 'armeabi'
      }
    }

    4丶插件化原理分析

    插件化是指将 APK 分为 宿主插件的部分。把需要实现的模块或功能当做一个独立的提取出来,在 APP 运行时,我们可以动态的 载入或者 替换插件部分,减少 宿主的规模

  • 宿主: 就是当前运行的 APP。
  • 插件: 相对于插件化技术来说,就是要加载运行的apk 类文件

热修复则是从修复 bug 的角度出发,强调的是在不需要二次安装应用的前提下修复已知的 bug。

类加载机制:

Android 中常用的两种类加载器, DexClassLoaderPathClassLoader,它们都继承于BaseDexClassLoader,两者 区别在于PathClassLoader 只能加载 内部存储目录dex/jar/apk 文件。DexClassLoader 支持加载 指定目录(不限于内部)的 dex/jar/apk 文件

插件通信:

通过给插件 apk 生成相应的 DexClassLoader 便可以访问其中的类,可分为单 DexClassLoader 和多DexClassLoader 两种结构。

  • 若使用多 ClassLoader 机制,主工程引用插件中类需要先通过插件的 ClassLoader 加载该类再通过 反
    射调用其方法。插件化框架一般会通过统一的入口去管理对各个插件中类的访问,并且做一定的限制。
  • 若使用单 ClassLoader 机制,主工程则可以 直接通过类名去访问插件中的类。该方式有个弊端,若两个不同的插件工程引用了一个库的不同版本,则程序可能会出错。
    资源加载:

    原理在于通过反射将插件 apk 的路径加入AssetManager 中并创建 Resource 对象加载资源,有两种处理方式:

  • 合并式: addAssetPath 时加入所有插件和主工程的路径;由于 AssetManager 中加入了所有插件和主工程的路径,因此生成的Resource 可以同时访问插件和主工程的资源。但是由于主工程和各个插件都是独立编译的,生成的资源 id 会存在相同的情况,在访问时会产生资源冲突。
  • 独立式: 各个插件只添加自己 apk 路径,各个插件的资源是互相隔离的,不过如果想要实现资源的共享,必须拿到对应的 Resource对象。

    5 、组件化原理

    引入组件化的原因: 项目随着需求的增加规模变得越来越大,规模的增大导致了各种业务错中复杂的交织在一起,每个业务模块之间,代码没有约束,带来了代码边界的模糊,代码冲突时有发生, 更改一个小问题可能引起一些新的问题, 牵一发而动全身,增加一个新需求,需要熟悉相关的代码逻辑,增加开发时间

  • 避免重复造轮子,可以节省开发和维护的成本。
  • 可以通过组件和模块为业务基准合理地安排人力,提高开发效率。
  • 不同的项目可以共用一个组件或模块,确保整体技术方案的统一性。
  • 为未来插件化共用同一套底层模型做准备。

组件化开发流程就是把一个功能完整的 App 或模块拆分成多个子模块( Module ),每个子模块可以 独立编译运行,也可以任意组合成另一个新的 App 或模块,每个模块即不相互依赖但又可以相互交互,但是最终发布的时候是将这些组件合并统一成一个 apk,遇到某些特殊情况甚至可以升级或者

6、跨组件通信

跨组件通信场景:
  • 第一种是组件之间的页面跳转 (Activity 到Activity, Fragment 到 Fragment, Activity 到Fragment, Fragment 到 Activity) 以及跳转时的数据传递 (基础数据类型和可序列化的自定义类型)
  • 第二种是组件之间的自定义类和自定义方法的调用(组件向外提供服务)
    跨组件通信方案分析:
  • 第一种 组件之间的页面跳转实现简单,跳转时想传递不同类型的数据提供有相应的 API 即可。
  • 第二种组件之间的自定义类和 自定义方法的调用要稍微复杂点,需要 ARouter 配合架构中的 公共服务(CommonService) 实现:
    • 提供服务的业务模块: 在公共服务(CommonService) 中声明 Service接口 (含有需要被调用的自定义方法), 然后在自己的模块中实现这个 Service 接口, 再通过 ARouter API 暴露实现类。
    • 使用服务的业务模块: 通过 ARouter 的 API 拿到这个Service 接口(多态持有, 实际持有实现类), 即可调用 Service 接口中声明的自定义方法, 这样就可以达到模块之间的交互。
  • 此外,可以使用 AndroidEventBus 其独有的Tag, 可以在开发时更容易定位发送事件和接受事件的代码, 如果以组件名来作为 Tag 的前缀进行分组, 也可以更好的统一管理和查看每个组件的事件, 当然也不建议大家过多使用 EventBus
如何管理过多的路由表?
  • RouterHub 存在于基础库, 可以被看作是所有组件都需要遵守的通讯协议, 里面不仅可以放路由地址常量, 还可以放跨组件传递数据时命名的各种 Key 值,再配以适当注释, 任何组件开发人员不需要事先沟通只要依赖了这个协议, 就知道了各自该怎样协同工作, 既提高了效率又降低了出错风险, 约定的东西自然要比口头上说强。
  • Tips: 如果您觉得把每个路由地址都写在基础库的RouterHub 中, 太麻烦了, 也可以在每个组件内部建立一个私有 RouterHub, 将不需要跨组件的路由地址放入私有 RouterHub 中管理, 只将需要跨组件的路由地址放入基础库的公有 RouterHub 中管理, 如果您不需要集中管理所有路由地址的话, 这也是比较推荐的一种方式。
    ARouter 路由原理:

    ARouter维护了一个路由表 Warehouse,其中保存着全部的模块跳转关系,ARouter 路由跳转实际上还是调用了 startActivity 的跳转,使用了原生的Framework 机制,只是通过 apt 注解的形式制造出跳转规则,并人为地拦截跳转和设置跳转条件

    7 、 Hook 以及插桩技术

    Hook 是一种用于 改变 API执行结果的技术,能够将系统的API 函数执行 重定向(应用的 触发事件后台逻辑处理是根据事件流程一步步地向下执行。而 Hook 的意思,就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时,处理一些自己特定的事件,例如逆向破解 App)
    答应我,我踩过的坑你别再踩了好嘛,那些年社招的坑坑洼洼

    Android 中的 Hook 机制,大致有两个方式:
  • 要 root 权限,直接 Hook 系统,可以干掉所有的App。
  • 无 root 权限,但是只能 Hook 自身 app,对系统其它 App 无能为力。

插桩是以静态的方式修改第三方的代码,也就是从编译阶段,对源代码(中间代码)进行编译,而后重新打包,是静态的篡改; 而 Hook则不需要再编译阶段修改第三方的源码或中间代码,是在运行时通过反射的方式修改调用,是一种 动态的篡改

8 、说下 Measurepec 这个类

作用: 通过宽测量值 widthMeasureSpec 和高测量值heightMeasureSpec 决定 View 的大小

组成: 一个 32 位 int 值,高 2 位代表 SpecMode(测量模式),低 30 位代表 SpecSize( 某种测量模式下的规格大小)。

三种模式:

  • UNSPECIFIED: 父容器不对 View 有任何限制,要多大有多大。常用于系统内部。
  • EXACTLY(精确模式): 父视图为子视图指定一个确切的尺寸 SpecSize。对应 LyaoutParams 中的match_parent 或具体数值。
  • AT_MOST(最大模式): 父容器为子视图指定一个最大尺寸 SpecSize,View 的大小不能大于这个值。对应LayoutParams 中的 wrap_content。

9丶图片加载库Glide

图片加载库:Fresco 丶Glide 、o Picasso 等

Glide 的设计:
  • Glide 的生命周期绑定: 可以控制图片的加载状态与当前页面的生命周期同步,使整个加载过程随着页面的状态而启动/恢复,停止,销毁
  • Glide 的缓存设计: 通过(三级缓存,Lru 算法,Bitmap 复用)对 Resource 进行缓存设计
  • Glide 的完整加载过程: 采用 Engine 引擎类暴露了一系列方法供 Request 操作

    10 、区别 Animation 和 和 Animator

  • 动画的种类: 前者只有 透明度, 旋转, 平移, 伸缩 4 种属性,而对于后者,只要是该控件的属性,且有 setter 该属性的方法就都可以对该属性执行一种 动态变化的效果。
  • 可操作的对象: 前者只能对 I UI 组件执行动画,但属性动画几乎可以对任何对象执行动画(不管它是否显示在屏幕上)。
  • 动画播放顺序: 在 Animator 中,AnimatorSet正是通过
    playTogether()playSequentially()animSet.play().with()before()after()这些方法来控制多个动画协同工作,从而做到对动画播放顺序的精确控制
// animation主要用于tween动画
   //根据资源得到动画
   Animation roitateAnimation = AnimationUtils.loadAnimation(this,R.anim.rotata_anim);
  //播放动画完成之后,保留动画最后的状态
   rotateAnimation.setFillAfter(true);
 //播放动画
  btnRotate.startAnimation(rotateAnimation);

// animator主要用于属性动画
   objectAnimator animator = objectanimator.ofFloat(textview,"alpha,1f,0f,1f);
   animator.setDuration(5000);
   animator,start();

   AnimatorSet animatorSet = new AnimatorSet();
    //移动
        objectAnimator ty = object Animator.ofFloat(btn,"translationY",0,300);
          ty.setDuration(1000);
   //旋转
       objectAnimator ty = objectAnimator.ofFloat(btn, "rotationY", 0,1080);
   //透明度
       objectAnimator alpha = objectAnimator.ofFloat(btn, "alpha", 1,0,0.5f,1);
   //缩放
       objectAnimator sx = objectAnimator.ofFloat(btn, "scaleX",1,0.5f);
   //一起播放
        // animatorSet.playTogether(items);
        animatorSet.play(ry),with(sx).after(ty).before(alpha);
        animatorSet.start();

请查看完整的PDF版
(更多完整项目下载。未完待续。源码。图文知识后续上传github。)
可以联系我获取完整PDF
(VX:mm14525201314)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!