Fighting!

潜行者的沉默

library和phone的区别

  1. library 是集成化,phone是组件化
  2. library中是apply plugin: ‘com.android.android’,且没有applicationId,phone是apply plugin: ‘com.android.application’,有applicationId
  3. library不能单独运行和单独打包,phone可以

流程

  1. 定义config.gradle,统一配置信息及,定义是否为isRelease开关(是否为发布版本)
  2. 在每个模块里做isRelease判断,是否是library,修改组件和集成库的区别
  3. 组件时debug代码的配置

组件化开发规范

组件前缀

module之间的交互

  1. EventBus 必须一对一,若一对多将混乱不堪难以维护
  2. 反射 维护成本高,容易出现高版本@hide限制
  3. 隐式意图,需要维护manifest
  4. BroadcastReceiver ,7.0后需要动态注册,需求方发送广播
  5. 类加载,需要准确的全类名,维护成本高容易出错

注解处理器的APT的应用

  1. 简单来说是一种按一定规则,自动生成代码的工具
  2. 需要了解他的程序元素Element包括:PackageElement包元素,TypeElement类或接口元素,VariableElement属性字段参数元素,ExecutableElement方法(包括构造,静态方法)元素
  3. Android studio 及 gradle的 版本兼容 1.as3.3.2 gradle 4.10.1(临界版本) 2. as3.4.1 gradle5.1.1(向下兼容)
  4. 定义需扫描的注解类
  5. 创建注解处理器@AutoService(Processor.class),指定支持的注解,编译的java版本及注解处理器接受内容的参数
  6. 通过获取节点,定义包名及新的class名称,通过filter创建一个JavaFileObject对象
  7. 获取JavaFileObject的Write对象,进行一行一行的写入文件

JavaPoet的应用

JavaPoet是square推出的开源的java代码自动生成框架,提供javaApi生成Java文件

  1. MethodSpec生成方法
  2. TypeSpec生成类或者接口class
  3. JavaFile创建java文件
  4. Element的子类,包括TypeElement, PackageElement等等表示程序可操作的元素,包括类,方法,属性,包等
  5. TypeMirror,获取类的所有信息,可以判断子类关系

Arouter的源碼分析

1. 初始化過程ARouter.init -> Arouter.init -> LogisticsCenter.init()
* 在该方法中会进行APT生成文件的dex文件的扫码,加载所有的class文件,保存到routerMap对像并保存到sp中(第一次启动比较慢),同时开启了线程池每个dex的加载都会创建一个线程,最后放入缓存对象Warehouse的map中
* 在 LogisticsCenter.init() -> _Arouter.afterInit()内部会创建拦截器对象interceptorService

路由的设计及应用

参考Arouter

  1. 组件间的跳转
  2. 组件间的值传递
  3. 获取其他组件的资源及实习方法
  4. 对未安装组件的拦截

项目实践

存在问题

  1. 各模块的service耦合度高
  2. 模块间的跳转都类名
  3. 数据的传输是调用方法的

进程通讯的方式

  1. 管道,耗性能
  2. 共享内存,多进程访问,管理混乱
  3. Socket适用于网络通讯,进程通讯不适用

Binder

  1. binder机制为每个进程都分配了UID和PID来作为鉴别身份的标识,安全性
  2. 对数据只进行了一次拷贝,通过驱动的内核空间拷贝数据,不需要额外的同步处理
  3. 使用简单C/S架构,实现面向对象的调用方式即在使用binder时,就和调用1个本地对象实例一样

Binder的四个重要角色

  1. server
  2. Client
  3. ServiceMannager
  4. Binder驱动(存在线程池 16个),具体实现通过内存映射,内部调用了mmap()函数
    前三者在用户空间,Binder驱动在内核空间

AIDL接口定义语言

是对Binder通讯的封装
IBinder代表有能力进行快进程的能力
IIterface 拥有了Binder机制的能力,只有一个asBinder的方法
Binder
Stub 本地对象

Binder的源码分析

  1. 打开binder设备
    1
    2
    frameworks /native /cmds /servicemanager /service_manager.c
    driver = "/dev/binder"; //返回文件描述符
  2. buffer的创建(用于进程间数据传递)
    1
    bs = binder_open(driver, 128*1024); binder,创建128k的那内存映射
  3. 开辟内存呢映射128k
    1
    2
    3
    4
    5
    frameworks /native /cmds /servicemanager /binder.c
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    mmap()命令
    device /google /cuttlefish_kernel /4.4-x86_64 /System.map
    在系统启动的时候开辟了内存映射
  4. serviceManager的启动
    1
    2
    3
    system /core /rootdir /init.rc
    start servicemanager
    在系统启动的时候做了启动服务
  5. 打包Parcel,数据写入binder驱动,copy_from_user
    1
    2
    3
    4
    5
    6
    7
    打包Parcel
    frameworks native libs binder IServiceManager.cpp
    addService

    数据写入binder驱动
    frameworks native libs binder IPCThreadState.cpp
    writeTransactionData
  6. 服务注册,添加到链表SVClist中
  7. 定义主线程中的线程池
  8. 循环从mIn和mOut中读和写数据请求,发到binder设备中

