自定义注解,打造自己的框架-下篇

2023-05-16

2019-12-04 22:53:52

文章目录

        • 结构
        • 声明注解
        • 声明注解处理器
        • 处理注解逻辑
        • 给使用者提供调用方法
        • 使用

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用

该系列源码在https://github.com/huangyuanlove/AndroidAnnotation

前两篇介绍了一丢丢自定义注解过程中使用到的东西,现在我们正式开始写框架。

结构

一般来讲,注解类框架(在Android)会分成三个部分,

  • annotation模块(java lib)

    用来存放注解类的,对应gradle引用annotationProcessor xxxx

  • compiler模块(java lib)

    用来存放生成辅助类的,对应gradle引用implementation xxxx

  • api模块(Android lib)

    用来提供给使用者的接口,对应gradle引用implementation xxxx,这个模块中会存在大量的反射调用,主要是调用生成的辅助类中的方法。

  • example(lib)模块和app(application)模块

    用来存放demo的,一般会区分在lib和application中的使用

也有一些框架会把api模块和compiler模块放在一块,无所谓了。。。。

首先来创建新的工程,然后创建对应的模块,注意:annotation和compiler模块是java lib,不要创建成Android lib

我们先来实现一下BindViewClickResponder这两个注解

声明注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int id() default -1;
    //在Android lib中生成的R文件中资源id不是final类型的,所以我们换个思路,
    //通过`getResources().getIdentifier()`来实现
    String idStr() default ""; 
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ClickResponder {
    int[] id() default {};
    String[] idStr() default {""};
}

声明注解处理器

声明一个processer类继承avax.annotation.processing.AbstractProcessor类,对这个类使用@AutoService(Processor.class)注解。
我们需要实现四个方法

@AutoService(Processor.class)
public class ViewInjectProcessor extends AbstractProcessor{

