Android 大笔记

Table of Contents

Android开发学习的琐碎笔记, 自用.

View笔记

View是Android 中的用户交互组件,一个 view 就代表屏幕上的一块区域, 你可以在这块区域上画图, 点击, 移动等各种操作. View 的一个子类为 ViewGroup,viewgroud 是一种"布局"的概念, 它本身对用户是不可见的, 开发者 可以在通过 viewgroup 定义各种 view(按钮,图片)的布局, viewgroup 本身可以嵌套 viewgroup. View 在应用中通过"tree"的方式进行管理维护.创建 view 有两种方式:1是在代码中动态添加; 2是通过 定义一个 xml 文件来添加.通常的 Android 开发都是使用第二种方式.

坐标系统

---------------------------------
|parent   | screen     |        |
|view     |  --------  |        |
|         |  |      |  |        |
|         |  |      |  |        |
|         |  |view  |  |        |
|         |  |      |  |        |
|         |  |      |  |        |
|         |  --------  |        |
|         |            |        |
---------------------------------
   
getLeft()/getRight() 左/右边界与 父视图 边界距离
getTop()/getBottom() 上/下边界与 父视图 边界距离
getHeight()/getWidth() 自身 宽/高
getX()/getY() 事件点距离 自身 左/上边界的距离
getRawX()/getRawY() 事件点距离 屏幕 左/上边界的距离
getLocationInWindow() 获取在 窗口 内的坐标
getLocationInScreen 屏幕 中的位置.
scrollTo(x,y) view内容 移动到 view 的 (x,y)处
getScrollX()/getScrollY() *view内容*距离view的边界的距离, scrollX > 0, view的内容在其位置的左边(效果类似于左划内容移动).

适配

使用dp的原因是为了使组件在不同屏幕上的尺寸大体相同. 下表为常用数据.

   
分辨率 px, 横纵向上的像素点数, 如1920x1080
像素密度(density) dpi, 每英寸上的像素点数, 等于"对角线像素/尺寸"
dp 逻辑密度计算单位, px=(dpi/160) * dp
mdpi 120dpi-160dpi
hdpi 160-240
xhdpi 240-320
xxhdpi 320-480
getResource().getDisplayMetrics()  
metrics.heightPixels 屏幕高 px
metrics.widthPixels 屏幕宽 px
metrics.densityDpi 密度(dpi)
metrics.density densityDpi/160 = dpi/160
   

常用技巧:

  1. 为不同屏幕设置UI和资源. layout-sw<N>dp, 这里N是指宽高中的最小值. layout-w<N>dp, 这里当屏幕发生旋转时, N的值会变,因此可能会选择不同的UI目录.
  2. 使用webp和.9.png, vectorDrawable等图片.
  3. 当系统找不到合适的bitmap时,可能会讲一个低分辨率的图片进行扩放, 例如 将50x50的图片扩展到240dpi中的75*75的图片, 这样会占用更多内存. 为了防止这样, 可以将图片放到drawable-nodpi目录中.

在竖直方向移动view   view

offsetTopAndBottom(offset),offset>0, 向下移动. 否则向上移动.

给当前window添加view:   view

getWindow().getDecorView().addView(); 会添加到最上层.

设置view在不同状态下的UI展示   view

例如设置在点击或可用状态下的背景图片,背景颜色等。 通过设置一个xml文件来实现,使用 selector 标签来设置 在不同状态下的UI展示。然后在View的设置中引用这个drawable。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_apk_delete_clicked" android:state_selected="true"/>
    <item android:drawable="@drawable/ic_apk_delete_clicked" android:state_focused="true"/>
    <item android:drawable="@drawable/ic_apk_delete_clicked" android:state_pressed="true"/>
    <item android:drawable="@drawable/ic_apk_delete_nor"/>
</selector>

//layout file
    <ImageView
        android:src="@drawable/bg_apk_delete_selector"
        />

事件分发

  1. 事件传递顺序: Activity -> Window -> View. 收到事件会调用Activity的dispatchTouchEvent():
    1. 分发给Window, 通过window传给所有的子view. 即函数 superDispatchTouchEvent().
      1. 调用decorView的superDispatchTouchEvent(). decorView是layout的顶层view. 通过 ((ViewGroup)decorView.findViewById(android.R.id.content)).getChildAt(0) 可以获得activity通过setContentView()所设置的view.
    2. 如果没有view处理事件, 调用activity的onTouchEvent.
  2. 当一个事件过来时, view 的 dispatchTouchEvent(ev)函数会 调用, 该函数做以下判断:
    1. 调用viewgroup 的 onInterceptTouchEvent(ev). 如果返回true, 调用 onTouchEvent(ev). 否则, 调用子view的dispatchTouchEvent(ev). 重复这个过程.
  3. 如果设置了OnTouchListener, 那么先去判断OnTouchListener是否消耗 事件, 如果是则其消耗. 否则再调用onTouchEvent.
  4. 如果子view的 onTouchEvent()返回false, 则会调用父view的onTouchEvent. 直到传给Activity的onTouchEvent().
  5. 如果onTouchEvent()对于ACTION_DOWN返回false, 那么后续所有事件都不会再 传递给他. 而是交给父view.
  6. view没有onInterceptTouchEvent()方法, 事件过来直接调用onTouchEvent.

实验

自定义一个relativelayout和view, layout结构是两个relativelayout A/B, 里面放一个view C.

  1. 默认点击行为. onInterceptTouchEvent()都返回false, onTouchEvent()都返回false. ACTIONDOWN的传递过程:
    1. A:onInterceptTouchEvent->B:onInterceptTouchEvent->C:onTouchEvent->B:onTouchEvent->A:onTouchEvent
    2. MOVE和UP的动作都不会被收到.
  2. A拦截DOWN和MOVE. onTouchEvent中DOWN和MOVE都返回true. A的onInterceptTouchEvent()只有在DOWN时被调用. 后续ACTION不会调用. 所有ACTION都会传递给onTouchEvent().
  3. A拦截DOWN, 但是onTouchEvent返回false. 后续所有的事件都不会传递给ABC.
  4. A不拦截DOWN, 但是在onTouchEvent()里消耗DOWN(BC都是false, 所以会回传到A的onTouchEvent). 这样会导致所有的MOVE和UP都会再传递给A.
  5. A不拦截DOWN, 拦截MOVE. B拦截DOWN.
    1. DOWN发生时AB的intercept都会被调用.
    2. 第一次MOVE时只调用A的intercept. 但是会转化成CANCEL传给B的onTouchEvent.
    3. 后续的MOVE和UP都只传给A.
  6. A拦截UP, B拦截MOVE, C拦截DOWN.
    1. DOWN会传给C的onTouchEvent.
    2. 第一次MOVE最终会变成CANCEL, 传给C.
    3. 后续的MOVE都会先经过A的onInterceptTouchEvent, 然后传给B的onTouchEvent.
    4. UP会变成CANCEL传给B的onTouchEvent.

findViewById()使用DFS来查找View

@Override
protected View findViewTraversal(@IdRes int id) {
    if (id == mID) {
        return this;
    }

    final View[] where = mChildren;
    final int len = mChildrenCount;

    for (int i = 0; i < len; i++) {
        View v = where[i];

        if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
            v = v.findViewById(id);

            if (v != null) {
                return v;
            }
        }
    }

    return null;
}

AsyncTask

android消息机制

