Android | 带你探究 LayoutInflater 布局解析原理

简介: Android | 带你探究 LayoutInflater 布局解析原理

目录

image.png


1. 获取 LayoutInflater 对象


@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
    ...
}
复制代码


首先,你要获得 LayoutInflater  的实例,由于 LayoutInflater 是抽象类,不能直接创建对象,因此这里总结一下获取 LayoutInflater 对象的方法。具体如下:


  • 1. View.inflate(...)


public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
复制代码
  • 2. Activity#getLayoutInflater()
public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}
复制代码
  • 3. PhoneWindow#getLayoutInflater()
private LayoutInflater mLayoutInflater;
public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}
public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}
复制代码
  • 4. LayoutInflater#from(Context)
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}
复制代码


可以看到,前面 3 种方法最后走到LayoutInflater#from(Context),这其实也是平时用的最多的方式。现在,我们看getSystemService(...)内的逻辑:

ContextImpl.java


@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
复制代码


SystemServiceRegistry.java


private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>();
static {
    ...
    1. 注册 Context.LAYOUT_INFLATER_SERVICE 与服务获取器
    关注点:CachedServiceFetcher
    关注点:PhoneLayoutInflater
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            注意:getOuterContext(),参数使用的是 ContextImpl 的代理对象,一般是 Activity
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});
    ...
}
2. 根据 name 获取服务对象
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
注册服务与服务获取器
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
3. 服务获取器创建对象
static abstract interface ServiceFetcher<T> {
    T getService(ContextImpl ctx);
}
复制代码


可以看到,ContextImpl 内部通过 SystemServiceRegistry 来获取服务对象,逻辑并不复杂:


1、静态代码块注册了 name - ServiceFetcher 的映射

2、根据 name 获得 ServiceFetcher

3、ServiceFetcher 创建对象


ServiceFetcher 的子类有三种类型,它们的getSystemService()都是线程安全的,主要差别体现在 单例范围,具体如下:


ServiceFetcher子类 单例范围 描述 举例
CachedServiceFetcher ContextImpl域 / LayoutInflater、LocationManager等(最多)
StaticServiceFetcher 进程域 / InputManager、JobScheduler等
StaticApplicationContextServiceFetcher 进程域 使用 ApplicationContext 创建服务 ConnectivityManager


对于 LayoutInflater 来说,服务获取器是 CachedServiceFetcher 的子类,最终获得的服务对象为 PhoneLayoutInflater


image.png

这里有一个重点,这句代码非常隐蔽,要留意:


return new PhoneLayoutInflater(ctx.getOuterContext());
复制代码


LayoutInflater.java


public Context getContext() {
    return mContext; 
}
protected LayoutInflater(Context context) {
    mContext = context;
    initPrecompiledViews();
}
复制代码


可以看到,实例化 PhoneLayoutInflater 时使用了 getOuterContext(),也就是参数使用的是 ContextImpl 的代理对象,一般就是 Activity 了。也就是说,在 Activity / Fragment / View / Dialog 中,获取LayoutInflater#getContext(),返回的就是 Activity。


小结:


  • 1、获取 LayoutInflater 对象只有通过LayoutInflater.from(context),内部委派给Context#getSystemService(...),线程安全;
  • 2、使用同一个 Context 对象,获得的 LayoutInflater 是单例;
  • 3、LayoutInflater 的实现类是 PhoneLayoutInflater。


2. inflate(...) 主流程源码分析


上一节,我们分析了获取 LayoutInflater 对象的过程,现在我们可以调用inflate()进行布局解析了。LayoutInflater#inflate(...)有多个重载方法,最终都会调用到:


public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    1. 解析预编译的布局
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    2. 构造 XmlPull 解析器 
    XmlResourceParser parser = res.getLayout(resource);
    try {
    3. 执行解析
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
复制代码


  1. tryInflatePrecompiled(...)是解析预编译的布局,我后文再说;
  2. 构造 XmlPull 解析器 XmlResourceParser
  3. 执行解析,是解析的主流程


提示: 在这里,我剔除了与 XmlPull 相关的代码,只保留了我们关心的逻辑:


public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    1. 结果变量
    View result = root;
    2. 最外层的标签
    final String name = parser.getName();
    3. <merge>
    if (TAG_MERGE.equals(name)) {
        3.1 异常
        if (root == null || !attachToRoot) {
            throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
        }
        3.2 递归执行解析
        rInflate(parser, root, inflaterContext, attrs, false);
    } else {
        4.1 创建最外层 View
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        ViewGroup.LayoutParams params = null;
        if (root != null) {
            4.2 创建匹配的 LayoutParams
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                4.3 如果 attachToRoot 为 false,设置LayoutParams
                temp.setLayoutParams(params);
            }
        }
        5. 以 temp 为 root,递归执行解析
        rInflateChildren(parser, temp, attrs, true);
        6. attachToRoot 为 true,addView()
        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }
        7. root 为空 或者 attachToRoot 为 false,返回 temp
        if (root == null || !attachToRoot) {
            result = temp;
        }
    }
    return result;
}
-> 3.2
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) {
    while(parser 未结束) {
        if (TAG_INCLUDE.equals(name)) {
            1) <include>
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            2) <merge>
            throw new InflateException("<merge /> must be the root element");
        } else {
            3) 创建 View 
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            4) 递归
            rInflateChildren(parser, view, attrs, true);
            5) 添加到视图树
            viewGroup.addView(view, params);
        }
    }
}
-> 5. 递归执行解析
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
复制代码


关于 <include> & <merge>,我后文再说。对于参数 root & attachToRoot的不同情况,对应得到的输出不同,我总结为一张图:


image.png

3. createViewFromTag():从  到 View


第 2 节 主流程代码中,用到了 createViewFromTag(),它负责由  创建 View 对象:


已简化
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
    1. 应用 ContextThemeWrapper 以支持 android:theme
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }
    2. 先使用 Factory2 / Factory 实例化 View,相当于拦截
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }
    3. 使用 mPrivateFactory 实例化 View,相当于拦截
    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }
    4. 调用自身逻辑
    if (view == null) {
        if (-1 == name.indexOf('.')) {
            4.1 <tag> 中没有.
            view = onCreateView(parent, name, attrs);
        } else {
            4.2 <tag> 中有.
            view = createView(name, null, attrs);
        }
    }
    return view;     
}
-> 4.2 <tag> 中有.
构造器方法签名
static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};
缓存 View 构造器的 Map
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
            new HashMap<String, Constructor<? extends View>>();
public final View createView(String name, String prefix, AttributeSet attrs) {
    1) 缓存的构造器
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;
    2) 新建构造器
    if (constructor == null) {
        2.1) 拼接 prefix + name 得到类全限定名
        clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
        2.2) 创建构造器对象
        constructor = clazz.getConstructor(mConstructorSignature);
        constructor.setAccessible(true);
        2.3) 缓存到 Map
        sConstructorMap.put(name, constructor);
    }
    3) 实例化 View 对象
    final View view = constructor.newInstance(args);
    4) ViewStub 特殊处理
    if (view instanceof ViewStub) {
        // Use the same context when inflating ViewStub later.
        final ViewStub viewStub = (ViewStub) view;
        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
    }
    return view;
}
----------------------------------------------------
-> 4.1 <tag> 中没有.
PhoneLayoutInflater.java
private static final String[] sClassPrefixList = {
    "android.widget.",
    "android.webkit.",
    "android.app."
};
已简化
protected View onCreateView(String name, AttributeSet attrs) {
    for (String prefix : sClassPrefixList) {
        View view = createView(name, prefix, attrs);
            if (view != null) {
                return view;
            }
    }
    return super.onCreateView(name, attrs);
}
复制代码


  1. 应用 ContextThemeWrapper 以支持android:theme,这是处理针对特定 View 设置主题;
  2. 使用 Factory2 / Factory 实例化 View,相当于拦截,我后文再说;
  3. 使用 mPrivateFactory 实例化View,相当于拦截,我后文再说;
  4. 调用 LayoutInflater 自身逻辑,分为:
  • 4.1  中没有.,这是处理<linearlayout>、<TextView>等标签,依次尝试拼接 3 个路径前缀,进入 3.2 实例化 View
  • 4.2  中有.,真正实例化 View 的地方,主要分为 4 步:


1) 缓存的构造器
2) 新建构造器
3) 实例化 View 对象
4) ViewStub 特殊处理
复制代码