三方登录实例

A应用client端,B应用为Server端。A调用B进行登录。两应用aidl文件的包名必须一致

A应用

  1. 创建ILoginInterface.aidl
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // ILoginInterface.aidl
    package com.netease.binder;

    // Declare any non-default types here with import statements

    interface ILoginInterface {

    // 登录
    void login();

    // 登录返回
    void loginCallback(boolean loginStatus, String loginUser);
    }
  2. 在A应用中建立Binder连接,记得销毁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public void initBindService() {
    Intent intent = new Intent();
    // 设置Server应用Action
    intent.setAction("BinderB_Action");
    // 设置Server应用包名(5.1+要求)
    intent.setPackage("com.netease.binder.b");
    // 开始绑定服务
    bindService(intent, conn, BIND_AUTO_CREATE);
    // 标识跨进程绑定
    isStartRemote = true;
    }

    // 服务连接
    private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    iLogin = ILoginInterface.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
    };

  3. 因为需要接受B进程返回的数据,需要同样创建一个服务,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.netease.binder.a.service;

    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.util.Log;

    import com.netease.binder.ILoginInterface;

    public class MyService extends Service {

    @Override
    public IBinder onBind(Intent intent) {

    return new ILoginInterface.Stub() {
    @Override
    public void login() throws RemoteException {
    }

    @Override
    public void loginCallback(boolean loginStatus, String loginUser) throws RemoteException {
    Log.e("netease >>> ", "loginStatus: " + loginStatus + " / loginUser: " + loginUser);
    }
    };
    }
    }

    B应用

  4. 创建ILoginInterface.aidl
  5. 创建对应的service服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class MyService extends Service {

    @Override
    public IBinder onBind(Intent intent) {

    return new ILoginInterface.Stub() {
    @Override
    public void login() throws RemoteException {
    Log.e("netease >>> ", "BinderB_MyService");
    // 单项通信有问题,真实项目双向通信,双服务绑定
    serviceStartActivity();
    }

    @Override
    public void loginCallback(boolean loginStatus, String loginUser) throws RemoteException {
    }
    };
    }

    /**
    * 在Service启动Activity,需要配置:.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    */
    private void serviceStartActivity() {
    Intent intent = new Intent(this, MainActivity.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(intent);
    }

    }
  6. 在AndroidManifest配置文件中注册服务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!--
    代表在应用程序里,当需要该service时,会自动创建新的进程。
    android:process=":remote"

    是否可以被系统实例化
    android:enabled="true"

    代表是否能被其他应用隐式调用
    android:exported="true"
    -->
    <service
    android:name=".service.MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":remote_service">

    <intent-filter>
    <!-- 激活 MyService 唯一name,不能重名-->
    <action android:name="BinderB_Action" />
    </intent-filter>
    </service>
  7. 当A请求登录时,会唤起B应用打开登录界面,当输入完登录信息后需要将登录结果返回给A应用,因此同样需要A应用有一个跨进程服务接受B进程的登录结果,所以同样在B中创建Ade服务连接
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public void initBindService() {
    Intent intent = new Intent();
    // 设置Server应用Action
    intent.setAction("BinderA_Action");
    // 设置Server应用包名(5.1+要求)
    intent.setPackage("com.netease.binder.a");
    // 开始绑定服务
    bindService(intent, conn, BIND_AUTO_CREATE);
    // 标识跨进程绑定
    isStartRemote = true;
    }

    // 服务连接
    private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
    iLogin = ILoginInterface.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
    };

Handler的几个问题

  1. Handler内存泄漏的测试
    在消息未发送之前,将activity销毁,需要remove消息及将handler赋值为null
  2. 为什么不能在子线程创建Handler
    在子线程中直接new Handler,会获取当前线程的Looper,但我们并没有穿件Looper
  3. textView.setText()只能在主线程执行?
    是的,因为setText()会刷新界面,调用requestLayout及invalidate,内部会做是否为主线程的检查
  4. new Handler()两种写法有什么区别?
    直接new Handler 重写他的handlerMessage()方法不推荐,通过new Handler(new Handler.CallBack)是推荐做法 CallBack接口的handlerMessage方法应返回为true
  5. ThreadLocal用法和原理
    ThreadLocal 内部是一个Map对象,保存着key和value,可以为当前的线程,value为线程保存的值