Apk启动简要流程

  1. 点击launcher的图标会调用launcher的startActivity()来启动应用的页面.
  2. AMS如果发现应用还未启动, 会通过socket通知Zygote来启动应用的进程.
  3. 应用进程启动完成后会调用ActivityThread的main()函数, 作为主线程. 该函数会实现了一个mainLooper, 然后循环等待消息.
  4. ActivityThread会将其ApplicationThread注册到AMS中,方便AMS调用其相关 的函数.
  5. AMS通过ApplicationThread通知应用程序进程创建Activity.

代码简要流程

第一次启动应用时的代码流程

  1. startActivityForResult() -> Instrumentation.execStartActivity -> AMS.startActivity() -> AMS.startActivityAsUser() -> AMS.startActivityMayWait() -> ActivityStack.startActivityLocked() -> ActivityStack.startSpecificActivityLocked() -> AMS.startProcessLocked -> Process.start() -> Process.startViaZygote() -> ZygoteInit.invokeStaticMain(启动ActivityThread的main()函数) -> ActivityThread.attachApplication(绑定到AMS) -> AMS.realStartActivityLocked() -> ApplicationThread.scheduleLaunchActivity() -> ActivityThread.sendMsg -> H.handleLaunch…

补间动画vs属性动画

(1)对于 Animation 动画:

他的实现机制是,在每次进行绘图的时候,通过对整块画布的矩阵进行变换,从而实现一种视图坐标的移动,但实际上其在 View 内部真实的坐标位置及其他相关属性始终恒定.

(2)对于 Animator 动画:

Animator 动画的实现机制说起来其实更加简单一点,因为他其实只是计算动画开启之后,结束之前,到某个时间点得时候,某个属性应该有的值,然后通过回调接口去设置具体值,其实 Animator 内部并没有针对某个 view 进行刷新,来实现动画的行为,动画的实现是在设置具体值的时候,方法内部自行调取的类似 invalidate 之类的方法实现的.也就是说,使用 Animator ,内部的属性发生了变化.

说完他们的基本实现原理,我们现在来对比一下他们的优势劣势:

(1)版本兼容

不得不说,相对于 Animation,Animator 的版本兼容性还是太差,直到 Android3.0才开始出现的 Animator, 是无法满足目前开发环境2.x 的兼容支持的,而且在 android 官方的 support 包中也没有对于低版本的 Animator 进行支持,所以单从版本兼容来看, Animator 还是不够的,不过这是系统历史原因,我们只能接受.

(2)实现效率

同样的,这也是 Animator 的一个缺点,由于 Animator 是直接通过设置对象的 setter,getter 方法,来起到动画显示效果的,所以为了满足对任意对象调用正确方法, Animator 使用了 Java 反射机制, 而 Animation 则是直接通过代码对矩阵进行处理,所以就效率这一方面而言, Animator比不上 Animation

已经说了 Animator 相较于 Animation 的两种劣势了,那么我们再来说说 Animator 相较于 Animation 的优势

(3)适用性

在上一个分析中,我们看到了由于 Animator 使用了反射机制导致其效率偏低,但是这也带来了他适用的对象范围的增加, Animation 仅对 View 这一种对象有用,但是 Animator 可以设置任意对象的属性,使其在某段时间内进行变化

(4)使用效果

相信大家平时使用 Animation 的时候,都有发现当正在进行平移移动,或者动画结束后,但位置发生改变的时候,你点击之前的位置,点击效果仍然存在,这就是因为 View 在内部的坐标位置其实没有发生改变,而如果使用 Animator 进行位移变换,那么你的点击位置就会随着动画效果发生相应改变,所以即使你正处在动画过程中,你也可以去点击按钮得到你想要的效果.

四种启动模式

  1. standard: 标准模式, 建立一个新的栈放入task中, 什么都不判断.
  2. singleTop: 如果当前页面已经是栈顶, 则不新建, 否则新建.
  3. singleTask: 如果当前页面不是栈顶, 弹出所有.
  4. singleInstance: 给当前页面建立一个全新的task, 只有一个实例.

用途: BrowserActivity uses singleTask. There is only one browser activity at a time and it doesn't become part tasks that send it intents to open web pages. While it might return to whatever most recently launched it when you hit back it is actually fixed at the bottom of its own task activity stack. It will share its task with activities that it launches like bookmarks. BrowserBookmarksPage uses singleTop. While there can be multiple instances of this activity, if there is already one at the top of the task's activity stack it will be reused and onNewIntent() will be called. This way you only have to hit back once to return to the browser if the bookmarks activity is started multiple times. AlarmClock uses standard. The user can launch multiple instances of this activity and these instances can be part of any task and anywhere in the activity stack. As a fairly simple application it doesn't really demand tight control of its activity. AlarmAlert uses singleInstance. Only one alert activity at a time and it is always its own task. Anything it launches (if anything) becomes part of its own new task.

一些性能技巧

性能优化(最佳实践)的一些原则:

  1. 不要做不比要做的事情.
  2. 如果可以, 尽量避免不必要的内存分配.

常用技巧:

  1. 避免创建不必要的对象. 例如返回的String直接用于StringBuffer()或StringBuilder,则比较浪费.
  2. 解析String时,使用substring而不是做copy.
  3. 两个数组比一个二维数组快..
  4. 如果函数不访问对象变量, 将其设置为static.
  5. 善用static final.
  6. 尽量使用库函数, 做了很多优化.
  7. 少用layout-weight. 会导致被measure()两次.
  8. 使用 include 和 merge.
  9. 使用ViewStub占位符.

Robolectric简单教程

该项目官网 http://robolectric.org/. github地址: https://github.com/robolectric/robolectric.

该文章基于Robolectric3.0

项目介绍

Robolectric是一个开源的单元测试框架, 它可以实现直接在JVM里跑Android相关的测试(Activity/Service), 避免Android自家出品的 古老 的必须要在虚拟机上跑的测试. (注: 目前来看, Android的后续版本对测试的支持越来越好…..)

官网上给出了Robolectric的几点特性:

  1. 模拟SDK, 资源和native方法: 总的来说, robolectric可以模拟虚拟机环境, 使你可以在 JVM就可以实现大部分测试.
  2. 摆脱虚拟机的束缚. 省去编译/打包/安装流程, 加快测试和重构速度.
  3. 不需要Mocking框架

简单的测试项目

加入到项目工程

添加robolectric的依赖, 由于要使用Junit和assert相关的函数, 所以把他们的依赖也一起加上.

testCompile 'junit:junit:4.12'
testCompile "org.assertj:assertj-core:1.7.0"
testCompile 'org.robolectric:robolectric:3.0'

加入完成后, 把Build Variants的 "Test Artifact" 设置为 Unit Tests.

编写简单测试代码

在src目录下创建test目录, 然后在test目录下创建与main相同的package目录. 创建TestMainActivity.class类, 来测试MainActivity. 在类名的前面加入以下两个注解:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class TestMainActivity {

第二个注解必须要将constants设置为编译系统生成的BuildConfig文件.

可以在类里面有 @Test 注解编写测试方法.例如:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class TestMainActivity {
    @Test
    public void init(){
        ActivityController controller = Robolectric.buildActivity(MainActivity.class).create().start();
        MainActivity activity = (MainActivity)controller.get();

        controller.resume();

        FloatingActionButton button = (FloatingActionButton)activity.findViewById(R.id.fab);
        button.performClick();

        assertTrue(button.getVisibility() == View.GONE);
    }
}

最后可以右键该类点击运行或通过gradle命令来实现跑测试.

Robolectric文档

模拟Activity的生命周期

通过ActivityController这个API可以实现对Activity生命周期 的控制. 通过以下API可以获取一个ActivityController实例化.

ActivityController controller = Robolectric.buildActivity(MyAwesomeActivity.class).create().start();

controller创建出来之后, 就可以调用start(), pause(), stop() 或者destroy()等函数来模仿Activity流程, 例如下面的代码就是 一个完整的activity流程:

Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().start().resume().visible().get();

注: visible()函数用来模拟activity attach到一个窗口的过程, 如果需要使用activity中 view相关的函数, 必须要先调用visible().

用Intent 或 savedInstanceState启动/恢复 Activity

//intent
Intent intent = new Intent(Intent.ACTION_VIEW);
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).withIntent(intent).create().get();

