有了它,再也不用写setContentView了

本文探讨了注解在Android开发中的应用,通过自定义注解和反射技术,实现不再需要手动调用setContentView来设置Activity的布局。介绍了元注解、自定义注解的创建及使用,同时对比了getMethod和getDeclaredMethod的区别,并简要讨论了IOC(控制反转)的概念和依赖注入的思想。

前言

大家多多少少都用过或者看过注解(Annotation),比如最常见@Override、@Deprecated等。近年来一些比较流行的三方框架都使用的注解,像ButterKnife(渐渐被Databinding、ViewBinding取代,已经停止维护)、Dagger、Room等等。那为什么这些大牛都这么热衷于使用注解呢?原因肯定是注解的好处多多了。

 

注解的优点

  • 代码的检查,将一些错误暴露在运行期之前。
  • 减少重复的工作,提高工作效率。例如ButterKnife,可以减少我们的findViewById,设置点击事件等。
  • 降低代码的耦合。典型的就是Dagger。
  • 信息配置,运行时利用反射动态处理。

元注解(meta-annotation)

什么叫元注解,意思就是在JDK中定义好的注解。在JDK1.5中提供了4个元注解

@Target

表示该注解用于哪个位置,例如作用在类、方法等上面,详细的作用范围如下:

  • ElementType.TYPE 类、接口(包括注解类型)或枚举声明
  • ElementType.FIELD 字段声明
  • ElementType.METHOD 方法声明
  • ElementType.PARAMETER 方法的参数声明
  • ElementType.CONSTRUCTOR 类的构造法声明
  • ElementType.LOCAL_VARIABLE 局部变量声明
  • ElementType.ANNOTATION_TYPE 注解声明
  • ElementType.PACKAGE 包声明
  • ElementType.TYPE_PARAMETER JDK1.8新加的,类型参数声明
  • ElementType.TYPE_USE JDK1.8新加的,类型使用声明

@Retention

表示注解的生命周期

  • RetentionPolicy.SOURCE 源码阶段,在编译时会去除
  • RetentionPolicy.CLASS 注解会保存在class文件中,运行时会去除
  • RetentionPolicy.RUNTIME 注解会一直存在,在运行时可以利用反射去获取注解上面对应的值

@Inherited

表示允许子类继承父类的注解类型

@Documented

表示文档注解

今天我们就是利用运行时注解的特性,来做一些骚操作。

自定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationTest {
	int value();
}

上面的代码自定义了一个AnnotationTest注解,@Target是ElementType.TYPE,表示是作用在类、接口或者枚举上面的。@Retention是RetentionPolicy.RUNTIME,表示在运行时期。内部定义了一个返回值为int的方法,表示使用注解时要传递一个int型的参数。那下面我们建个类来看看如何使用。

@AnnotationTest(1)
public class Test {
}
Test test = new Test();
//获取Test的Class对象
Class<? extends Test> clazz = test.getClass();
//使用Class对象获取对应的注解
AnnotationTest annotation = clazz.getAnnotation(AnnotationTest.class);
if (annotation != null) {
       //获取注解传递的值
       int value = annotation.value();
       Log.d(TAG, "initView: >>>>>>>>>>>" + value);
}

 

上面代码首先定义了一个Test类,然后使用@AnnotationTest注解,传递数值1.然后使用反射获取该类的注解,接着获取注解传递的值,如上图打印出来的是1。

那下面开始进入我们今天的主题,使用注解去设置Activity的页面布局,不在用setContentView了。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
    int value();
}

定义了一个ContentView注解,返回值是int型的方法。

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        InjectUtils.injectLayout(this);
        initView();
    }

    protected void initView() {

    }
}

非常简单的一个BaseActivity

@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}

MainActivity,使用ContentView注解,然后注解传递布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/btn2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

MainActivity的布局

还有一个InjectUtils类,获取MainActivity的注解和布局,然后在设置给MainActivity,主要的操作就是放在InjectUtils类中。