源码分析

  1. 从应用的创建:ActivityThread.main() -> Looper.preMainLooper-> 这时候ThreadLocal.get() = null ->会创建主线程的Looper对象及唯一关联的MessageQueue
  2. 当在主线程中new Handler()时 -> 内部回调Looper.myLooper获取到主线程的Looper-> 将looper的messagequeue赋值给Handler的变量 -> 当handler调用sendMessage -> queue.enqueueMessage()
  3. 在ActivityThread.main() -> 会调用Looper.loop()方法 -> 从消息队列中获取消息 ->
  4. 调用message.target.dispachMessage -> target即Handler,则会调用handler中的dispatch方法 -> 最终掉用handler的handlerMessage方法

解决几个问题

  1. 为什么主线程用Looper死循环不会引发ANR异常
    因为Looper.next开启死循环的时候,一旦需要等待时或者还没有执行到执行的时候,会调用NDK里的JNI方法,释放当前的时间片,这样就不会引发ANR异常
  2. 为什么Handler构造方法里面的Looper不是直接new?
    因为直接new出来不一定能保证唯一,只有用Looper.prepare才能保证唯一性
  3. MessageQueue为什么要放在Looper的私有构造方法里初始化
    因为一个线程只能由一个Looper,所以在构造方法里初始化也就能保证MessageQueue的唯一性
  4. 主线程里的Looper.prepare/Looper.loop,是一直在无限循环里面的吗
    是的

MVC架构设计

流程关系

  1. View接受用户交互事件
  2. View将用户的操作,交给Controller
  3. Controller完成具体业务逻辑
  4. 得到封装好的Model,再进行View的更新

缺点

Controller是作为媒介,处于Model和View之前,Model和View之间有紧密的联系,耦合性强
Controller做的事情过多,违反了面向对象的单一职责原则

MVP架构设计

流程关系

  1. View接受用户交互事件
  2. View把用户的操作交给了Presenter
  3. presenter控制Model进行业务逻辑的处理
  4. presenter处理完毕后,数据封装进Model
  5. presenter收到通知后,再更新数据

优点

双向通信方式

  1. View层与Model层完全解耦
  2. 所有的逻辑都交给Presenter处理
  3. MVP分层较为严谨

MVVM架构设计

流程关系

  1. View接受用户数据
  2. View把用户的操作交给了ViewModel
  3. ViewModel控制Model进行业务处理
  4. ViewModel处理完毕后,数据封装进Model,刷新View

优点

  1. 降低耦合度:一个ViewModel层可以绑定不同的View层,当Model变化时View可以不变
  2. 可以把一些视图逻辑放在ViewModel层中,让很多View可以重用这些视图逻辑

dataBinding的使用

1.gradle文件添加,支持

1
2
3
4
// 添加DataBinding依赖
dataBinding{
enabled = true
}
  1. 布局文件中,添加最外层layout根布局
    1
    <layout xmlns:android="http://schemas.android.com/apk/res/android"></layout>
  2. 添加定义data标签,定义数据及数据来源
    1
    2
    3
    4
    5
    6
    <!-- 定义该View(布局)需要绑定的数据来源 -->
    <data>
    <variable
    name="user"
    type="com.zyx.cherish.mvp.bean.UserInfo" />
    </data>
  3. 在正常布局中用data中定义的name去使用“@=”是为了实现双向绑定(v -> model)
    1
    2
    3
    4
    <EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@={user.name}" />
  4. build之后在activity中通过DatabindinngUtils绑定类与布局,然后就可以设置数据
    1
    2
    3
    ActivityLoginLayoutBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login_layout);

    viewDataBinding.setUser(userInfo);

    源码分析

内存消耗的缺点

  1. 会生成大量的object的对象,用到了数组保存数组
  2. 每个View会监听数据变化,创建了线程
  3. 在刷新时会轮训发送handler消息
    在定义布局后通过rebuild,会生成额外的文件,这个是系统通过APT生成的
    主要有
  4. activity_login_layout-layout.xml 配置文件,可以让dataBing方便查找View
  5. activity_login_layout 为每个View添加了Tag如binding_1
  6. DataBinderMapperImpl 这个是具体实现绑定类
  7. 在DataBindingUtil.setContentView(this, R.layout.activity_login_layout)后会对布局中的控件进行绑定
  8. 线程 mRebindRunnable 控件会对M数据修改做监听,来更新V的数据
  9. InverseBindingListener 控件自身数据修改即V数据修改,会更新M的数据