/bundle
Bundle savedInstanceState = new Bundle();
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class)
    .create()
    .restoreInstanceState(savedInstanceState)
    .get();

通过反射获取toolbar的title view   view

  private TextView getActionBarTextView() {
    TextView titleTextView = null;

    try {
        Field f = mToolBar.getClass().getDeclaredField("mTitleTextView");
        f.setAccessible(true);
        titleTextView = (TextView) f.get(mToolBar);
    } catch (NoSuchFieldException e) {
    } catch (IllegalAccessException e) {
    }
    return titleTextView;
}

ormlite库操作数据库   sqlite

ormlite 对比 android 自带数据库 api 的好处是可以基于类创建一个表, 及表中数据和类的映射.

常见用法:

  1. 实现一个类 继承 OrmLiteSqliteOpenHelper(后者继承自 SQLiteOpenHelper), 实现onCreate 和 onUpgrade 逻辑.
  2. 在调用 Orgmlite 的 api 时,传入这个 子类的调用.
  3. 在想要建表的类上,用注解 @DatabaseTable 和 @DatabaseField 类设置表的 名称和表项.
  4. 通过 Ormlite 的 api 获取上面类的一个 DAO(Ormlite 会给每个类创建一个 DAO 实例) ,然后使用 DAO 来进行 CRUD 操作.

app 常用库

gson操作json字符串

okhttp进行网络操作

fresco网络图片加载

umeng进行统计分析和反馈

sharesdk进行统一分享

阿里聚安全进行加密

广点通和百度的广告进行广告投放

小米push和umengpush来推送消息

设置多进程

  1. 方法: 设置 taskAffinity
  2. 保持锁屏占用的资源变少. 避免被系统应用杀死.
  3. 加快应用的相应速度.

让activity支持scheme

通过在activity节点中设置 intent-filter 节点. 里面的内容

  1. 设置scheme格式, 包括 host 和 scheme
  2. 设置action为 VIEW.
  3. 设置category为 DEFAULT. 也可以设置browsable, 这样可以通过浏览器打开activity.

通常可以放在Application类的动作

  1. 设置自定义异常捕获类.
  2. 统计, 注册, 反馈, 更新.
  3. 数据库.

可替换库对比

recyclerView VS listView

使用RecyclerView的一些好处:

  1. 支持添加/删除的动画
  2. 支持item的装饰
  3. 支持layoutManager实现多重布局.

这篇文章 比较了这两个控件, 主要包括: 总体来说就是RecyclerView提供了更多的客制化的功能, 尤其是对于 复杂的布局或者list实现. 具体来说:

  1. RecyclerView强制使用ViewHolder, 其ViewHolder与Adapter绑定. 因为ListView并没有强制使用ViewHolder,如果不用时, 查找View会变得 麻烦, 可能导致性能下降.
  2. RecyclerView通过LayoutManager的方式提供布局的多样化. ListView默认只支持竖直方向(可以通过重写代码实现水平方向, 但是麻烦).
  3. ItemDecoration 支持对每个项目进行修饰.

startService VS bindService   service

http://codetheory.in/understanding-android-started-bound-services/

  1. 如果需要和service进行交互,可以使用bindService.
  2. startService的service可以一直运行, 即使创建他的组件挂掉. 而bindService不行, 跟组件的生命周期一样.

how to resolve ANR

http://www.programering.com/a/MTMyEDMwATI.html

  1. first analysis log
  2. from the trace.txt file call stack.
  3. see code
  4. check the ANR origin(iowait?block?memoryleak?)

使用 Resource 类来获取 resource 资源

context 的 getResource 会返回一个 Resource 类, 使用该类可以获取定义的资源文件, 例如 String,Drawable,Integer 等等.

一些特殊设置技巧

辅助功能

通过开启辅助功能权限(也称作无障碍)可以模拟用户的点击,滚动等一切行为, 同时可以监控屏幕的变化, 实现一些"自动化"操作. 比如设置自动设置一些权限.

开启步骤

  1. 实现一个类继承自AccessibilityService类.
  2. 在AndroidManifest.xml里注册这个service, 并需要做如下配置
    1. 为该service增加permission "android.permission.BIND_ACCESSIBILITY_SERVICE"
    2. 为该service设置filter. action为android.accessibilityservice.AccessibilityService. 这样权限开启后才可以获得回调.
    3. 如果需要在xml里(也可以在代码里)对service进行配置, 比如要过滤屏幕事件.则需要增加一个meta-data.

      <meta-data
          android:name="android.accessibilityservice"
          android:resource="@xml/keyguard_accessibility_service_config"/>
      

通过上面的设置, 就可以在辅助功能设置页面看到app的项,勾选就可以, 或者在程序里直接 跳转到该页面, 使用如下代码:

Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);

自动控制逻辑

  1. 当用户在上一步中把权限开启后, 自定义的Service的onServiceConnected()函数 会被调用. 可以在这一步里进行配置. 或者如上一步所述在xml里配置. 并可以由此进行设置操作.
  2. 当有事件发生时, 例如窗口变化, 点击等, 会回调 onAccessibilityEvent()函数. 并传入事件.

系统的"最近任务"中不现实activity

可以在Actvivity的配置中加入 excludeFromRecents, 或者Intent中设置相应的FLAG.

打开"最近任务"窗口

private static void openRecentApps() {
    try {
        Class serviceManagerClass = Class.forName("android.os.ServiceManager");
        Method getService = serviceManagerClass.getMethod("getService", String.class);
        IBinder retbinder = (IBinder) getService.invoke(serviceManagerClass, "statusbar");
        Class statusBarClass = Class.forName(retbinder.getInterfaceDescriptor());
        Object statusBarObject = statusBarClass.getClasses()[0].getMethod("asInterface", IBinder.class).invoke(null, new Object[] { retbinder });
        Method clearAll = statusBarClass.getMethod("toggleRecentApps");
        clearAll.setAccessible(true);
        clearAll.invoke(statusBarObject);
    } catch (Exception e) {
        Log.d("Licc","Exception "+e.getMessage());
    }
}

不同机型特殊配置

判断是否为小米及获取小米版本

通过反射 android.os.SystemProperties 这个类, 调用其 get()函数, 来获取属性"ro.miui.ui.version.name"的值.

通过Build.java的BRAND变量来获取品牌名称判断是否为xiaomi.

public static int getMIUIVersion() {
    int versionName = UNKNOWN;
    try {
        Class<?> classType = Class.forName("android.os.SystemProperties");
        Method getStringMethod = classType.getDeclaredMethod("get", String.class, String.class);
        String version = (String) getStringMethod.invoke(classType, KEY_MIUI_VERSION_NAME, "");
        if ("v5".equalsIgnoreCase(version)) {
            versionName = V5;
        } else if ("v6".equalsIgnoreCase(version)) {
            versionName = V6;
        } else if ("v7".equalsIgnoreCase(version)) {
            versionName = V7;
        }
    } catch (Exception e) {
    }
    return versionName;
}

