重拾Android的快乐-Service

说明 REF https://developer.android.com/develop/background-work/services

2025-07-29 · 1 min · 3 words · tomyli

重拾Android的快乐-ListView组件

说明 ListView 组件用来以垂直滚动的显示View集合,配置方式如下: <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" /> ListView 实现动态数据展示 ListView 是一个 AdapterView,它不关心要显示的数据,只负责展示 ListView 显示的数据是交由 ListAdapter 进行处理,ListAdapter 本质是一个 Adapter,它是ListView和展示数据的桥,这样就可以实现内容与数据的分离 BaseAdapter 抽象基类,自定义扩展都会继承这个类 ArrayAdapter 支持泛型操作,实现最简单的展示一行文字 SimpleAdapter 具有良好扩展性的Adapter,支持Map类型的List数据(List<Map>),支持自定义多种效果 重点关注一下 Adapter 的 getView 方法,它提供获取显示数据的View,这个View可以直接创建或者从XML layout中创建 实战 实现一个最简单的列表 最终效果 核心代码 ListView 容器layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/listView" /> </LinearLayout> 文件名为: list_item1 在线性布局下定义一个 ListView , 设置id为 listView MainActivity 代码 private fun testListView1() { setContentView(R.layout.list_item1) val list = listOf("111", "222", "333", "444", "555") val adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, list) val listView = findViewById<ListView>(R.id.listView) listView.adapter = adapter } 主要关注创建 ArrayAdapter 的代码,创建时需要的参数: ...

2025-07-25 ·  (🌟Updated: 2025-08-04) · 1 min · 115 words · tomyli

重拾Android的快乐-TableLayout布局

说明 TableLayout 是一个用来以行或者列方式来显示子View的视图组。TableLayout不显示行、列或者单元格的边框线。一个表可以留空多个单元格,也可以跨越多列,这个与html类似 TableRow TabRow是 TableLayout 的子View,用来表示一行,此元素下可以定义多个单元格来包含其它元素,单元格又可以嵌套 TableLayout 约束 列的宽度由该列中最宽单元格的行定义 TableLayout 子类不能指定 layout_width 属性,宽度值一直为 MATCH_PARENT layout_height 属性可以由子类进行设置,默认值为 WRAP_CONTENT ;如果子类是 TableRow ,则其高度值一直为 WRAP_CONTENT 常用属性 android:collapseColumns 基于索引0来隐藏列,多个值用英文逗号分隔,例如配置为1,则隐藏第2列 table中的列索引从0开始计数,如果不指定,则自动增加 android:shrinkColumns 基于索引0来收缩列 android:stretchColumns 基于索引0来拉升列 示例 效果图 代码 <?xml version="1.0" encoding="utf-8"?> <TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:stretchColumns="1"> <TableRow> <TextView android:layout_column="1" android:text="Open..." android:padding="3dip" /> <TextView android:text="Ctrl-O" android:gravity="right" android:padding="3dip" /> </TableRow> <TableRow> <TextView android:layout_column="1" android:text="Save..." android:padding="3dip" /> <TextView android:text="Ctrl-S" android:gravity="right" android:padding="3dip" /> </TableRow> <TableRow> <TextView android:layout_column="1" android:text="Save As..." android:padding="3dip" /> <TextView android:text="Ctrl-Shift-S" android:gravity="right" android:padding="3dip" /> </TableRow> <View android:layout_height="2dip" android:background="#FF909090" /> <TableRow> <TextView android:text="X" android:padding="3dip" /> <TextView android:text="Import..." android:padding="3dip" /> </TableRow> <TableRow> <TextView android:text="X" android:padding="3dip" /> <TextView android:text="Export..." android:padding="3dip" /> <TextView android:text="Ctrl-E" android:gravity="right" android:padding="3dip" /> </TableRow> <View android:layout_height="2dip" android:background="#FF909090" /> <TableRow> <TextView android:layout_column="1" android:text="Quit" android:padding="3dip" /> </TableRow> </TableLayout> 因为在 TableLayout 中设置了 android:stretchColumns="1" 属性来拉升第一列,可以看到第一列的宽度最宽

2025-07-17 ·  (🌟Updated: 2025-07-17) · 1 min · 124 words · tomyli

重拾Android的快乐-RelativeLayout布局