小结:

  • 使用 Factory2 接口可以拦截实例化 View 对象的步骤;
  • 实例化 View 的优先顺序为:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
  • 使用反射实例化 View 对象,同时构造器对象做了缓存;


image.png

4. Factory2 接口


现在我们来讨论Factory2接口,上一节提到,Factory2可以拦截实例化 View 的步骤,在 LayoutInflater 中有两个方法可以设置:LayoutInflater.java


方法1:
public void setFactory2(Factory2 factory) {
    if (mFactorySet) {
        关注点:禁止重复设置
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}
方法2 @hide
public void setPrivateFactory(Factory2 factory) {
    if (mPrivateFactory == null) {
        mPrivateFactory = factory;
    } else {
        mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
    }
}
复制代码


现在,我们来看源码中哪里调用这两个方法:

4.1 setFactory2()

在 AppCompatActivity & AppCompatDialog 中,相关源码简化如下:

AppCompatDialog.java


@Override
protected void onCreate(Bundle savedInstanceState) {
    设置 Factory2
    getDelegate().installViewFactory();
    super.onCreate(savedInstanceState);
    getDelegate().onCreate(savedInstanceState);
}
复制代码

AppCompatActivity.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    设置 Factory2
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    夜间主题相关
    if (delegate.applyDayNight() && mThemeId != 0) {
        if (Build.VERSION.SDK_INT >= 23) {
            onApplyThemeResource(getTheme(), mThemeId, false);
        } else {
            setTheme(mThemeId);
        }
    }
    super.onCreate(savedInstanceState);
}
复制代码

AppCompatDelegateImpl.java

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        关注点:设置 Factory2 = this(AppCompatDelegateImpl)
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
        }
    }
}
复制代码

LayoutInflaterCompat.java

public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    inflater.setFactory2(factory);
    if (Build.VERSION.SDK_INT < 21) {
        final LayoutInflater.Factory f = inflater.getFactory();
        if (f instanceof LayoutInflater.Factory2) {
            forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
        } else {
            forceSetFactory2(inflater, factory);
        }
    }
}
复制代码


可以看到,在 AppCompatDialog & AppCompatActivity 初始化时,都通过setFactory2()设置了拦截器,设置的对象是 AppCompatDelegateImpl:

AppCompatDelegateImpl.java


已简化
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }
    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }
    }
    委托给 AppCompatViewInflater 处理
    return mAppCompatViewInflater.createView(...)
}
复制代码


AppCompatViewInflater  与 LayoutInflater 的核心流程差不多,主要差别是前者会<TextView>等标签解析为AppCompatTextView对象:

AppCompatViewInflater.java


final View createView(...) {
    ...
    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            break;
        ...
        default:
            view = createView(context, name, attrs);
    }
    return view;
}
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}
复制代码


4.2 setPrivateFactory()


setPrivateFactory()是 hide 方法,在 Activity 中调用,相关源码简化如下:

Activity.java


final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
final void attach(Context context, ActivityThread aThread,...) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    关注点:设置 Factory2
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
}
复制代码


可以看到,这里设置的 Factory2 其实就是 Activity 本身(this),这说明 Activity 也实现了 Factory2 :


public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,...{
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }
        return mFragments.onCreateView(parent, name, context, attrs);
    }
}
复制代码


原来<fragment>标签的处理是在这里设置的 Factory2 处理的,关于FragmentController#onCreateView(...)内部如何生成 Fragment 以及返回 View 的逻辑,我们在这篇文章里讨论,请关注:《你真的懂 Fragment 吗?—— AndroidX Fragment 核心原理分析》


小结:


  • 使用 setFactory2() 和 setPrivateFactory() 可以设置 Factory2 接口(拦截器),其中同一个 LayoutInflater 的setFactory2()不能重复设置,setPrivateFactory() 是 hide 方法;
  • AppCompatDialog & AppCompatActivity 初始化时,调用了setFactory2(),会将一些<tag>转换为AppCompat版本;
  • Activity 初始化时,调用了setPrivateFactory(),用来处理<fragment>标签。

image.png

5. <include> & <merge> & <ViewStub>


这一节,我们专门来讨论<include> & <merge> & <ViewStub>的用法与注意事项:

5.1 <include> 布局重用