     /**
     * 每个Annotation Processor必须有一个空的构造函数。
     * 编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnvironment参数,
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }

    /**
     * 用于指定该处理器支持哪些注解
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
     * 用于指定支持的java版本,
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    /**
     * Annotation Processor扫描出的结果会存储进roundEnvironment中,可以在这里获取到注解内容,编写你的操作逻辑。
     * 注意:process()函数中不能直接进行异常抛出,并且该方法会执行多次
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return false;
    }
}

处理注解逻辑

声明几个属性

private Elements elementUtils;
private Map<TypeElement, List<Element>> bindViewMap = new HashMap<>();
private Map<TypeElement, List<Element>> clickResponderMap = new HashMap<>();

init方法中初始化用到的字段

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    elementUtils = processingEnv.getElementUtils();
}

getSupportedSourceVersion方法中返回支持的java版本

@Override
public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.RELEASE_7;
}

getSupportedAnnotationTypes方法中返回支持的注解

@Override
public Set<String> getSupportedAnnotationTypes() {
    Set<String> set = new LinkedHashSet<>();
    set.add(BindView.class.getCanonicalName());
    set.add(ClickResponder.class.getCanonicalName());
    return set;
}

这里为了方便以后在辅助类中添加各种方法,定义了TypeSpecWrapper

public class TypeSpecWrapper {

    private TypeSpec.Builder typeSpecBuilder;
    private String packageName;
    private HashMap<String, MethodSpec.Builder> methodBuildMap;

    public TypeSpec build(){
        for(Map.Entry<String,MethodSpec.Builder> entry:methodBuildMap.entrySet()){
            typeSpecBuilder.addMethod(entry.getValue().build());
        }
        return typeSpecBuilder.build();
    }

    public TypeSpec.Builder setTypeSpecBuilder(TypeSpec.Builder builder){
        this.typeSpecBuilder = builder;
        return builder;

    }

    public MethodSpec.Builder putMethodBuilder(MethodSpec.Builder builder){

        return methodBuildMap.put(builder.build().name,builder);
    }

    public MethodSpec.Builder getMethodBuilder(String methodName){
        return methodBuildMap.get(methodName);
    }

    public void writeTo(Filer filer){
        JavaFile javaFile = JavaFile.builder(packageName, build())
                .build();
        try {
            javaFile.writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public Map<String, MethodSpec.Builder> getMethodBuildMap(){
        return  methodBuildMap;
    }

    public TypeSpec.Builder getTypeSpecBuilder(){
        return typeSpecBuilder;
    }

    public TypeSpecWrapper(TypeSpec.Builder typeSpecBuilder,String packageName){
        this.typeSpecBuilder = typeSpecBuilder;
        this.packageName = packageName;
        methodBuildMap = new HashMap<>();
    }

}

process中生成辅助类并写入文件,

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
bindViewMap.clear();
clickResponderMap.clear();
Set<? extends Element> bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Set<? extends Element> onClickSet = roundEnvironment.getElementsAnnotatedWith(ClickResponder.class);

//收集BindView对应的信息
collectBindViewInfo(bindViewSet);
//收集ClickResponder对应的信息
collectClickResponderInfo(onClickSet);
//生成辅助类代码
generateCode();
//将生成代码写入文件
for (Map.Entry<TypeElement, TypeSpecWrapper> entry : typeSpecWrapperMap.entrySet()) {
          entry.getValue().writeTo(processingEnv.getFiler());
      }
return true;
}

因为会有多个类使用同一个注解,这里需要根据使用该注解的类名来保存对应的注解信息

private void collectBindViewInfo(Set<? extends Element> elements) {
    for (Element element : elements) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        List<Element> elementList = bindViewMap.get(typeElement);
        if (elementList == null) {
            elementList = new ArrayList<>();
            bindViewMap.put(typeElement, elementList);
        }
        elementList.add(element);
    }
}

private void collectClickResponderInfo(Set<? extends Element> elements) {
    for (Element element : elements) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        List<Element> elementList = clickResponderMap.get(typeElement);
        if (elementList == null) {
            elementList = new ArrayList<>();
            clickResponderMap.put(typeElement, elementList);
        }
        elementList.add(element);
    }
}

生成辅助类的内容,这里为了简单,将BindViewClickResponder以及之后的LongClickResponderCode注解处理都放在了bind方法中

 private void generateCode() {
    generateBindViewCode();
    generateClickResponderCode();
 }

private void generateBindViewCode() {
    for (TypeElement typeElement : bindViewMap.keySet()) {
        MethodSpec.Builder methodBuilder = generateBindMethodBuilder(typeElement);

        List<Element> elements = bindViewMap.get(typeElement);
        for (Element element : elements) {
            processorBindView(element, methodBuilder);
        }
    }
}

private void generateClickResponderCode() {
    for (TypeElement typeElement : clickResponderMap.keySet()) {
        MethodSpec.Builder methodBuilder = generateBindMethodBuilder(typeElement);

        List<Element> elements = clickResponderMap.get(typeElement);
        for (Element element : elements) {
            processorClickResponder(element, methodBuilder);

        }
    }

}

生成对应的辅助类,类名为使用该注解的类名+$ViewInjector

private TypeSpecWrapper generateTypeSpecWrapper(TypeElement typeElement) {
    final String pkgName = getPackageName(typeElement);
    final String clsName = getClassName(typeElement, pkgName) + "$ViewInjector";
    TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(clsName)
            .addModifiers(Modifier.PUBLIC);

    TypeSpecWrapper typeSpecWrapper = typeSpecWrapperMap.get(typeElement);
    if (typeSpecWrapper == null) {
        typeSpecWrapper = new TypeSpecWrapper(typeSpecBuilder, pkgName);
        typeSpecWrapperMap.put(typeElement, typeSpecWrapper);
    }
    return typeSpecWrapper;
}

生成辅助类的bind方法

private MethodSpec.Builder generateBindMethodBuilder(TypeElement typeElement) {
    TypeSpecWrapper typeSpecWrapper = generateTypeSpecWrapper(typeElement);
    MethodSpec.Builder methodBuilder = typeSpecWrapper.getMethodBuilder("bind");
    if (methodBuilder == null) {
        methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addParameter(ClassName.get(typeElement.asType()), "target", Modifier.FINAL)
                .addParameter(ClassName.get("android.view", "View"), "view")
                .addStatement("int resourceID = 0");
        typeSpecWrapper.putMethodBuilder(methodBuilder);
    }
    return methodBuilder;

}

对于BindView来讲,就是通过findViewById来获取到对应的控件,然后赋值给对应的字段。
这里我们为了能在lib中使用,对于没有传入id的属性,通过getIdentifier方法来获取到对应的资源id。

private void processorBindView(Element element, MethodSpec.Builder methodBuilder) {
    VariableElement variableElement = (VariableElement) element;
    String varName = variableElement.getSimpleName().toString();
    String varType = variableElement.asType().toString();
    BindView bindView = variableElement.getAnnotation(BindView.class);

    int params = bindView.id();
    //使用注解没有传入id的属性值,则使用isStr值来获取对应的资源id。
    //严谨来讲,这里需要判断一下isStr是不是空串,如果是空串,则直接抛出异常,终止编译
    if (params <= 0) {
        String idStr = bindView.idStr();
        methodBuilder.addStatement("resourceID = view.getResources().getIdentifier($S,$S, view.getContext().getPackageName())", idStr, "id");

    } else {
        methodBuilder.addStatement("resourceID = ($L)", params);
    }
    methodBuilder.addStatement("target.$L = ($L) view.findViewById(resourceID)", varName, varType);

}

对于ClickResponder来讲,就是通过setOnClickListener对对应的控件设置点击方法。由于可能存在多个控件使用同一个响应点击的方法,这里传入的都是资源数组
同样我们为了能在lib中使用,对于没有传入id的属性,通过getIdentifier方法来获取到对应的资源id。

private void processorClickResponder(Element element, MethodSpec.Builder methodBuilder) {
    ExecutableElement executableElement = (ExecutableElement) element;
    ClickResponder clickView = executableElement.getAnnotation(ClickResponder.class);
    int[] ids = clickView.id();
    String[] idStrs = clickView.idStr();


    if (ids.length > 0) {

        for (int id : ids) {
            if (id == 0) {
                continue;
            }
            MethodSpec innerMethodSpec = MethodSpec.methodBuilder("onClick")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(ClassName.get("android.view", "View"), "v")
                    .addStatement("target.$L($L)", executableElement.getSimpleName().toString(), "v")
                    .build();
            TypeSpec innerTypeSpec = TypeSpec.anonymousClassBuilder("")
                    .addSuperinterface(ClassName.bestGuess("View.OnClickListener"))
                    .addMethod(innerMethodSpec)
                    .build();
            methodBuilder.addStatement("view.findViewById($L).setOnClickListener($L)", id, innerTypeSpec);
        }
    }
    if (idStrs.length > 0) {

        for (String idStr : idStrs) {
            if (idStr == null || idStr.length() <= 0) {
                continue;
            }

            MethodSpec innerMethodSpec = MethodSpec.methodBuilder("onClick")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(ClassName.get("android.view", "View"), "v")
                    .addStatement("target.$L($L)", executableElement.getSimpleName().toString(), "v")
                    .build();
            TypeSpec innerTypeSpec = TypeSpec.anonymousClassBuilder("")
                    .addSuperinterface(ClassName.bestGuess("View.OnClickListener"))
                    .addMethod(innerMethodSpec)
                    .build();

            methodBuilder.addStatement("resourceID = view.getResources().getIdentifier($S,$S, view.getContext().getPackageName())", idStr, "id");

            methodBuilder.addStatement("view.findViewById($L).setOnClickListener($L)", "resourceID", innerTypeSpec);

        }
    }
}

给使用者提供调用方法

api模块中定义提供给使用者的方法。
新建一个ViewInjector类,调用者通过调用这个类中的方法,完成调用生成辅助类的方法

public class ViewInjector {
    static final Map<Class<?>, Method> BINDINGS = new LinkedHashMap<>();

    public static void bind(Activity activity) {
        bind(activity, activity.getWindow().getDecorView());
    }

    public static void bind(Object target, View view) {
        Method constructor = findBindMethodForClass(target);
        try {
            constructor.invoke(null,target, view);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static Method findBindMethodForClass(Object target) {
        Method constructor = BINDINGS.get(target.getClass());
        if (constructor == null) {
            try {
                Class<?> bindingClass = Class.forName(target.getClass().getName() + "$ViewInjector");
                constructor = bindingClass.getMethod("bind",target.getClass(), View.class);
                BINDINGS.put(target.getClass(), constructor);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return constructor;
    }
}

使用

在我们项目的主模块(application模块)中新建一个Activity,就可以愉快的使用了


public class TestViewInjectActivityTwo extends Activity {
    @BindView(id = R.id.test_view_inject_one)
    protected Button buttonOne;
    @BindView(idStr = "test_view_inject_two")
    protected Button buttonTwo;
    。
    。
    。
    @ClickResponder(id = {R.id.test_view_inject_one})
    public void onClickButtonOne(View v) {
        Toast.makeText(TestViewInjectActivity.this, "test_view_inject_one", Toast.LENGTH_SHORT).show();
    }

    @ClickResponder(idStr = {"test_view_inject_two"})
    public void onClickButtonTwo(View v) {
        Toast.makeText(TestViewInjectActivity.this, "test_view_inject_two", Toast.LENGTH_SHORT).show();
    }

执行一下assembleDebug任务,就可以找到TestViewInjectActivityTwo$ViewInjector类了(一般是在对用模块中的build/generated/source/apt/debug)文件夹下,当然,assembleRelease会在build/generated/source/apt/release文件夹下。

public class TestViewInjectActivity$ViewInjector {
  public static void bind(final TestViewInjectActivity target, View view) {
    int resourceID = 0;
    resourceID = (2131165388);
    target.buttonOne = (android.widget.Button) view.findViewById(resourceID);
    resourceID = view.getResources().getIdentifier("test_view_inject_two","id", view.getContext().getPackageName());
    target.buttonTwo = (android.widget.Button) view.findViewById(resourceID);

    view.findViewById(2131165388).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        target.onClickButtonOne(v);
      }
    });
    resourceID = view.getResources().getIdentifier("test_view_inject_two","id", view.getContext().getPackageName());
    view.findViewById(resourceID).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        target.onClickButtonTwo(v);
      }
    });
  }

其实生成的代码还是有优化空间的,比如对于一个既用了BindView又用了ClickResponder的控件,对应的findViewById会执行两次,这里可以优化一下.

可以自己写一下LongClickResponder注解呀,代码在https://github.com/huangyuanlove/AndroidAnnotation


以上

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自定义注解,打造自己的框架-下篇 的相关文章

  • K210人脸识别+人脸信息存储

    在我的上一篇博客中已经介绍了如何使用K210实现基本的人脸识别功能 https blog csdn net HuangChen666 article details 113995079 spm 61 1001 2014 3001 5501
  • 旅行商问题--蚁群优化算法求解(matlab实现)

    今天给大家分享一下matlab实现蚁群优化算法 xff0c 解决旅行商问题 在上一篇博客中对蚁群优化算法做了较为详细的介绍 xff0c 有需要的小伙伴可以看一下 https blog csdn net HuangChen666 articl
  • 粒子群优化算法及MATLAB实现

    上一篇博客是关于蚁群优化算法的 xff0c 有兴趣的可以看下 https blog csdn net HuangChen666 article details 115913181 1 粒子群优化算法概述 2 粒子群优化算法求解 2 1 连续
  • A星(A*、A Star)路径规划算法详解(附MATLAB代码)

    首先看看运行效果 xff0c 分别有三种模式 xff0c 代码运行前需要通过鼠标点击设置起点和终点 第一种模式直接输出最短路径 第二种模式输出最短路径的生成过程 第三种模式输出最短路径的生成过程和详细探索的过程 代码获取 gitee链接 x
  • Ubuntu20.04+MAVROS+PX4+Gazebo保姆级安装教程

    Ubuntu20 04 43 MAVROS 43 PX4 43 Gazebo 安装PX4步骤安装MAVROS安装QGCPX4仿真 安装PX4步骤 从github上clone源码 span class token function git s
  • PX4+Offboard模式+代码控制无人机起飞(Gazebo)

    参考PX4自动驾驶用户指南 https docs px4 io main zh ros mavros offboard cpp html 我的另一篇博客写了 键盘控制PX4无人机飞行 PX4无人机 键盘控制飞行代码 可以先借鉴本篇博客 xf
  • 基于ESP32的小型四轴无人机

    粗糙版试飞成功 xff01 陀螺仪部分直接飞线飞了一个模块 xff0c 懒得焊了 不是很水平 xff0c 稳定性不是很好 因为滤波算法中加入的元素太少了 xff0c 目前也就MPU6050的输出数据加入了计算 xff0c 所以很多自稳定性飞
  • PX4无人机 - 键盘控制飞行代码

    PX4无人机 键盘控制飞行代码 仿真效果 实机效果 由于图片限制5M以内 xff0c 只能上传一小段了 xff0c 整段视频请点击链接 Pixhawk 6c 无人机 键盘控制无人机 Offboard模式 核心 xff1a 发布 mavros
  • 【FreeRTOS学习 - 消息队列学习】

    跟着韦东山老师FreeRTOS教学资料的学习记录 FreeRTOS全部项目代码链接 xff08 更新中 xff09 https gitee com chenshao777 free rtos study 本文章一共分为一下几个部分 1 创建
  • 【Linux多线程编程-自学记录】08.Linux多线程互斥量

    Linux多线程编程学习代码 xff08 代码已上传gitee xff0c 还请各位兄弟点个Star哦 xff01 xff09 https gitee com chenshao777 linux thread git 笔记 xff1a 1

随机推荐

  • 【Linux多线程编程-自学记录】09.Linux多线程之读写锁

    Linux多线程编程学习代码 xff08 代码已上传gitee xff0c 还请各位兄弟点个Star哦 xff01 xff09 https gitee com chenshao777 linux thread git 笔记 xff1a 1
  • 【Linux多线程编程-自学记录】10.条件变量

    Linux多线程编程学习代码 xff08 代码已上传gitee xff0c 还请各位兄弟点个Star哦 xff01 xff09 https gitee com chenshao777 linux thread git 笔记 xff1a 1
  • 树莓派4B安装Ros 2 Foxy踩坑记录

    1 通过树莓派官方提供的写卡工具raspberry pi imager选择Ubuntu 20 04 5 xff08 64 bit xff09 xff0c 因为我打算用一个8G的存储卡安装ros 2 xff0c Ubuntu 22 04的比较
  • 港科大vins-fusion初探

    SLAM新手 xff0c 欢迎讨论 关于vins fusion的博客 xff1a 1 初探 xff1a https blog csdn net huanghaihui 123 article details 86518880 2 vio主体
  • vins-fusion代码解读[一] vio主体

    SLAM新手 xff0c 欢迎讨论 港科大vins fusion代码解读 一 vins fusion与vins mono代码结构有很大相似性 这次先看看vins estimator节点内的内容 1 程序入口 xff1a 1 vins est
  • vins-fusion代码解读[二] 惯性视觉里程结果与GPS松耦合

    感谢 slam萌新 xff0c 本篇博客部分参考 xff1a https blog csdn net weixin 41843971 article details 86748719 欢迎讨论 惯性视觉里程结果与GPS松耦合 xff1a g
  • vins-fusion代码解读[四] 图像回环检测loop_fusion主体

    SLAM新手 xff0c 欢迎讨论 这篇主要讲loop fusion包的程序结构 xff0c loop fusion主要作用 xff1a 利用词袋模型进行图像的回环检测 在vinsmono中 xff0c 该程序包处于pose graph包内
  • 基于乐鑫开源ESP32四轴无人机项目分享

    上次说重新焊接一块的 xff0c 周末搞定了 xff0c 基本组装的也完成了 xff0c 上个图 试飞还是可以的 xff0c 因为没有光流和定高模块 xff0c 所以稳定性不是很好 xff0c 不过乐鑫预留了扩展模块的接口 xff0c 大家
  • vins-回环检测单独剥离运行

    由于前端如果单纯依靠视觉 43 imu作为里程计 效果经常不稳定 因此最近做项目的过程中 xff0c 将前端转化为以里程计 xff08 码盘编码器 xff09 来进行 xff0c 相对比较鲁棒 由于这个局部传感器有累计误差 xff0c 因此
  • apt 的 update 和 upgrade 命令的区别是什么?

    如果想让你的 Ubuntu 或者 Debian 系统保持更新 xff0c 要用 sudo apt update 和 sudo apt upgrade 命令组合 一些以前的教程也会提到 sudo apt get update 和 sudo a
  • Ubuntu 下 ROS 使用 serial 包进行无线串口通信

    1 查看本机当前USB 串口设备 查看当前已连接的 USB 设备 xff1a lsusb 查看电脑连接的USB 转串口的信息 xff1a dmesg grep ttyUSB 查看电脑连接的串口的信息 xff1a dmesg grep tty
  • 画PCB时,一些非常好的布线技巧

    布线是PCB设计过程中技巧最细 限定最高的 xff0c 即使布了十几年布线的工程师也往往觉得自己不会布线 xff0c 因为看到了形形色色的问题 xff0c 知道了这根线布了出去就会导致什么恶果 xff0c 所以 xff0c 就变的不知道怎么
  • 不能错过的4本Linux好书

    2010年大学毕业 xff0c 在Linux下编程已三年有余了 最近看论坛上不少朋友谈论看过的编程 xff08 Linux xff09 书籍 xff0c 我感触良多 回头想想 xff0c 当初那个一无所知 xff0c 而且老是爱问白痴问题的
  • 微策略2017年秋招线下笔试+技术面+在线测评+主管面总结

    1 前言 微策略可能在国内的知名度比较小 xff0c 它是一家总部在美国 xff0c 在杭州设立研发中心 xff0c 主要做智能商用软件的外企 更多的信息 xff0c 请自行搜索 我是17年10月份面试微策略 xff0c 然后拿到的开发 x
  • github,dockerhub下载文件

    1 打开github xff0c dockerhub代理页面 xff0c 见菜单点击可进入dockerhub代理页面 github下载代理 2 把需要下载的文件的url复制到输入框 3 常用的脚本命令 git clone git clone
  • C++ 开源程序库

    1 系统和网络编程库 xff1a ACE 除了ACE之外 xff0c 还有很多系统和网络编程方面的程序库 比如在线程库方面 xff0c 还有ZThread boost thread xff0c 如果放大到C C 43 43 领域 xff0c
  • 360笔试题2013

    编程题 传教士人数M xff0c 野人C xff0c M C xff0c 开始都在岸左边 xff0c 船只能载两人 xff0c 传教士和野人都会划船 xff0c 当然必须有人划船 两岸边保证野人人数不能大于传教士人数 把所有人都送过河 xf
  • 搜狗笔试题

    搜狗 xff1a 1 xff0c 有n n个正方形格子 xff0c 每个格子里有正数或者0 xff0c 从最左上角往最右下角走 xff0c 只能向下和向右走 一共走两次 xff0c 把所有经过的格子的数加起来 xff0c 求最大值 且两次如
  • [INS-20802] Oracle Net Configuration Assistant failed

    INS 20802 Oracle Net Configuration Assistant failed 在安装Oracle 11g R2时出现了该错误提示 以前安装的时候没有碰到过类似 的错误 原来是64bit和32bit系统兼容性的问题
  • 自定义注解,打造自己的框架-下篇

    2019 12 04 22 53 52 文章目录 结构声明注解声明注解处理器处理注解逻辑给使用者提供调用方法使用 该系列介绍自定义注解 xff0c 完成如下功能 64 BindView 代替 findViewById 64 ClickRes