public class InjectUtils {
	public static void injectLayout(Object context) {
    	//获取Class对象,这里context就是传递过来的MainActivity
        Class<?> clazz = context.getClass();
        //获取@ContentView注解
        ContentView contentView = clazz.getAnnotation(ContentView.class);
        if (contentView != null) {
        	//获取注解中的布局文件
            int layoutId = contentView.value();
            try {
            	//利用反射调用Activity中的setContentView方法,将布局设置给MainActivity
                Method setContentViewMethod = clazz.getMethod("setContentView", int.class);
                setContentViewMethod.invoke(context, layoutId);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
}

 

如上图所示,已经成功的将布局文件设置给了MainActivity。上面的injectLayout方法中主要的操作已经加了注释,这里我就不一一解释了。其实除了使用注解设置Activity的布局,还是使用注解替代fingViewById,设置点击等等。有需要看使用注解去设置控件和点击事件的,可以戳这里

这里我要说的一点就是通过反射获取方法对于有些同学可能会存在的坑(准确的说应该是自己不经常用反射的原因吧),我之前就被这个坑了好久,不过踩过这种坑会让我更加记忆深刻,也让我对反射有了新的认识。

getMethod和getDeclaredMethod的区别

  • getMethod 可以获取自身以及父类的被public修饰的方法
  • getDeclaredMethod 可以获取自身任何的方法,父类的方法是获取不到,哪怕父类的方法是用public修饰的也无法获取。

IOC

IOC,Inversion of Control的缩写,是控制反转。 什么意思呢?IOC是原来由程序代码中主动获取的资源,转变由第三方获取并使原来的代码被动接收的方式,以达到解耦的效果,称为控制反转。最常见的方式就是依赖注入(Dependency Injection),简称DI。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。

我个人的理解,就是把事情交给别人做,做好了之后把成果交给我就行了。 举个简单的例子,老板招了个年轻又漂亮的秘书。老板要和另一家公司合作要吃饭是吧,那秘书负责定酒店,定好之后告诉老板。那合作伙伴喝多了,老板叫秘书定房间,然后秘书定好房间了之后告诉老板。这就是传中的有事秘书干,没事。。。

 

依赖注入它是一种思想,没有什么标准。就像面向对象思想一样,说简单也简单。说难它就太难了。能领略到思想真谛了,那绝非一朝一夕的时间能做到的。只有通过我们不断积累,不断的实战才有可能做到。

总结

今天主要说的是注解,然后使用运行时注解去设置Acivity的布局,控件等。后面又简单的谈了一下依赖注入,不是很详细。那今天就到这里,大家加油。

你遇到的这个错误: ``` E:\AndroidStudio2025\project\MyApplication\app\src\main\java\com\example\myapplication\MainActivity.java:189: 错误: 兼容的类型: String无法转换为Expression expression = ""; ^ ``` 说明:**你定义了一个叫做 `expression` 的变量,它的类型是 `Expression`(来自 exp4j 库),但现在你却试图给它赋一个字符串 `""`,这在 Java 中是允许的!** --- ## 🔍 问题根源 你了类似这样的代码: ```java Expression expression; // ← 来自 exp4j 的 Expression 类 // 在某个地方你: expression = ""; // ❌ 错误!String 能转成 Expression ``` 这是典型的 **变量命名冲突 + 类型混淆**。 你本意应该是: - `expression`:保存用户输入的表达式字符串,比如 `"5+3"` - 然后用 `exp4j` 把这个字符串构建成真正的 `Expression` 对象来计算 但现在你把 **字符串表达式** 和 **exp4j.Expression 对象** 搞混了! --- # ✅ 正确解决方案 ### ✅ 步骤 1:拆分两个变量(推荐做法) ```java private String currentInput = ""; // 当前正在输入的数字 private String expressionStr = ""; // 用户输入的完整表达式字符串,如 "5+3" private Expression expressionObj; // exp4j 的 Expression 对象,用于计算 ``` > ⚠️ 关键:要叫都叫 `expression`,容易混淆! --- ### ✅ 步骤 2:修改 `evaluateExpression()` 方法 ```java private void evaluateExpression() { // 构建要计算的表达式字符串 String input; if (!currentInput.isEmpty()) { input = expressionStr + currentInput; } else { if (expressionStr.isEmpty()) return; input = expressionStr; } if (input.isEmpty()) return; // 替换 × ÷ 为 * / String formattedInput = input.replace("×", "*").replace("÷", "/"); try { // 使用 exp4j 构建并计算表达式 expressionObj = new ExpressionBuilder(formattedInput).build(); double result = expressionObj.evaluate(); // 格式化结果 long longResult = (long) result; String displayResult = (result == (double) longResult) ? String.valueOf(longResult) : String.format("%.6g", result); // 更新 UI binding.textViewHistory.setText(input + " ="); binding.editTextDisplay.setText(displayResult); // 将结果显示作为下一次输入 currentInput = displayResult; expressionStr = ""; // 清空表达式字符串 } catch (Exception e) { Log.e("CalcError", "Failed to evaluate: " + formattedInput, e); binding.editTextDisplay.setText("Error"); } } ``` --- ### ✅ 步骤 3:其他地方使用 `expressionStr` 而是 `expression` #### 示例:处理操作符时 ```java private void handleOperator(String op) { if (!currentInput.isEmpty()) { expressionStr = currentInput + op; // ← 存的是字符串 binding.textViewHistory.setText(expressionStr); currentInput = ""; } else if (!expressionStr.isEmpty()) { // 替换最后一个操作符 expressionStr = expressionStr.replaceAll("[+\\-×÷]$", op); binding.textViewHistory.setText(expressionStr); } } ``` #### 示例:清空操作 ```java private void resetCalculator() { currentInput = ""; expressionStr = ""; binding.textViewHistory.setText(""); binding.editTextDisplay.setText("0"); } ``` --- ## ✅ 完整字段定义示例 ```java public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; private String currentInput = ""; private String expressionStr = ""; // 表达式字符串 private Expression expressionObj; // exp4j 的 Expression 对象 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setupButtonListeners(); } // ... 其他方法 } ``` --- ## 🧠 总结:为什么会出错? | 原因 | 解释 | |------|------| | 变量名歧义 | 你用了 `expression` 同时表示“字符串”和“对象” | | 类型匹配 | `Expression` 是对象,`""` 是字符串,能直接赋值 | | 概念混淆 | `expressionStr` 是给人看的;`Expression` 是给机器算的 | --- ## ✅ 修改后效果 | 输入操作 | `currentInput` | `expressionStr` | 显示结果 | |--------|----------------|------------------|---------| | 按 `5` | `"5"` | `""` | `5` | | 按 `+` | `""` | `"5+"` | `5+`(历史) | | 按 `3` | `"3"` | `"5+"` | `3` | | 按 `=` | `"8"` | `""` | `8`,历史:`5+3 =` | ✅ 再也会出现 `"Error"`(除非真除零或非法输入) ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值