ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Android 相关源码分析,真服了

2021-09-05 14:01:47  阅读:166  来源: 互联网

标签:... return 源码 真服了 mLayout Android view holder View



public class OkHttpUserService implements UserService {

   @Override

   Call<User> getASingleUser(String username) {

       String url = buildUrl(annotations, username);

       okhttp3.Call okHttpCall = new OkHttpClient().newCall(new Request.Builder().url(url).get().build());

       return new Call<User>(okHttpCall);

   }

} 

也就是说生成一个实现类,实现每个 API 方法,每个方法的内部实现 okhttp3.Call 的组装和调用,完成请求的执行以及结果的解析和封装

实现这样的代码生成有两种方式,一种是利用 编译时注解和 APT 技术 在编译时自动生成这样的实现类然后手动创建对象,一种是利用 运行时注解和动态代理技术 在运行时动态生成代理对象。Retrofit 选择了后者,即在运行时通过反射解析接口并生成代理对象

对于开发者,尤其是 Android 开发者,对性能的要求甚至到了苛刻的地步,对于枚举和反射等技术的使用更是敏感。传统方式虽然也能完成枚举和反射的工作,也会比枚举和反射更快一点,但是复杂度要更高,直观性不好。而枚举反射虽然性能差了一些但是要更加的简单直观,我觉得这是一个权衡问题(Trade off)。很多时候总是要做取舍的,就像有人花费了大量的时间、精力和资源把性能提升了一二十,可能在我看来不值得也没有必要。我用简单的方式实现了一个功能,可能在他人看来不够完美精致。所以我觉得以包容的态度看待技术还是很重要的

create

Retrofit 的 create 就是用来创建代理对象的,这个代理对象封装并实现了具体的网络请求逻辑。通过 设计模式:代理模式 一文我们已经知道,动态代理会把代理对象的 hashCode(), equals(), toString(), getASingleUser() 方法的调用都转发给 InvocationHandlerinvoke 处理,所以只需要在 invoke 完成对 API 的解析和请求逻辑的封装即可,而 hashCode(), equals(), toString() 这些方法不需要特别处理:


if (method.getDeclaringClass() == Object.class) {

  return method.invoke(this, args);

} 

对于 getASingleUser() 方法,只需要把这个方法封装为 ServiceMethod 并调用它的 invoke 实现


loadServiceMethod(method).invoke(args); 

HttpServiceMethod 或它的子类 CallAdapted 会利用 RequestFactory 完成对方法注解的解析以及请求 URL、Header 等的拼装,最终和 okhttp3.Call.Factory 一起完成对 okhttp3.Call 的组装


RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

...

for (Annotation annotation : method.getAnnotations()) {

  parseMethodAnnotation(annotation);

}

...

private void parseMethodAnnotation(Annotation annotation) {

  ...

  if (annotation instanceof GET) {

    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);

  }

  ...

}

...

RequestBuilder requestBuilder =

    new RequestBuilder(

        httpMethod,

        baseUrl,

        relativeUrl,

        headers,

        contentType,

        hasBody,

        isFormEncoded,

        isMultipart);

...

return requestBuilder.get().tag(Invocation.class, new Invocation(method, argumentList)).build();

...

if (callFactory == null) {

  callFactory = new OkHttpClient();

}

...

okhttp3.Call call = callFactory.newCall(requestFactory.create(args)); 

至此 Retrofit 已经组装好 okhttp3.Call 了,已经可以用了

而 Retrofit 灵活的一点是可以对这个 okhttp3.Call 进行封装,如封装成 retrofit2.CallCompletableFutureio.reactivex.Observable 等,这样你就可以随意选择自己喜欢的异步技术进行网络请求了

Retrofit 利用 CallAdapter.Factory 完成对 okhttp3.Call 的封装,利用 Converter.Factory 完成对请求响应的解析

Retrofit 默认有两个 CallAdapter.Factory,一个可以把 okhttp3.Call 封装成 CompletableFuture,一个可以把 okhttp3.Call 封装成 retrofit2.Call


List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);

callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));