/** The consumer-visible brand with which the product/hardware will be associated, if any. */
public static final String BRAND = getString("ro.product.brand");

获取当前正在运行的应用   system

在实现中从三个地方取数据, 因为使用的API都说系统级的api, 在官方文档中明确支出这些api获取的数据是不可靠的, 所以多试几个以保证正确率.

getRunningAppProcess()

调用了ActivityManager的getRunningAppProcess()函数.获取 RunningAppProcessInfo的一个list.

: 在 L 及其以后的版本中, 首先会调用该方法, 观察系统的返回值, 如果系统返回的值为 null 或只包含 CH 的信息, 那么会设置一个 flag, 告知后面的程序需要打开下一步的 UsageStat 权限.

queryUsageStats()   stat

调用了UsageStatManager的queryUsageStats()函数, 该函数返回UsageStats的一个list. UsageStatManager需要在权限中 申请 android.permission.PACKAGE_USAGE_STATS, 并需要用户 在手机中授权才可以使用.

: 可以通过 PackageManager 的 queryIntentActivities() 函数来查询是否有 响应 "android.settings.USAGE_ACCESS_SETTINGS" 这个 action 的 activity 来判断能否打开设置这个权限的页面.

getRunningTasks()

调用了 ActivityManager 的 getRunningTasks() 函数, 该函数返回正在运行 的 Task, 该函数 L 版本之后被抛弃.

在别人的页面(例如系统页面)显示指导

可以有两种方法:

  1. 先把别人页面调起, 同时设置一个 postDelay 操作推迟一段时间后再启动一个 activity. activity 的背景可以设置为透明, 这样方便看到下面的内容.
  2. 在一个 service 里,通过 WindowManager 给当前的 window(其他页面) 添加 view.

使用 native 程序保持进程不死

CH 的思路是 app 开启的时候通过 jni 来启动一个 native 程序 每次 app 启动的时候,都会去检查这个程序进程是否还在, 如果不在就启动一下.

而这个 native 的进程会周期性的检查 app 的进程目录(/proc/pid)是否存在, 如果发现不存在, 就会执行系统命令 "am" 发送 intent 来启动 Service.

版本更新

一般的版本更新分为两种情况:

  1. 强制更新, 每次启动 app 都弹出更新提示, 不更新进不去.
  2. 非强制更新, 用户可以选择取消更新.

更新同时也分为"免流量"或"需要流量"更新, "省流量"的伎俩就是在 wifi 环境下偷偷把 apk 包下载下来,然后提示用户安装. "需要流量" 则是在用户确认之后再下载. "需要流量"一般是在移动网络环境下使用. 只要在 wifi 下测到有新版本就偷偷下载.

apk包下载中的一些知识:

  1. 何时去服务器检验是否有新包? 可以通过下载一个配置文件来获取服务器最新包的所有信息. 然后通过当前包的版本信息与配置文件做对比.
  2. 下载下来的包校验. 在配置文件中返回新包的 MD5值, 把 apk 下载之后计算新包的 MD5. 然后做对比.

与服务器数据传输的加密

主要使用了两个东西, u-key 和 us.

  1. u-key: 通过 AESCoder来加密程序获取的一系列手机参数, AESCoder 的 encoder() 函数的 key 放到 jni 层,通过 jni 获取.
  2. us:通过阿里聚安全再对上面的 u-key 做进一步加密生成 us. 这样传给服务器的加密数据有两个, 一般 us 不会被识破.

: 阿里聚安全还可以识别是否为虚拟机.

通过 intentservice 来执行后台任务   intentservice

intentService 通过执行一个后台线程来处理接受到的 intent.所以可以让一些繁重的工作通过 intentService 来处理. 但是 intentService 只启动一个线程, 对于接受到的 intent 都是 依次处理的. 可能需要等待较长时间.

注: intentService 是一个抽象类, 使用者必须自己实现一个其子类并实现 onHandleIntent() 函数.

JobScheduler

一般使用流程

  1. 创建一个自定义JobService
  2. 使用JobInfo的builder基于上面的JobService创建一个JobInfo.
  3. 调用JobScheduler的schedule()函数安排工作.

ch

常见的主页展示方式

目前国内主流的主页展示方式是下面一个 tab 栏, 然后 点击每个 tab 项展示不同的页面.

CH 的实现使用了 TabLayout 和 FragmentPageAdapter.

锁屏上的滑动解锁 view

  1. 整个的这块区域就是一个 view, 图标都是通过画布画上去的.
  2. 构造函数来获取要绘制的 drawable
  3. onMeasure 中计算 view 的实际宽高.
  4. onLayout 中确定各个 drawable 的摆放位置
  5. onDraw 中将各个 drawable 画到画布上.

通过 view 学习的思考自定义 view 的实现

  1. 要确定好这个 view 的原型图和动画效果. 控件要怎么布局, 控件支持的动画和控件之间的动画交互.
  2. View 支持的自定义属性.
  3. 尽量把 view 细分, 一个 view 可能包含多个组件. 每个组件要怎么 实现要想好. 组件之间有没有共性, 能否抽象.
  4. 耦合性, 在这个例子里, 动画使用 ObjectAnimator, 可以支持 View 的封装对象(该对象本身不是 view, 但通过该对象的变化来支持 view).
  5. 如果 view UI 有改变要记得调用 invalidate().
  6. 要熟练画布的使用.

jni

jni里分配的内存会算到OOM内存上吗?

jni原理

performace patterns 笔记

Rendering Performance

该视频主要讨论 UI 的流畅度问题,如果用户在使用 App 发现有卡顿或不流畅的现象,这一般都是 渲染 问题.

Android 系统一般每16ms 重绘一下应用界面,所以一秒能画60帧. 这意味着你所有的 UI逻辑最好都在16ms 内完成,如果你的应用需要更新 UI,但是新的界面的生成时间超过了16ms,那么当系统在下一次需要去 重绘画面的时候, 就找不到新的界面,就不会做任何动作, 这就是 掉帧 现象. 对于用户来说,他看到当前 界面的停留时间就是32ms,而不是16ms. 对于 动画 效果来说,用户很容易就可以看到这种延迟问题, 尤其当用户需要用应用进行交互时(e.g 拖动画面或输入), 这是很不好的用户体验.

a) 产生这个问题的一些主要原因:

  1. 重绘 view 花费太多 CPU 周期,尤其是重绘一个结构复杂的 view.
  2. OverDraw. 对于重叠的 layout, 对用户来说, 被 遮挡 住的对象是不可见的. 所以如果将整个层次都 绘制完成后才呈现给用户, 会浪费很多的时间在用户看不到的像素上.

    打开 Show GPU Overdraw, 就可以观察应用的 overdraw 现象, Android 系统透过不同的颜色表示 overdraw 程度, 一般某一像素被重绘的次数越多,该像素的颜色越重.

    一个常见的产生 overdraw 的情景就是大量使用 background,例如整个 activity 有一个 background,然后 里面的 view 控件也有自己的 background.

  3. 动画太多.使用大量的 CPU 和 GPU 资源.

b) 渲染性能分析的常用方法

  1. 使用 HIerarchy Viewer 分析 layout 结构,如果 layout 结构过于复杂,重绘时间会过长.
  2. 使用手机上的 Developer Option 中带的一些 debug 选项来查看应用是否有 overdraw 的问题. 包括: Profile GPU Rendering/Show GPU Overdraw/GPU View Updates.
  3. 使用 traceview 分析绘制过程的 cpu 使用情况.