说明 RelativeLayout 是一个提供子View相对位置的View组,可以指定相邻同级节点的位置(上下左右)或相对于父区域的位置。RelativeLayout主要是用来消除嵌套布局来保持布局层级扁平来提升性能。 布局方向 Android中的布局方向有两种: LTR 从左到右 RTL 从右到左 可以通过 android:layoutDirection 进行设置 常用属性配置 对齐相关 android:layout_alignParentTop 此视图顶部边缘与父视图顶部边缘对齐,忽略布局方向,对应的有 android:layout_alignParentBottom android:layout_alignParentLeft 此视图顶部边缘与父视图左侧对齐,只支持LTR,对应的有 android:layout_alignParentRight android:layout_alignParentStart 此视图顶部边缘与父视图左侧对齐,支持LTR与RTL,对应的有 android:layout_alignParentEnd android:layout_alignTop 此视图顶部边缘与给定视图顶部边缘对齐,忽略布局方向,对应的有 android:layout_alignParentBottom android:layout_alignLeft 此视图顶部边缘与给定视图左侧对齐,只支持LTR,对应的有 android:layout_alignRight android:layout_alignStart 此视图顶部边缘与给定视图左侧对齐,支持LTR与RTL,对应的有 android:layout_alignEnd android:layout_alignWithParentIfMissing 如果没有设置 layout_toLeftOf 或者 layout_toRightOf 属性则使用父view做为锚点 居中相关 android:layout_centerHorizontal 子视图在父视图中垂直居中 android:layout_centerVertical 子视图在父视图中水平居中 android:layout_centerInParent 子视图在父视图中水平和垂直居中 android:layout_below 用于控制垂直方向,此视图定位于指定资源id的下方,对应的还有 android:layout_above android:layout_toRightOf 用于左右(LTR)布局并忽略右左布局,此视图的左边缘定位于指定资源的右方,其它的还有 android:layout_toLeftOf android:layout_toEndOf 支持LTR与RTL两种布局,对于LTR表示位于指定资源id的右侧,对于RTL则相反 示例 这里引用一个官方提供的样例 效果图 代码 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="16dp" android:paddingRight="16dp" > <EditText android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/reminder" /> <Spinner android:id="@+id/dates" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_below="@id/name" android:layout_alignParentLeft="true" android:layout_toLeftOf="@+id/times" /> <Spinner android:id="@id/times" android:layout_width="96dp" android:layout_height="wrap_content" android:layout_below="@id/name" android:layout_alignParentRight="true" /> <Button android:layout_width="96dp" android:layout_height="wrap_content" android:layout_below="@id/times" android:layout_alignParentRight="true" android:text="@string/done" /> </RelativeLayout> 这里主要关注中间两个Spinner和Button ...

2025-07-10 ·  (🌟Updated: 2025-07-17) · 1 min · 168 words · tomyli

重拾Android的快乐-LinearLayout布局

说明 LinearLayout 是一个视图组件,它可以将所有子视图按一个方向进行 垂直/水平 对齐 垂直方向(Vertical) 垂直方向会按每行一个元素排列,而不管设置的宽度 水平方向(Horizontal) 水平方向只有一行高,它的高度为最高的子元素+Padding值 常用属性配置 android:orientation 布局方向,可配置值: horizontal 水平 vertical 垂直 android:layout_width 元素宽度 android:layout_height 元素高度,可配置具体的数值,还支持常用的值如下: match_parent 与父元素同尺寸 wrap_content 只包含内容大小(+padding) android:layout_weight 布局权重,用于设置元素占据的空间值,值越大占的空间越大,默认值为0(匹配内容) android:gravity 指定视图内容在其自身边界内的对齐方式 实战案例 实现一个简单的邮件发送功能 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="16dp" android:paddingRight="16dp" android:orientation="vertical" > <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="" android:hint="@string/to" android:inputType="text" /> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="" android:hint="@string/subject" android:inputType="text" /> <EditText android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:autofillHints="" android:gravity="top" android:hint="@string/message" android:inputType="text" /> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:layout_gravity="end" android:text="@string/send" /> </LinearLayout> 效果图 Jetpack Compose组件实现同样效果 Jetpack Compose 是google新推出并推荐使用的基于kotlin声明式UI组件库,可以用更少的代码来加速开发 ...

2025-07-01 ·  (🌟Updated: 2025-07-04) · 1 min · 204 words · tomyli

Spring学习之@Conditional