5.2 <merge> 降低布局层次

5.3 <viewstub> 布局懒加载

Editting...


6. 总结


  • 应试建议
  1. 理解 获取 LayoutInflater 对象的方式,知晓 3 种 getSystemService() 单例域的区别,其中 Context 域是最多的,LayoutInflater 是属于 Context 域。
  2. 重点理解 LayoutInflater 布局解析的 核心流程
  3. Factory2 是一个很实用的接口,需要掌握通过 setFactory2() 拦截布局解析 的技巧。


image.png



目录
相关文章
|
11月前
|
数据采集 监控 API
告别手动埋点!Android 无侵入式数据采集方案深度解析
传统的Android应用监控方案需要开发者在代码中手动添加埋点,不仅侵入性强、工作量大,还难以维护。本文深入探讨了基于字节码插桩技术的无侵入式数据采集方案,通过Gradle插件 + AGP API + ASM的技术组合,实现对应用性能、用户行为、网络请求等全方位监控,真正做到零侵入、易集成、高稳定。
1080 100
|
安全 算法 网络协议
解析:HTTPS通过SSL/TLS证书加密的原理与逻辑
HTTPS通过SSL/TLS证书加密,结合对称与非对称加密及数字证书验证实现安全通信。首先,服务器发送含公钥的数字证书,客户端验证其合法性后生成随机数并用公钥加密发送给服务器,双方据此生成相同的对称密钥。后续通信使用对称加密确保高效性和安全性。同时,数字证书验证服务器身份,防止中间人攻击;哈希算法和数字签名确保数据完整性,防止篡改。整个流程保障了身份认证、数据加密和完整性保护。
|
12月前
|
存储 消息中间件 人工智能
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
342 11
【05】AI辅助编程完整的安卓二次商业实战-消息页面媒体对象(Media Object)布局实战调整-按钮样式调整实践-优雅草伊凡
|
12月前
|
XML 存储 Java
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
274 3
【06】AI辅助编程完整的安卓二次商业实战-背景布局变更增加背景-二开发现页面跳转逻辑-替换剩余图标-优雅草卓伊凡
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
561 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
1557 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
机器学习/深度学习 数据可视化 PyTorch
深入解析图神经网络注意力机制:数学原理与可视化实现
本文深入解析了图神经网络(GNNs)中自注意力机制的内部运作原理,通过可视化和数学推导揭示其工作机制。文章采用“位置-转移图”概念框架,并使用NumPy实现代码示例,逐步拆解自注意力层的计算过程。文中详细展示了从节点特征矩阵、邻接矩阵到生成注意力权重的具体步骤,并通过四个类(GAL1至GAL4)模拟了整个计算流程。最终,结合实际PyTorch Geometric库中的代码,对比分析了核心逻辑,为理解GNN自注意力机制提供了清晰的学习路径。
1042 7
深入解析图神经网络注意力机制:数学原理与可视化实现
|
机器学习/深度学习 缓存 自然语言处理
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
Tiktokenizer 是一款现代分词工具,旨在高效、智能地将文本转换为机器可处理的离散单元(token)。它不仅超越了传统的空格分割和正则表达式匹配方法,还结合了上下文感知能力,适应复杂语言结构。Tiktokenizer 的核心特性包括自适应 token 分割、高效编码能力和出色的可扩展性,使其适用于从聊天机器人到大规模文本分析等多种应用场景。通过模块化设计,Tiktokenizer 确保了代码的可重用性和维护性,并在分词精度、处理效率和灵活性方面表现出色。此外,它支持多语言处理、表情符号识别和领域特定文本处理,能够应对各种复杂的文本输入需求。
1742 6
深入解析Tiktokenizer:大语言模型中核心分词技术的原理与架构
|
XML JavaScript Android开发
【Android】网络技术知识总结之WebView,HttpURLConnection,OKHttp,XML的pull解析方式
本文总结了Android中几种常用的网络技术,包括WebView、HttpURLConnection、OKHttp和XML的Pull解析方式。每种技术都有其独特的特点和适用场景。理解并熟练运用这些技术,可以帮助开发者构建高效、可靠的网络应用程序。通过示例代码和详细解释,本文为开发者提供了实用的参考和指导。
685 15
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
11076 2

推荐镜像

更多