c) 关于VSYNC

刷新率: 屏幕每秒更新的次数, 用 HZ 表示; 帧率: GPU 每秒生成帧的数量, fps.

显示一个画面的一般流程: GPU 获取数据,绘制,硬件将绘制好数据显示到屏幕上.如果这种协作不一致,会产生视觉上的问题.

例如:显卡使用同一片内存来绘制帧,因此新的帧会覆盖旧帧.这种覆盖是 一行一行 覆盖的. 所以, 可能出现这种情况, 当屏幕需要显示时, 它不知道当前的内存中的内容(有可能这时候覆盖 正在进行中, 或者当前的帧还没画完).

对这个问题的解法是使用 内存,当 GPU 画完一帧后,将其从当前 buffer(backbuffer)移动 到 frame buffer.然后再使用 back buffer 画下一帧.当屏幕需要更新, 就从 frame buffer 中 取数据, 这能保证不影响 GPU 的绘制过程.

VSYNC 就是协调这种 copy 过程的机制. 理想情况下,帧率一般大于刷新率,这样当一次屏幕更新完成 后, 可以通过VSYNC 机制告诉 GPU 下一次刷新过程. 相反, 如果刷新率大于帧率, 当屏幕需要刷新时, 有可能在 frame buffer 中取到的还是上一次的数据. 所以如果系统的帧率间歇性的出现问题(小于刷新率), 用户就会感到 卡顿 现象发生.

对于应用程序而言,出现这种间歇性问题的原因,有可能就是生成的数据过慢, 导致 GPU 饥饿. 没有时间在下一次屏幕刷新前做完成绘制.

d) GPU 渲染分析

打开 开发者选项GPU 呈现模式分析, 选择在屏幕上显示. 选好后, 会在屏幕上显示一些颜色 条. 这些颜色条显示了三部分的渲染效果:1, 最底层代表导航栏; 2, 最上层代表通知栏; 3, 中间 代表当前活动的应用程序. 我们只关注第三部分.

当这个功能开启后, 会从左到右的显示颜色条,每个竖条都代表一个被渲染的帧,竖条越高, 代表渲染时间 越长.还可以看到屏幕上有一条绿线, 该线表示16ms.所以如果想要达到60帧/s 的效果,必须保证每个竖条 都在绿线以下.

每个竖条都有大约3种颜色组成:

  • 蓝色表示绘画时间; 在一个 view 被渲染之前,首先要被转化成 GPU 可以处理的格式,这种转换可能知识 简单的几个绘图命令,也可能是很复杂的Canvas 数据.一旦转换完成,结果就会被系统当成存储为 display list. 蓝色条即表示转换和 cache 该帧的所有 view 花费的时间. 时间长的原因可能是 需要绘制的 view 过多, 或者某个 view 的onDraw()逻辑太复杂.
  • 红条代表执行时间. 即 Android 的2D 渲染器执行上一步的 display list 的过程.Android 系统 通过与 OpenGL ES API 交互来将 display list绘制到屏幕,该过程首先将数据传给 GPU,然后在将 像素绘制到屏幕上. 当 view 约复杂(自定义 view),可能就需要更复杂的 OpenGL 绘图命令.重绘更多 的 view 同样会导致该问题.
  • 橙色代表处理时间.也可表示 CPU 的等待时间.如果该条过长,说明 GPU 的工作太多. About Execute: if Execute takes a long time, it means you are running ahead of the graphics pipeline. Android can have up to 3 buffers in flight and if you need another one the application will block until one of these bufferes is freed up. This can happen for two reasons. The first one is that your application is quick to draw on the Dalvik side but its display lists take a long time to execute on the GPU. The second reason is that your application took a long time to execute the first few frames; once the pipeline is full it will not catch up until the animation is done. This is something we'd like to improve in a future version of Android.

e) More about GPU

将对程序所描述的内容转化为最后屏幕上的像素的过程用到了 光栅化 这项技术. 对该技术的解释为 "把物体的数学描述以及与物体相关的颜色信息转换为屏幕上用于对应位置的像素及用于填充像素的颜色, 这个过程称为光栅化,这是一个将离散信号转换为模拟信号的过程。"

光栅化是一项很耗时的技术,所以该项动作专门交给 GPU 处理. CPU 首先将这些数据(图形/纹理…) 传输给 GPU(通过 displaylist 这个数据结构),然后GPU 将其绘制到屏幕上. 这个过程是通过 OpenGL ES 完成的. 但是CPU 将组件转化为纹理的过程以及将转化后的数据传给 GPU 的过程都是非常耗时的操作.

为了优化这项操作, OpenGL ES 提供了 API 可以一次将数据传给 GPU,当需要重绘同一物体时,只需 告诉 GPU 就好了.所以要尽可能的将最多的数据提供给 GPU 并尽量不去修改.

f) Invalidate/layout

上节说过 CPU 通过 displaylist 将数据传给 GPU,如果一个 view 的位置发生改变,可能只需重新 执行一次这个 displaylist 就可以.但是在另一种情况下,view 的改变会导致 displaylist 不合法, 需要重新创建一个 displaylist.

当一个 view 的 size 改变时,会触发 measure 流程,该流程会遍历 view 树,询问每个 view 的新 size. 当位置改变,会触发 layout 流程,对每个 view 生成新的位置.

g) Overdraw/Cliprect/Quickreject

Android 目前在尽量避免 overdraw 现象.但是对于自定义 view,android 系统的优化程序通常无法触及 (重写onDraw()函数). 但是可以通过下述方法给优化程序一些提示:

  • Canvas.cliprect(): 该函数可以让你定义 boundaries.所以只有 boundaries 区域内的内容会被绘制. 屏幕上的其他区域会被忽略.在底层实现上,也只有该区域内的数据会传输给 GPU.
  • quickreject: 规划不用 draw 的区域.

单例模式使用volatile

MVVM 的一种实现

MVVM 的含义

  1. M: model, 负责提供数据以及跟后台服务交互.
  2. VM: 中间件. 负责将 model 提供的数据进行转化, 方便 view 使用.并将 view 的请求发送给 model.
  3. V: 实现用户接口并包含一个 VM.

主流的两种实现方式

  1. RxJava Observables.
  2. Android Data Binding.

常见代码:activity 做所有事情

下述代码中, activity 同时负责数据的请求和 UI 的更新.这样做的弊端:

  1. MainActivity 知道的太多, 例如数据的获取方式.
  2. 测试代码不好写.
public class MainActivity extends AppCompatActivity {

    EditText editText;
    ImageButton imageButton;
    BooksAdapter adapter;
    ListView listView;
    TextView textNoDataFound;
    GoogleBooksService service;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Configure Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                // Base URL can change for endpoints (dev, staging, live..)
                .baseUrl("https://www.googleapis.com")
                // Takes care of converting the JSON response into java objects
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        // Create the Google Book API Service
        service = retrofit.create(GoogleBooksService.class);


        editText = (EditText) findViewById(R.id.editText);
        imageButton = (ImageButton) findViewById(R.id.imageButton);
        textNoDataFound = (TextView) findViewById(R.id.text_no_data_found);