注解说明 @Conditional 是 Spring 提供的用于判断是否对 Bean 注入的条件注解(在4.0后版本引入),只有满足了所有的条件,一个 bean 才会被注册成功并使用。此注解可标注在类上、元注解上、@Bean方法上,通常与 @Configuration 注解配合使用,标识在 @Configuration 注解上的 @Conditional 会影响此配置类的所有 @Bean 注解方法、@Import 注解和@ComponentScan 注解。另需注意 @Conditional 注解不支持继承 对于标注在元注解上的样例,可查看 SpringBoot 项目下的 org.springframework.boot.autoconfigure.condition 包以 ConditionalOn 开头的类 注解属性 此注解只有一个属性 value ,返回类型为 Class 数组,它用来配置所有的 Condition 类信息 Condition 是 Spring 提供的一个接口,它提供了 matches 方法来提供判断的依据逻辑,对于其参数 ConditionContext 提供了丰富的上下文信息 测试代码 @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.condition") public class MyConfiguration { @Bean @ConditionalOnDate public MyBean myBean() { return new MyBean(); } @Bean public MyBean2 myBean2() { return new MyBean2(); } @Bean @Conditional(ConfigPropertyCondition.class) public MyBean5 myBean5() { return new MyBean5(); } } @Configuration @Conditional(ConfigPropertyCondition.class) public class MyConfigConfiguration { @Bean public MyBean3 myBean3() { return new MyBean3(); } @Bean public MyBean4 myBean4() { return new MyBean4(); } } public class MyDateCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { LocalDateTime now = LocalDateTime.now(); int minute = now.getMinute(); return minute % 2 == 0; // 每隔两分钟满足条件 } } @Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @Target({java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.METHOD}) @Documented @Conditional(MyDateCondition.class) public @interface ConditionalOnDate { } public class ConfigPropertyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return "true".equals(System.getProperty("my.config.property")); } } public class Main { public static void main(String[] args) { System.setProperty("my.config.property", "true"); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); for (String beanDefinitionName : context.getBeanDefinitionNames()) { System.out.println("beanDefinitionName = " + beanDefinitionName); } } } 源码阅读 对于 Configuration 类条件处理分为两个阶段: ...

2025-06-18 ·  (🌟Updated: 2025-06-23) · 2 min · 333 words · tomyli

Spring学习之@Bean

注解说明 @Bean 是Spring的一个核心注解,通常与 @Configuration 注解一起使用,用于标记一个方法,方法返回的实体做为Spring管理的一个Bean,通过此注解可以使用JAVA的特性配置对象。默认情况下Bean名字为方法名,也支持通过属性进行配置 属性说明 name 自定义Bean的名称,支持配置多个名称,不配置默认为方法名称 initMethod 配置实例初始化方法,在Bean实例化被调用,默认为空 destroyMethod 配置实例销毁方法,默认值为 (inferred) 即会根据返回的Bean尝试查找识别对象的销毁方法 close 或者 shutdown(限制只查找public无参方法),如返回一个 BasicDataSource 实例,会默认查找 close() 方法进行注册,如果不想Spring进行此操作,需要将此值设置为: destroyMethod="" (此配置对 DisposableBean 回调不生效) 核心逻辑在 DisposableBeanAdapter#inferDestroyMethodsIfNecessary() 方法中 与其它注解的关联 @Bean 与XML配置方式中的 <bean> 标签不同,它使用与其它注解组合的方式来实现类型 Scope 或者懒加载的功能,具体可组合注解如下: @Profile @Scope @Lazy @DepednsOn @Primary @Order @Description(给注解bean详细描述,此注解的值会保存在 BeanDefinition 实例上) @Conditional 接口默认方法定义Bean Spring 支持在接口中使用默认方法的方式进行bean的定义,方便子类配置类直接复用,示例代码: public interface BaseConfig { @Bean default MyBean3 myBean3() { return new MyBean3(); } } @Configuration public class AppConfig implements BaseConfig { } 源码阅读 Bean注解配置实例化 由于@Bean通常与@Configuration 一起使用,核心逻辑在 refresh 阶段,定位代码发现在 ConfigurationClassParser#doProcessConfigurationClass() 方法进行对 @Bean 标识方法的解析 ...

2025-06-17 · 1 min · 191 words · tomyli

Spring学习之@Import

注解说明 @Import 注解用于标识多个需要导入的 Component 类,支持导入 @Configuration 类, ImportSelector 与 ImportBeanDefinitionRegistrar 实现,与 AnnotationConfigApplicationContext.register() 方法实现的功能类似。此注解常见的实践方式是标注在 EnableXXXX 注解上,用于开启某些功能,在开启功能时就需要通过 @Import 注解来选择导入哪些类的定义 属性说明 此注解只有一个属性 value,返回 Class[] ,用于标识导入的类 Class类型 @Configuration标识的类 对于不同功能的配置使用多个 @Configuration 标识,由一个类来 Import 另一个 类的配置,来达到复用的效果 @Configuration public class MyConfig2 { @Bean public MyBeanD myBeanD() { return new MyBeanD(); } } @Configuration @Import({MyConfig2.class}) public class MyConfig { } ImportSelector实现类 所有的 ImportSelector 实现都需要实现它的 selectImports 方法,来返回要导入的bean类名称集合 public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[]{MyBeanA.class.getName()}; } } 在 ImportSelector 可以通过Aware或者构造器注入的方式使用 Spring中的容器组件: ...