List<? extends CallAdapter.Factory> defaultCallAdapterFactories(

    @Nullable Executor callbackExecutor) {

  DefaultCallAdapterFactory executorFactory = new DefaultCallAdapterFactory(callbackExecutor);

  return hasJava8Types

      ? asList(CompletableFutureCallAdapterFactory.INSTANCE, executorFactory)

      : singletonList(executorFactory);

} 

Retrofit 默认有两个转换器,一个可以把响应转成 ResponseBody,一个可以把响应转成 Optional


converterFactories.add(new BuiltInConverters());

converterFactories.addAll(this.converterFactories);

converterFactories.addAll(platform.defaultConverterFactories());

...

List<? extends Converter.Factory> defaultConverterFactories() {

  return hasJava8Types ? singletonList(OptionalConverterFactory.INSTANCE) : emptyList();

} 

Retrofit 分析

Retrofit 最大个贡献是改变了描述 API 的方式,尤其是描述 RESTful API 的方式,让客户端对 API 的调用更加的简单、直观、安全

Retrofit 这种 “ 接口 + 抽象方法 + 注解 ” 的方式虽然可以实现 API 的描述,但是不能可视化,不能结构化,不能文档化,不能直接 mock,不能自动化测试,不能指定公共参数。所以我觉得换成 “ 配置文件(JSON?) + GUI 插件 ” 等其它方式要更好一点

ViewStub 29


只是一个占位,继承 View,不绘制(setWillNotDraw(true)),且宽高为 0(setMeasuredDimension(0, 0)),inflate() 就是从父容器中移除自己并 inflate 给定的 view 到自己的本来的位置(index 和 layoutParams),由于移除后就不知道自己的父容器了所以 inflate() 只能调用一次。ViewStub 的 setVisibility() 方法一般不建议使用,如果用,那么在没 inflate() 的情况下会自动调用 inflate()

Handler 29


Looper.prepare(); 可以给一个普通线程关联一个消息队列,Looper.loop(); 开始循环处理消息队列中的消息,new 一个 Handler 可以发送和处理消息,创建 Handler 需要指定 Looper,如果不指定那么表明是针对当前线程的 Looper 的,主线程有个创建好的 Looper.getMainLooper() 单例可以直接用

Looper.loop(); 是个死循环,循环获取队列中的消息,转发给消息的 target 去处理,也就是当初发送它的 Handler 去处理

Handler 处理消息的线程就是它关联的 Looper 所在的线程,也就是说创建 Handler 时传的 Looper 在哪个线程调用了 Looper.loop();,那么就在哪个线程回调 handleMessage()


for (;;) {

    Message msg = queue.next(); // might block

    ...

    msg.target.dispatchMessage(msg);

} 

queue.next() 最终调用的是名为 nativePollOnce() 的 native 方法,而该方法使用的是 epoll_wait 系统调用,表示自己在等待 I/O 事件,线程可以让出 CPU,等到 I/O 事件来了才可以进入 CPU 执行

而每次有新消息来的时候 enqueueMessage(),最终都会调用名为 nativeWake() 的 native 方法,该方法会产生 I/O 事件唤醒等待的线程

所以 nativePollOnce() / nativeWake() 就像对象的 wait() / notify() 一样,死循环并不会一直占用 CPU,如果没有消息要处理,就让出 CPU 进入休眠,只有被唤醒的时候才会进入 CPU 处理工作

IdleHandler 可以在消息队列中的消息都处理完了,进入休眠之前做一些工作,所以可以利用 Looper.myQueue().addIdleHandler() 做一些延迟任务,如在主线程中延迟初始化一些大对象或做一些可能耗时的操作

Handler 的延迟发消息功能如 sendMessageDelayed()postDelayed() 是通过延迟唤醒实现的,在消息入队的时候就确定好消息要唤醒的时间,即 msg.when = SystemClock.uptimeMillis() + delayMillis,插入自己在队列中应该出现的位置,在取下一个消息时延迟 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE) 时间去取即可

线程池技术保持线程活跃也是通过 epoll 机制实现的,死循环中阻塞地从队列中取消息,利用 LockSupport.park()Conditionawait() 就能让线程保持活跃的同时让出 CPU 等待事件

RecyclerView 1.1.0


术语

目的: 在有限的窗口中显示大量的数据

Adapter: 为数据集中的数据项提供对应的 View