        adapter = new BooksAdapter(this, -1);

        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);

        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                performSearch();
            }
        });
    }

    private void performSearch() {
        String formatUserInput = getUserInput().trim().replaceAll("\\s+", "+");
        // Just call the method on the GoogleBooksService
        service.search("search+" + formatUserInput)
                // enqueue runs the request on a separate thread
                .enqueue(new Callback<BookSearchResult>() {

                    // We receive a Response with the content we expect already parsed
                    @Override
                    public void onResponse(Call<BookSearchResult> call, Response<BookSearchResult> books) {
                        updateUi(books.body().getBooks());
                    }

                    // In case of error, this method gets called
                    @Override
                    public void onFailure(Call<BookSearchResult> call, Throwable t) {
                        t.printStackTrace();
                    }
                });
    }

    private void updateUi(List<Book> books) {
        if (books.isEmpty()) {
            // if no books found, show a message
            textNoDataFound.setVisibility(View.VISIBLE);
        } else {
            textNoDataFound.setVisibility(View.GONE);
        }
        adapter.clear();
        adapter.addAll(books);
    }

    private String getUserInput() {
        return editText.getText().toString();
    }
}

使用 RxJava 改造

创建 Model

顶层的 model 使用接口实现, 接口定义了 model 的行为和返回的结果. 然后提供一个 实现类. 实现类中使用 Retrofit 的 Observable 取代了 Callable. 返回的 Observable 已经配置为在线程中执行请求.

public interface BooksInteractor {
    Observable<BookSearchResult> search(String search);
}

public class BooksInteractorImpl implements BooksInteractor {
    private GoogleBooksService service;

    public BooksInteractorImpl() {
        // Configure Retrofit
        Retrofit retrofit = new Retrofit.Builder()
                // Base URL can change for endpoints (dev, staging, live..)
                .baseUrl("https://www.googleapis.com")
                // Takes care of converting the JSON response into java objects
                .addConverterFactory(GsonConverterFactory.create())
                // Retrofit Call to RxJava Observable
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
        // Create the Google Book API Service
        service = retrofit.create(GoogleBooksService.class);
    }

    @Override
    public Observable<BookSearchResult> search(String search) {
        return service.search("search+" + search).subscribeOn(Schedulers.io());
    }
}

创建 ViewModel

ViewModel 的功能比较简单, 配置执行的线程. 如下例设置了返回的搜索结果在 scheduler 中被执行.

public class BooksViewModel {

    private BooksInteractor interactor;
    private Scheduler scheduler;

    public BooksViewModel(BooksInteractor interactor, Scheduler scheduler) {
        this.interactor = interactor;
        this.scheduler = scheduler;
    }

    public Observable<BookSearchResult> search(String search) {
        return interactor.search(search).observeOn(scheduler);
    }
}

改造后的 activity

public class MainActivity extends AppCompatActivity {

    EditText editText;
    ImageButton imageButton;
    BooksAdapter adapter;
    ListView listView;
    TextView textNoDataFound;

    private CompositeSubscription subscriptions = new CompositeSubscription();
    private BooksViewModel booksViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        booksViewModel = new BooksViewModel(new BooksInteractorImpl(), 
                                            AndroidSchedulers.mainThread());

        editText = (EditText) findViewById(R.id.editText);
        imageButton = (ImageButton) findViewById(R.id.imageButton);
        textNoDataFound = (TextView) findViewById(R.id.text_no_data_found);

        adapter = new BooksAdapter(this, -1);

        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);

        imageButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                performSearch();
            }
        });
    }
  
    @Override
    protected void onDestroy() {
        subscriptions.unsubscribe();
        super.onDestroy();
    }

    private void performSearch() {
        String formatUserInput = getUserInput().trim().replaceAll("\\s+", "+");
        subscription = booksViewModel.search(formatUserInput)
                .subscribe(new Observer<BookSearchResult>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(BookSearchResult bookSearchResult) {
                        updateUi(bookSearchResult.getBooks());
                    }
                });
    }

    private void updateUi(List<Book> books) {
        if (books.isEmpty()) {
            // if no books found, show a message
            textNoDataFound.setVisibility(View.VISIBLE);
        } else {
            textNoDataFound.setVisibility(View.GONE);
        }
        adapter.clear();
        adapter.addAll(books);
    }

    private String getUserInput() {
        return editText.getText().toString();
    }
}

好处

  1. 方便写测试用例.

       public class BooksInteractorMock implements BooksInteractor {
        @Override
        public Observable<BookSearchResult> search(String search) {
            return Observable.just(getMockedBookSearchResult());
        }
    
        private BookSearchResult getMockedBookSearchResult() {
            BookSearchResult bookSearchResult = new BookSearchResult();
    //        bookSearchResult.setBooks(myListOfBooks);
            return bookSearchResult;
        }
    }
    
  2. ViewModel 不需要知道 View 的存在(对比 Presenter).
  3. 可以包含多个 ViewModel.

AppBarLayout笔记   coordinatorlayout

  1. 该类的默认behavior是Behavior, 是用标注修饰的.
  2. 定义了OnOffsetChangedListener接口. 可以将该view offset的变化传递给子view.
  3. getTotalScrollRange() 获取appbar的scroll的范围, 因为appbar是linearlayout, 所以从上向下遍历, 直到遇到没有设置sroll flag的子view, 其range是所有该设置flag的子view的高度和, 如果碰到设置了 scroll flag的子view同时设置了SCROLL_FLAG_EXIT_UNTIL_COLLAPSED, 则值等于前面相加的和减去该子view的最小高度.
  4. 调用checkAppCompatTheme()检查有没有用AppCompat主题.
  5. 获取xml配置中的elevation属性
  6. 设置xml定义的drawable
  7. 如果xml中定义了expand属性(boolean值), 获取并调用expand()设置.
  8. ViewCompat的isLaidOut()函数返回true如果view至少经历过一次layout.
  9. recycle()函数会回收typedarray, 调用只有不能再使用typearray.
  10. 设置elevation 阴影.
  11. 调用ViewCompat的setOnApplyWindowIInsetsListener()函数设置 使用window insets的策略. 策略为: 设置变量mLastInsets的值为得到的insets.然后 遍历子view, 用该insets调用每个child的dispatchApplyWindowInsets函数.
  12. 在layout阶段会检查子view是否设置了interpolator.

Behavior

  1. scroll()函数
    1. 调用setAppBarTopBottomOffset()函数
      1. 判断当前的offset是否在合理区间 如果是, 计算新的offset值.
      2. 调用setTopAndBottomOffset移动view.
      3. 通知依赖appbarlayout的兄弟view appbarlayout的移动事件.
      4. 通知注册了OffsetChange Listener的listenerview的变化. 将新的offset通知给listeners.
  2. interpolateOffset()
  3. 可以给appbarlayout的子view设置interpolator.
  4. 在onStartNestedScroll()中, 检查子view是否设置了scroll参数, 是则返回true.
  5. 在onNestPreScroll()中, 检查y方向移动参数是否不为0, 如果是
    • dy<0. 向下滚动, 最小高度getTotalScrollRange()的值, 该值 代表的是移动的最大的高度. 因为appbarlayout是向上移动, 所以 该值是一个负值. max也是一个负值. 最大高度是该值加上getDownNestedPreScrollRange()的值 (当设置了ENTER_ALWAYS时该函数的返回值不为0). 向下滚动时, 其实该behavior实际没有做任何移动的动作. 移动此时min=max, 而appbar的offset一般>min, 所以不满足 min<=curoffset<=max的判断条件.
    • dy>0. 向上滚动. 最小值跟上面一样. 最大值为0.
    • 调用scroll()函数. 只有向上滚动的时候这个函数才起左右. 此时将修改appbar的offsettop的值, 并将其与之前值的差值 返回.
  6. onNestScroll(). 该函数有四个坐标参数, 其中dxConsumed/dyConsumed 是在scroll过程中生成scroll动作的view本身消耗的距离, 然后剩下的 才通过该函数传递给appbarlayout, 以协调两个函数之间的ui动画.
    • 首先判断未消耗的y方向参数是否小于0, 如果是, 则调用scroll()函数 移动appbar. 并设置mSkipNNestedPreScroll的值为true.
  7. 在onStopNestedScroll()函数中会调用snapToChildIfNeed() 对snap标志进行检查. 如果设置该参数, 则会计算子view的位移量, (不是该view的top就是bottom), 然后在移动子view到新的offset.
  8. 如果侦测到这次scroll是一此fling动作(速度比较快), 则会调用 onNestedFling()函数, 该函数有一个参数consumed, 代表这次fling 是否已被被发出scroll动作的view消耗.
    • 如果否, 则appbar调用fling()函数自己做fling动画. 对于appbar来说, 向上滚动时, consumed总是是true.所以 只对向下滚动有效.
  9. 如果子view设置了interpolator, 计算interpolator offset.
  10. 如果在behavior中设置了layoutDependsOn()函数(即该函数返回true), 则代表该behavior依附的view有依赖view, 则CoL在每次对依赖view昨晚laid out 之后, 都会对该view做layout. 如果依赖view的位置改变了, 则 会调用该view的onDependentViewChanged()函数.