AspectJ 的几个注意

  1. as3.0.1 gradle4.4-all 需要r17(NDK)
  2. as3.2.1 gradle4.6-all
  3. as3.4.0 gradle5.1.1-all 存在过时的api

1. fragment的切换有多少种方式,有区别吗

  1. add/remove,replace(remove后add,只有一个),hide/show(较耗性能), attach/detach(不会回收fragment,但会回收里面的View)
  2. 只要我们创建了activity一定要在manifest中声明吗

    3. activity有几种启动模式,各自的使用场景

  3. 共有四种启动模式
  4. 应用的首页,通常是singleTask

    4.实际开发中文件命名有何规范

  5. java文件以系统名结尾,XXXActivity,XXXFragment,XXXApplication
  6. 布局名以系统名+业务名+类型, activity_home_layout ,Layout布局文件
  7. 类或者方法添加注释

    5. 如何封装框架

  8. 从调用时可变参数的封装
  9. 核心部分不变参数及通用方法的设计
  10. 回调函数处理,异常处理,线程切换,数据转化的通用设计

    6.Charles的使用

  11. 请求拦截,查看请求及响应信息
  12. 请求地址的映射(Map),主要用于接口调试
  13. 请求参数设置,模拟慢网,超时等情况
  14. 7. 如何有序地做内存分析与优化

    https://juejin.im/post/5b1b5e29f265da6e01174b84

    8. 启动优化

    https://juejin.im/post/5d95f4a4f265da5b8f10714b

Material Design

它是一种Google官方推荐的设计规范。正如Android的碎片化,基于Android系统自研发的,拥有独特的交互和视觉效果的手机品牌在国内也是层出不穷,如小米的MIUI,魅族的flyme,锤子的Smartisan等等,这种开发被叫做ROM开发。对于App应用,按照安卓最新的MaterialDesign规范来进行单独的安卓版界面设计,这个是最花时间的,但是是最规范的

常用组件

  1. FloatActionBar
  2. bottomBar
  3. topBar
  4. Bottom navigation
  5. Navigation drawer
  6. Snackbars
    …等等

CoordinatorLayout的原理

依赖于NestedScroll的实现,CoordinatorLayout必须实现NestedScrollingParent,滚动对象如RecycleView必须实现NestedScrollingChild,

CardView的原理

Android的碎片化

android的碎片化严重,屏幕尺寸不同,分辨率密度,决定了做适配的重要性

常见方式

  1. 避免写死控件,使用wrap_content,match_parent
  2. LinearLayout线性布局:权重
  3. RelativeLayout绝对布局
  4. ContrainLayout 约束布局
  5. Percent-support-lib 百分比布局

图片资源释放

  1. .9图片或者SVG实现缩放
  2. 备用位图匹配不同的分辨率

用户流程匹配

  1. 根据业务逻辑不同执行不同的跳转
  2. 根据别名展示不同的界面

限定符适配

  1. 分辨率
  2. 尺寸
  3. 最小屏
  4. 横竖屏

刘海屏的适配

  1. Android 9.0官方适配
  2. 华为,OPPO,vivo

适配方式

限定符适配

优点

  1. 使用简单,无需开发者手动指定
  2. Google推荐使用方式,有系统自行判断
  3. 适配通过不同的xml实现,无需代码加逻辑

缺点

  1. 增加APK大小,适配机型越多,xml越多
  2. 适配所以,大概增加近3m
  3. 不能适配奇葩机型,如手表

    百分比适配

优点

  1. 通过百分比定义宽高,比较方便
  2. 彻底抛弃px,dp
  3. 开发量小

缺点

  1. 自定义不支持
  2. 对代码侵入强

    修改系统density, densityScale, densityDpi

    代码动态适配

网易云音乐歌单页面

Toolbar的使用

  1. 4.4之前是使用ActionBar,之后推荐使用Toolbar
  2. Toolbar不符合设计要求,通过反射修改子元素的属性(left,pading等)

    沉浸式设计

  3. 沉浸式与非沉浸式的区别是状态栏是否透明,是否与App主题是否一致.
    通过属性,开启沉浸式后状态栏就会变成浮动的,导致Toolbar向上移动
    1
    2
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
    通过代码控制,同样会导致Toolbar向上移动,内容延伸进状态栏
  4. 通过自定义设置一个透明的statusBarView,用于定义statusBar的颜色
0%