Position: 数据项在数据集中的位置

Index: View 在容器中的位置

Binding: 为 position 位置的数据准备对应 View 的过程

Recycle (view): 之前使用过的 View 可能会被放到缓存中,之后在显示相同类型数据时可以直接拿出来重用

Scrap (view): 进入临时 detached 状态的 View,不用完全 detached 就能重用

Dirty (view): 在被显示前必须重新绑定的 View

LayoutManager 维护的 LayoutPosition 理论上和 Adapter 维护的 AdapterPosition 是一样的,但是对数据集 position 的修改是马上就起效的,而修改布局的 position 需要一定时间。所以最好在写 LayoutManager 的时候用 LayoutPosition,写 Adapter 的时候用 AdapterPosition

如果数据集发生了变化,而你想通过 Diff 算法提升性能(只刷新必要的 View),那么可以直接使用 ListAdapter 这个 Adapter,它会在后台线程中比较新 List 和 旧 List 从而自动完成局部更新。或者在自己的 Adapter 中直接使用 AsyncListDiffer 实现。再或者直接使用 DiffUtil 工具比较列表也可以,更灵活,但也更繁琐

如果列表是有序的,那么使用 SortedList 可能比使用 List 要更好一些

onMeasure

作为容器,RecyclerView 要干两件事,在 onMeasure() 中确定自己和孩子的尺寸,在 onLayout() 中布局孩子的位置

如果没指定 LayoutManager 或者它的宽高都是 MeasureSpec.EXACTLY 的,那么就跟普通 View 一样默认测量就行了


if (mLayout == null) {

    defaultOnMeasure(widthSpec, heightSpec);

    return;

}

if (mLayout.isAutoMeasureEnabled()) {

    final int widthMode = MeasureSpec.getMode(widthSpec);

    final int heightMode = MeasureSpec.getMode(heightSpec);

    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

    final boolean measureSpecModeIsExactly =

            widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;

    if (measureSpecModeIsExactly || mAdapter == null) {

        return;

    }

    if (mState.mLayoutStep == State.STEP_START) {

        dispatchLayoutStep1();

    }

    mLayout.setMeasureSpecs(widthSpec, heightSpec);

    mState.mIsMeasuring = true;

    dispatchLayoutStep2();

    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

    if (mLayout.shouldMeasureTwice()) {

        mLayout.setMeasureSpecs(

                MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),

                MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));

        mState.mIsMeasuring = true;

        dispatchLayoutStep2();

        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

    }

} else {

    if (mHasFixedSize) {

        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

        return;

    }

    ...

} 

大部分 LayoutManagerisAutoMeasureEnabled() 都是 true,表示使用 RecyclerView 的 自动测量机制 进行测量,此时,LayoutManager#onMeasure(Recycler, State, int, int) 内部只是调用了 defaultOnMeasure(),不要重写这个方法。false 的话就得重写

第一步 dispatchLayoutStep1() 主要处理 Adapter 的更新和动画运行 processAdapterUpdatesAndSetAnimationFlags();,保存动画过程中的 View 状态 mViewInfoStore.addToPreLayout(holder, animationInfo);,必要的话还会预布局并保存信息 recordPreLayoutInformation()

第二步 dispatchLayoutStep2() 对孩子进行真正的测量和布局 mLayout.onLayoutChildren(mRecycler, mState);

你会发现 dispatchLayoutStep2() 可能被调用多次,你也会发现 dispatchLayoutStep1()dispatchLayoutStep2() 既有测量的功能也有布局的功能,虽然在 measure 里布局有点奇怪,但是在真正 layout 的时候能省这两步时间

onLayout


onLayout() 中只是调用 dispatchLayout() 方法真正开始对子 View 进行布局


void dispatchLayout() {

    if (mAdapter == null) {

        Log.e(TAG, "No adapter attached; skipping layout");

        return;

    }

    if (mLayout == null) {

        Log.e(TAG, "No layout manager attached; skipping layout");

        return;

    }

    mState.mIsMeasuring = false;

    if (mState.mLayoutStep == State.STEP_START) {

        dispatchLayoutStep1();

        mLayout.setExactMeasureSpecsFrom(this);

        dispatchLayoutStep2();

    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()

            || mLayout.getHeight() != getHeight()) {

        // First 2 steps are done in onMeasure but looks like we have to run again due to

        // changed size.

        mLayout.setExactMeasureSpecsFrom(this);

        dispatchLayoutStep2();

    } else {

        // always make sure we sync them (to ensure mode is exact)

        mLayout.setExactMeasureSpecsFrom(this);

    }

    dispatchLayoutStep3();

} 