ScrollingViewBehavior

  1. updateOffset()函数, 该函数会获取依赖view的behavior, 如果依赖view是appbar, 那么会调用appbar(及behavior)的相关 函数获取offset, 然后基于appbar的offset来移动自己.

ViewOffsetHelper

  • getTopAndBottomOffset

RecyclerView   recyclerview

note

  1. Scrap
  2. Recycle
  3. Dirty
  4. 在RV进行layout或scroll的过程中不能修改adapter的内容, 否则会报异常.

Adapter

  1. onCreateViewHolder() 创建一个VH. 客户端实现.
  2. createViewHolder(RV, type) 创建一个VH. RV内部在getViewForPosition中调用. 通过type,可以为RV创建不同的item view.
  3. bindViewHolder(VH, pos) 将adapter里pos位置的数据跟一个VH绑定起来.
  4. getItemId(pos) 返回adapter里pos位置的元素的id. 需要客户端实现.
  5. viewAttach/viewDetach
  6. adapter数据改变函数.

note

  1. 把stableIds设为true显示效果跟false不一样.

ViewHolder

该类用来存放view及其相关属性.

  1. 通过getLayoutPosition获取item在adapter的位置.
  2. 可以设置一个view的标志为ignore. 这样不会被回收.

AdapterDataObserver

用来监测Adapter的数据变化, 提供的API:

  1. onChanged()
  2. onItemRangeChanged()
  3. onItemRangeInserted()
  4. onItemRangeRemoved()
  5. onItemRangeMoved()

AdapterDataObservable

该类继承自Observable<T>, 所以天然自带一个arraylist, 该arraylist的元素类型为 AdapterDataObserver. 该类提供了一些api, 在api的实现上, 基本都是迭代调用 arraylsit里的AdapterDataObserver的对应api.

LayoutManager

  1. 该类用于测量和放置RV里的子View.
  2. 可以在RV的xml中设置一个layoutmanager.

setMeasuredDimensionFromChildren

该函数用于根据RV的子view来测量RV的边界. 会遍历 所有的子view, 找到"最边上"的子view的"上下左右"边界. 然后跟onMeasure()传入的spec作比较后生成最终的width和height.

比较规则:

  1. 如果mode是EXACTLY, 使用spec的size.
  2. 如果是AT_MOST, 选择spec的size和子view中size中的小值.

mAutoMeasure

这个变量标志measure过程由谁完成.

  1. true, measure过程由RV完成.
  2. false, 由LayoutManager完成.

onLayoutChildren

代码注释笔记:

  1. 如果mAutoMeasure为true. 该函数会被调用两次:
    1. 第一次确认items的位置.
    2. 第二次做实际的layout.

ChildHelper

  1. 该类用于管理子view, 它使用一个bitmap来表示目前的 view, bitmap的长度表示子view的数量. 如果某个bit被 设为 1, 则表示该view不可见.
  2. addView(index) 添加一个view, 传入的index参数是"视觉"上的要添加 的位置(从0开始), 但是因为可能存在"隐藏"的view, 所以 真实的添加位置是大于等于index的.
    1. 重置bucket.
    2. 清空mHiddenViews列表. 并对每个要清除的view调用 callback的onLeftHiddenState()函数. 这里的callback是 在RV里实现的. 他的onLeftHiddenState()接口实现为调用 ViewHolder的onLeftHiddenState()函数.
    3. 调用callback的removeAllViews()函数. 在RV中, 这个 函数被定义为.
      1. 对每个子view调用dispatchChildDetached.
      2. 调用RV的removeAllViews().

State

记录RV的一些信息, 例如

  1. 可以被layout的item数量. 该值可能不等于adapter的size.

LinearLayoutManager

Recycler

  1. "scrapped" view值被标记了的view. 可能会被移除或复用.
  2. Recycler包含两层缓存, "缓存"view和RVP, 如果缓存满的话 会将其放入到RVP中.
    1. 清空mAttachedScrap.
    2. 将mCachedViews里的元素移到RVP中.

RecycledViewPool

  1. 提供了在多个RV之间共享view的功能.
  2. 如果不为RV设置一个Pool, RV会自己创建一个.

AdapterHelper

UpdateOp

一直操作的命令类.

OpRecorderer

  1. reorderOps 操作重排序.
    1. MOVE后面是REMOVE:
      • MOVE可能分两种情况: 前面的元素移动到后面, 或者 后面的元素移动到前面.
      • 如果MOVE的元素最后落到了REMOVE的区间内, 则表示 这个元素最终会被REMOVE掉, 则可以将MOVE命令改为 REMOVE命令. 之前的REMOVE命令可以少remove一个元素. 如果减少之后之前的REMOVE命令要remove的数量为0, 怎直接从这个list中把其删除即可.

RecyclerViewAccessibilityDelegate

DefaultItemAnimator

ItemAnimator