2025-06-17 · 2 min · 245 words · tomyli

Spring学习之@Value

注解说明 @Value 是Spring提供用于处理属性与表达式注入的注解,可标注在字段、方法或构造器的参数上,并且支持动态解析SpringMVC中的方法参数。支持注入方式: 直接注入如: @Value(“test”) ${my.app.myProp} 属性注入 #{systemProperties.myProp} SpEL表达式 它提供了不同的配置源(属性文件、系统属性)的注入 注解属性 @Value 注解只有一个属性 value ,表示要注入的实际值的表达式,支持属性与SpEL表达式两种方式 测试代码 以下测试代码MyController的username的注入值为: tomyli,而不是配置文件中的admin @Controller public class MyController { @Value("${USER}") @Getter private String username; } @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.autowired") @PropertySource("classpath:application.properties") public class MyConfiguration { } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); MyController myController = context.getBean(MyController.class); System.out.println(myController.getUsername()); // tomyi } } application.properties配置文件 USER=admin 源码阅读 @Value 注解的处理由 AutowiredAnnotationBeanPostProcessor 类负责,与处理 @Autowired 注解的类为同一个PostProcessor。主要的处理逻辑实现在 DefaultListableBeanFactory#doResolveDependency 方法,对于 @Value注解的处理主要集中在第2步,解析@Value的操作主要分为3步 ...

2025-06-10 ·  (🌟Updated: 2025-06-19) · 1 min · 170 words · tomyli

Spring学习之@Autowireid

注解说明 @Autowired 是Spring框架的核心注解,用于自动装载构造器、字段、set方法、配置方法上的依赖,它是 JSR-330 @Inject 注解的替代,支持配置 依赖是否必须 选项 属性说明 此注解只有一个属性 required ,默认为 true 表示待注入组件必须存在 注解注入规则说明 构造器注入 如果一个类声明了多个构造器且没有一个显式标注 @Autowired,则Spring会使用默认的构造方法进行装载 如果一个类只声明了一个构造器,不管有没有标注 @Autowired 注解它都会被直接使用 多参数可以配置多个required属性,对于可选注入,可以声明为 Java8 的 Optional 类型 对于可选注入也可以使用 JSR-305 的 javax.annotation.Nullable 注解 字段注入 字段在构造器之后,配置方法注入之前进行自动注入 字段注入支持组件自引用(self references) 方法注入 方法注入不限制方法名与参数个数,所有参数会被Spring进行匹配注入 参数注入 目前只在 spring-test 模块中有效,其它地方使用会被忽略 数组/集合注入 Spring通过匹配数组/集合的值进行注入,Map的key必须为String类型,支持集合中元素的Order配置 不支持 BeanPostProcessor 或者 BeanFactoryPostProcessor 因为真实的注入动作发生在 BeanPostProcessor 阶段,所以不能在 BeanPostProcessor 或者 BeanFactoryPostProcessor 中使用此注解 源码阅读 在Spring中,处理 @Autowired 注解对应的类为 AutowiredAnnotationBeanPostProcessor ,又是一个Bean后置处理器 对于此类,需要关注其实现的两个接口类 MergedBeanDefinitionPostProcessor (运行时合并Bean的定义回调接口)和 InstantiationAwareBeanPostProcessor (在Bean实例化前后进行处理后置处理器) MergedBeanDefinitionPostProcessor 主要是为了在真正bean实例化前准备缓存的bean元数据 InstantiationAwareBeanPostProcessor Bean实例化回调接口,通常用于为bean创建代理 测试代码 @Service public class MyService { } @Controller public class MyController { @Autowired MyService myService; public void showService() { System.out.println("myService = " + myService); } } @Configuration @ComponentScan(basePackages = "cn.imcompany.bean.autowired") public class MyConfiguration { } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class); MyController myController = context.getBean(MyController.class); myController.showService(); } } 元数据收集 调用栈信息如下: ...

2025-06-06 ·  (🌟Updated: 2025-06-19) · 2 min · 267 words · tomyli