最后一步 dispatchLayoutStep3(); 记录并开始 View 动画 mViewInfoStore.process(mViewInfoProcessCallback);,然后做一些必要的清理工作

onDraw

onDraw() 中只需要绘制 ItemDecoration 即可:


mItemDecorations.get(i).onDraw(c, this, mState); 

但是对于边界装饰的绘制或者 ItemDecoration#onDrawOver() 的实现就要重写 draw() 方法了


mItemDecorations.get(i).onDrawOver(c, this, mState); 

缓存

在第二步布局时 dispatchLayoutStep2() 会调用 mLayout.onLayoutChildren(mRecycler, mState);,所以在 onLayoutChildren() 中完成 View 的获取

LinearLayoutManageronLayoutChildren() 为例,它的布局算法就是先检查孩子和其它变量,寻找锚点坐标,然后从尾到头的方向填充以及从头到尾的方向填充(fill()),然后滚动以满足需要

fill() 是一个神奇的方法,它可以在指定方向上填充满子 View


int fill(RecyclerView.Recycler recycler, LayoutState layoutState,

         RecyclerView.State state, boolean stopOnFocusable) {

    ...

    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

        ...

        layoutChunk(recycler, state, layoutState, layoutChunkResult);

        ...

    }

    ...

} 

layoutChunk() 中就是取 View 的过程 View view = layoutState.next(recycler);


View next(RecyclerView.Recycler recycler) {

    if (mScrapList != null) {

        return nextViewFromScrapList();

    }

    final View view = recycler.getViewForPosition(mCurrentPosition);

    mCurrentPosition += mItemDirection;

    return view;

}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,

                                                 boolean dryRun, long deadlineNs) {

    ...

    if (mState.isPreLayout()) {

        holder = getChangedScrapViewForPosition(position);

    }

    if (holder == null) {

        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

    }

    if (holder == null) {

        if (mAdapter.hasStableIds()) {

            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),

                    type, dryRun);

        }

        if (holder == null && mViewCacheExtension != null) {

            final View view = mViewCacheExtension

                    .getViewForPositionAndType(this, position, type);

            if (view != null) {

                holder = getChildViewHolder(view);

            }

        }

        if (holder == null) {

            holder = getRecycledViewPool().getRecycledView(type);

        }

        if (holder == null) {

            holder = mAdapter.createViewHolder(RecyclerView.this, type);

        }

    }

    ...

    return holder;

}

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {

    final int scrapCount = mAttachedScrap.size();

    for (int i = 0; i < scrapCount; i++) {

        final ViewHolder holder = mAttachedScrap.get(i);

        if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position

                && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {

            return holder;

        }

    }

    if (!dryRun) {

        View view = mChildHelper.findHiddenNonRemovedView(position);

        if (view != null) {

            final ViewHolder vh = getChildViewHolderInt(view);

            mChildHelper.unhide(view);

            mChildHelper.detachViewFromParent(layoutIndex);

            scrapView(view);

            return vh;

        }

    }

    final int cacheSize = mCachedViews.size();

    for (int i = 0; i < cacheSize; i++) {

        final ViewHolder holder = mCachedViews.get(i);

        if (!holder.isInvalid() && holder.getLayoutPosition() == position

                && !holder.isAttachedToTransitionOverlay()) {

            if (!dryRun) {

                mCachedViews.remove(i);

            }

            return holder;

        }

    }

    return null;

} 

所以取 ViewHolder(View) 的过程大体是这样的

  • getChangedScrapViewForPosition() isPreLayout

  • getScrapOrHiddenOrCachedHolderForPosition()

标签:...,return,源码,真服了,mLayout,Android,view,holder,View
来源: https://blog.csdn.net/m0_61068496/article/details/120114066

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有