RV

  1. mTouchSlop表示多长的距离就可以认为是scroll.
  2. mMinFlingVelocity和mMax…是滑动的最大/最小速度.
  3. 构造函数流程:
    1. 设置scroll和focus配置.
    2. 判断SDK是不是大于等于16, 16及以上版本才支持post animation.
    3. 获取系统的ViewConfiguration, 通过vc获取 被认为是scrolling的一些参数. 例如初始化一个fling的 最大/最小速度.
    4. 设置mItemAnimator的listener为mItemAnimatorListener.
      1. mItemAnimator被初始化为DefaultItemAnimator对象. DefaultItemAnimator继承自SimpleItemAnimator, 后者继承自 ItemAnimator.
    5. 初始化AdapterManager, 生成一个新的AdapterHelper类.
    6. 初始化ChildHelper, 生成一个新的ChildHelper对象.
    7. 获取AccessibilityMananger.
    8. 如果在xml里设置了layoutManager, 则创建LayoutManager.
    9. 创建一个NestedScrollingChildHelper实例.
    10. 设置上一步的helper的nested scroll为true.
  4. onMeasure.
    1. 如果mLayout为Null, 调用 defaultOnMeasure(). 该函数没有设置layoutmanager的默认"测量"函数.
    2. 如果mLayout不为null,即为RV设置了LayoutManager.
      1. 如果mLayout的mAutoMeasure被设置. (LinearLayoutManager里设置了该变量为true).
        1. 如果是EXACTLY, 那么设置skipMeasure为true.
        2. 调用mLayout的onMeasure函数.该函数就是调用defaultMeasure()函数. 该函数只会处理 EXACTLY 的mode.
      2. 否则.
  5. onLayout.
    1. 直接调用dispatchLayout().
      1. 如果adapter和layoutMananger有任何一个没有设置 返回.
  6. onDraw. 调用父类的onDraw, 如果mItemDecorations不为空, 调用每个元素的onDraw.
  7. setLayoutManager. 设置RV的layoutManager.
    1. 调用8函数停止当前的scroll.
    2. 如果之前有layoutmanager, 调用其dispatchDetachedFromWindow()函数.
    3. 调用recycler的3函数.
    4. 调用childHelper的3函数.
    5. 将RV的mLayout设置为传入的参数.并调用其dispatchAttachedToWindow()函数.
    6. 调用requestLayout()函数.
  8. 停止当前的scroll.
    1. 将当前的setScrollState()设置当前state为为SCROLL_STATE_IDLE.
      1. 调用stopScrollersInternal().
        1. 调用mViewFlinger的stop()函数. 该函数会调用 removeCallbacks()将mViewFlinger从RV中删除. 并调用mScroller的abortAnimation()函数.
        2. 如果mLayout不为null, 调用其stopSmoothScroller()函数. (第一次初始化LM时不会走到这里.)
      2. 调用dispatchOnScrollStateChanged()函数.
        1. 如果mLayout不为null,调用其onScrollStateChanged()函数.
        2. 调用onScrollStateChanged()函数, 这个函数在RV中为空, RV的子view可以重写这个函数.
        3. 如果有scrollListener, 调用其`onScrollStateChanged()
        4. 如果mScrollListeners不为空, 对每个listener调用onScrollStateChanged()
  9. setAdapter. 设置Adapter.
    1. 调用setLayoutFrozen(), 参数为false. 即重新enable layout跟scroll.
      1. 调用setAdapterInternal()设置adapter.
        1. 如果之前有adpater, 调用相关的unregister函数.
        2. 如果与之前的不兼容, 或者需要recycle view.
          1. 如果有animator, 调用endAnimation.
          2. 如果layoutManager不为null, 调用其相关的remove函数.
          3. 调用recycler的clear函数.
        3. 调用新adapter的注册函数.
        4. 调用layoutmanager的onAdapterChange函数.
        5. 调用markKnowViewsInvalid()函数标记所有view invalid.
  10. setHasFixedSize()设为true表示RV的size不会受adapter内容的影响.

ItemDecoration

Home页"卡片"源码学习笔记

自定义控件TwoWayView

  1. 继承自RecyclerView.
  2. 构造函数默认使用了RV的构造函数.
  3. 重写setLayoutManager()函数, 必须为TwoWayLayoutManager的子类.

TwoWayLayoutManager

  1. 继承自LayoutManager.
  2. 可以设置orientation, 即横还是竖.
  3. 可以设置direction. 即从前向后还是从后向前.
  4. getTotalSpace() 获取RV的"有效空间", 有效空间指:

    1. 高度 - paddingTop - paddingBottom. (vertical)
    2. 宽度- pR - pL (horizontal)

    LayoutManager提供了一系列的API来获取RV的参数.

  5. getChildStart(child) 获取child的"开始"位置. 这个位置包含的内容部分: 开始margin –> 修饰 –> child的实际开始位置(getTop()返回值)
  6. recycleChildrenFromStart() 当RV向底部滚动时, "前面"的view有可能就不在"视线"之内, 这时候可以调用该函数移除"前面"的view. 移除原则:
    1. 记录在RV的paddingTop之前的子view的数量.
    2. 对所有的子view调用removeAndRecyleView()函数.
    3. 每做一次第2步就更新一下mLayoutStart值,该值用于记录layout的起始位置.

DividerItemDecoration

该类继承自ItemDecoration, 用于修饰每个view. 支持水平和竖直方向的分割drawable设置.

  1. 该类实现了onDrawOver(), 用于在item之后draw "修饰".

文章阅读笔记

  1. RV动画详解
    1. LayoutTransition是Framework用于进行动画转化的.由于根据 layout之前和之后的状态进行动画. 但不适用于RV.
    2. prelayout阶段, RV会让layoutManager layout之前的状态, 但是 会提供一些信息. 请求类似这样: layout, 但是, C已经被删除. 这样, layoutmanager会layout一个新的view(例如G)来补充C留下的空间. 但是:
      1. RV仍然表现的好想C仍然存在于adapter. 例如调用getViewForPosition(2) 仍然返回C.
    3. postLayout阶段. layoutmanager重新layout, 这时候,'C'不存在. getViewForPosition()返回正确的
    4. 每次调用layoutManager的onLayoutChildren()函数. 所有的view都会 暂时detach并重新layout, 但是由于未变化的view其"测量数据"不变, 所以 不会被重新测量. 这样使整个layout并不复杂.
    5. LinearLayout在postLayout的完成阶段, layoutManager会调用getScrapList()函数获取 没有被layout但仍然在adapter中的数据, 然后layout这些.
    6. 可以在onLayoutChildren()里调用 addDisappearing() 函数来告知RV这些view 可以在动画完成之后删除掉. RV也会把该view加入到 hidden 列表中. 这样它就不在layoutmanager的children中了.
    7. 当child被layoutmananger移除时, RV仍然将其保留, 但是 对layoutmanager隐藏.
      1. 当LM调用其getChildCount()函数时, RV返回的是其children 数量减去隐藏的children数量.
      2. 当LM调用getChildAt()函数时, RV也会跳过隐藏的view,返回 正确的child.
      3. 当LM调用addView(view,index), RV同样也会插入到正确的位置.
      4. 动画完成时, RV会移除并回收隐藏的view.

APK结构

结构

.dex文件, resources, assets, 证书, mainifest文件, lib目录

dvm vs jvm

  1. DVM is the software that runs the applications on Android devices. It is open source.
  2. JVM is Stack based whereas DVM is Register based. Stack-based machines require more instructions(i.e. larger instruction set) than register-based machines for the same task. Whereas, each instruction in the register-based machines are larger.
  3. Mobile Devices have limited memory and power , low CPU speed as compared to Desktops. So, DVM is more efficient in terms of memory usage and performance.
  4. While running multiple instances of the DVM, DVM is supposed to be more efficient. In mobile phones, each application runs on its own sand box and thus each active application requires its own DVM instance. Single instance of JVM is shared with multiple applications.
  5. Java compiler complies Java source code into .class files. Then dx (dexer) tool, which is a part of the Android SDK, processes the .class files into .DEX files that contains Dalvik bytecode. DVM runs .Dex file (Dalvik Executable File) whereas JVM runs .class files. With dx tool, all the classes of the application are packed into one file. Also, all the classes in the same DEX file share field, method etc if they are same and these classes are loaded by the same class loader instance. Thus dx tool eliminate all the redundant information that is present in the classes.
  6. JVM supports multiple operating systems. DVM only supports Android operating system.
  7. In case if DVM, executable is APK whereas in JVM, executable is JAR.
  8. In case of JVM, each class has its own constant pool. In the dex conversion, all of the separate constant pools are collapsed into a single shared constant pool which is used by all classes in an application. Thus, storage space to store the shared constant pool is also conserved.

编译工作流

思考

前台service的好处?

如何设计网络框架?

ImageLoader如何同步?

Created At <2016-10-31 Mon 23:25> by Luis Xu. Email: xuzhengchaojob@gmail.com