android 线程池的管理工具类

封装了各种类型的线程池,方便直接使用

看下有哪些类型:

默认线程池,搜索模块专用线程池,网络请求专用线程池,U盘更新,同步SDK读写操作线程池,日志打印使用线程池

DEFALUT,SEARCH,NET_WORK,UDISK_DOWNLOAD,SDK_IO,LOG_PRINT

看下有哪些优先级级别

UI_TOP, UI_NORMAL, UI_LOW, DEFAULT, BG_TOP, BG_NORMAL, BG_LOW;

一:实现默认线程池

接下来先实现一个 默认实现 等待队列, 优先级比较,核心线程数等的线程池

开放 核心线程数,线程创建的工厂类(可以定制线程的优先级)供 外部定制(你也可以开放更多的参数)

public class TaskPriorityExecutor implements Executor {private static final int CORE_POOL_SIZE = 3;private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;private static final int KEEP_ALIVE = 30;private static final Comparator<Runnable> RUNNABLE_COMPARATOR = new Comparator<Runnable>() {@Overridepublic int compare(Runnable lhs, Runnable rhs) {if (lhs instanceof TaskPriorityRunnable && rhs instanceof TaskPriorityRunnable) {return ((TaskPriorityRunnable) lhs).priority.ordinal() - ((TaskPriorityRunnable) rhs).priority.ordinal();} else {return 0;}}};private final BlockingQueue<Runnable> mPoolWorkQueue =new PriorityBlockingQueue<Runnable>(MAXIMUM_POOL_SIZE, RUNNABLE_COMPARATOR);private final ThreadPoolExecutor mThreadPoolExecutor;public TaskPriorityExecutor() {this(CORE_POOL_SIZE, new TaskThreadFactory("Defalut"));}public TaskPriorityExecutor(int poolSize, ThreadFactory threadFactory) {mThreadPoolExecutor = new ThreadPoolExecutor(poolSize,MAXIMUM_POOL_SIZE,KEEP_ALIVE,TimeUnit.SECONDS,mPoolWorkQueue,threadFactory);}/*** 该构造器会创建单线程池执行任务*/public TaskPriorityExecutor(ThreadFactory threadFactory) {mThreadPoolExecutor = new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}public int getPoolSize() {return mThreadPoolExecutor.getCorePoolSize();}public void setPoolSize(int poolSize) {if (poolSize > 0) {mThreadPoolExecutor.setCorePoolSize(poolSize);}
//    Executors.newCachedThreadPool()
//    Executors.newFixedThreadPool()
//    Executors.newScheduledThreadPool()
//    Executors.newSingleThreadExecutor()
//    Executors.newSingleThreadScheduledExecutor()}public ThreadPoolExecutor getThreadPoolExecutor() {return mThreadPoolExecutor;}/*** 线程池忙*/public boolean isBusy() {return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize();}/*** 线程池超载*/public boolean isFull() {return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize() * 2;}public boolean isShutdown(){return mThreadPoolExecutor.isShutdown();}@Overridepublic void execute(final Runnable r) {mThreadPoolExecutor.execute(r);}

二 根据接口定制业务场景的线程池接口

接下来我们就开始根据开放的接口来根据不同的业务需要来配置自己的线程池

final class TaskProxy< extends Task<ResultType> {private final Task<ResultType> task;private Executor executor;/*package*/ static final InternalHandler INTERNAL_HANDLER = new InternalHandler();static TaskPriorityExecutor sDefaultExecutor = new TaskPriorityExecutor();static TaskPriorityExecutor sSearchExecutor = null;static TaskPriorityExecutor sNetExecutor = null;static TaskPriorityExecutor sAE8EngineExecutor = null;static TaskPriorityExecutor sUDiskExecutor = null;static TaskPriorityExecutor sTtsInitializeExecutor = null;static TaskPriorityExecutor sSyncSdkIOHandleExecutor = null;static TaskPriorityExecutor sAdapterExecutor = null;static TaskPriorityExecutor sLoggerExecutor = null;static TaskPriorityExecutor sActivateLogExecutor = null;private ResultType result;private Throwable exception;private CancelledException cancelledException;/*package*/ TaskProxy(Task<ResultType> task) {if (task == null) {throw new IllegalArgumentException("task must not be null");}this.task = task;this.executor = task.getExecutor();if (this.executor == null) {this.executor = sDefaultExecutor;}}public static synchronized TaskPriorityExecutor getExecute(TaskExector type){if (TaskExector.DEFALUT.equals(type)) {if (sDefaultExecutor == null || sDefaultExecutor.isShutdown()){sDefaultExecutor = new TaskPriorityExecutor(4, new TaskThreadFactory("Default"));}return sDefaultExecutor;}else if(TaskExector.SEARCH.equals(type)) {if (sSearchExecutor == null || sSearchExecutor.isShutdown()){sSearchExecutor = new TaskPriorityExecutor(2, new TaskThreadFactory("Search"));}return sSearchExecutor;} else if (TaskExector.NET_WORK.equals(type)){if (sNetExecutor == null || sNetExecutor.isShutdown()){sNetExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("Net"));}return sNetExecutor;} else if (TaskExector.AE8_ENGINE.equals(type)){if (sAE8EngineExecutor == null || sAE8EngineExecutor.isShutdown()){sAE8EngineExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("AE8_Engine"));}return sAE8EngineExecutor;} else if (TaskExector.UDISK_DOWNLOAD.equals(type)){if (sUDiskExecutor == null || sUDiskExecutor.isShutdown()){sUDiskExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("UDISK_DOWNLOAD"));}return sUDiskExecutor;} else if (TaskExector.SYNC_SDK_IO.equals(type)) {if (sSyncSdkIOHandleExecutor == null || sSyncSdkIOHandleExecutor.isShutdown()) {sSyncSdkIOHandleExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("SYNC_SDK_IO_HANDLE"));}return sSyncSdkIOHandleExecutor;} else if (TaskExector.USER_BL.equals(type)) {if (sSyncSdkIOHandleExecutor == null || sSyncSdkIOHandleExecutor.isShutdown()) {sSyncSdkIOHandleExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("USER_BL"));}return sSyncSdkIOHandleExecutor;}else if (TaskExector.ADAPTER.equals(type)) {if (sAdapterExecutor == null || sAdapterExecutor.isShutdown()) {sAdapterExecutor = new TaskPriorityExecutor(1, new TaskThreadFactory("ADAPTER"));}return sAdapterExecutor;}
//    else if (TaskExector.LOGGER.equals(type)) {
//       if (sLoggerExecutor == null || sLoggerExecutor.isShutdown()) {
//          //日志问题出于时序考虑只能单线程模型进行执行
//          sLoggerExecutor = new TaskPriorityExecutor(new TaskThreadFactory("LOGGER"));
//       }
//       return sLoggerExecutor;
//    }else if (TaskExector.ACTIVATE_LOG.equals(type)) {if (sActivateLogExecutor == null || sActivateLogExecutor.isShutdown()) {//日志问题出于时序考虑只能单线程模型进行执行sActivateLogExecutor = new TaskPriorityExecutor(new TaskThreadFactory("ACTIVATE_LOG"));}return sActivateLogExecutor;}return sDefaultExecutor;}public synchronized static void shutDown(TaskExector type){Logger.d("byron", "[TaskProxy] shutDown:= "+ type);if (TaskExector.SEARCH.equals(type)) {if (sSearchExecutor != null){sSearchExecutor.getThreadPoolExecutor().shutdown();sSearchExecutor = null;}} else if (TaskExector.NET_WORK.equals(type)){if (sNetExecutor != null){sNetExecutor.getThreadPoolExecutor().shutdown();sNetExecutor = null;}} else if (TaskExector.AE8_ENGINE.equals(type)){if (sAE8EngineExecutor != null){sAE8EngineExecutor.getThreadPoolExecutor().shutdown();sAE8EngineExecutor = null;}} else if (TaskExector.UDISK_DOWNLOAD.equals(type)){if (sUDiskExecutor != null){sUDiskExecutor.getThreadPoolExecutor().shutdown();sUDiskExecutor = null;}} else if (TaskExector.SYNC_SDK_IO.equals(type)) {if (sSyncSdkIOHandleExecutor != null) {sSyncSdkIOHandleExecutor.getThreadPoolExecutor().shutdown();sSyncSdkIOHandleExecutor = null;}} else if (TaskExector.USER_BL.equals(type)) {if (sSyncSdkIOHandleExecutor != null) {sSyncSdkIOHandleExecutor.getThreadPoolExecutor().shutdown();sSyncSdkIOHandleExecutor = null;}}else if (TaskExector.ADAPTER.equals(type)) {if (sAdapterExecutor != null) {sAdapterExecutor.getThreadPoolExecutor().shutdown();sAdapterExecutor = null;}}
//    else if (TaskExector.LOGGER.equals(type)) {
//       if (sLoggerExecutor != null) {
//          sLoggerExecutor.getThreadPoolExecutor().shutdown();
//          sLoggerExecutor = null;
//       }
//    }else if (TaskExector.ACTIVATE_LOG.equals(type)) {if (sActivateLogExecutor != null) {sActivateLogExecutor.getThreadPoolExecutor().shutdown();sActivateLogExecutor = null;}}}public static void onDestory(){Logger.d("byron", "[TaskProxy] onDestory");if(INTERNAL_HANDLER != null){INTERNAL_HANDLER.removeCallbacksAndMessages(null);}}@Overrideprotected ResultType doBackground() throws Exception {this.setState(State.Waiting);TaskPriorityRunnable runnable = new TaskPriorityRunnable(task.getPriority(),null,new Runnable() {@Overridepublic void run() {try {Logger.d("proxy", "taskProxy 1");// trace_start runningTaskProxy.this.setState(State.Running);TaskProxy.this.onStart();result = task.doBackground();if (TaskProxy.this.state.get() == State.Cancelled) { // 没有在doBackground过程中取消成功Logger.d("proxy", "taskProxy 1 cancelled");throw new CancelledException("");}TaskProxy.this.setState(State.Finished);TaskProxy.this.onFinished(result);} catch (CancelledException cex) {Logger.d("proxy", "taskProxy e1 = {?}", cex);TaskProxy.this.setState(State.Cancelled);TaskProxy.this.onCancelled(cex);} catch (Throwable ex) {Logger.d("proxy", "taskProxy e2 = {?}", ex);TaskProxy.this.setState(State.Error);TaskProxy.this.onError(ex, false);}}});this.executor.execute(runnable);return null;}@Overrideprotected void onFinished(ResultType result) {INTERNAL_HANDLER.obtainMessage(MSG_WHAT_ON_FINISH, this).sendToTarget();}@Overrideprotected void onError(Throwable ex, boolean isCallbackError) {exception = ex;INTERNAL_HANDLER.obtainMessage(MSG_WHAT_ON_ERROR, this).sendToTarget();}@Overrideprotected void onStart() {INTERNAL_HANDLER.obtainMessage(MSG_WHAT_ON_START, this).sendToTarget();}@Overrideprotected void onUpdate(int flag, Object... args) {INTERNAL_HANDLER.obtainMessage(MSG_WHAT_ON_UPDATE, flag, 0, new ArgsObj(this, args)).sendToTarget();}@Overrideprotected void onCancelled(CancelledException cex) {cancelledException = cex;INTERNAL_HANDLER.obtainMessage(MSG_WHAT_ON_CANCEL, this).sendToTarget();}private void setState(State state) {this.state.set(state);this.task.state.set(state);}@Overridepublic TaskPriority getPriority() {return task.getPriority();}@Overridepublic Executor getExecutor() {return task.getExecutor();}// ########################### inner type #############################private static class ArgsObj {TaskProxy taskProxy;Object[] args;public ArgsObj(TaskProxy taskProxy, Object[] args) {this.taskProxy = taskProxy;this.args = args;}}private final static int MSG_WHAT_ON_START = 1;private final static int MSG_WHAT_ON_FINISH = 2;private final static int MSG_WHAT_ON_ERROR = 3;private final static int MSG_WHAT_ON_UPDATE = 4;private final static int MSG_WHAT_ON_CANCEL = 5;/*package*/ final static class InternalHandler extends Handler {private InternalHandler() {super(Looper.getMainLooper());}@SuppressWarnings("unchecked")@Overridepublic void handleMessage(Message msg) {if (msg.obj == null) {throw new IllegalArgumentException("msg must not be null");}TaskProxy taskProxy = null;Object[] args = null;if (msg.obj instanceof TaskProxy) {taskProxy = (TaskProxy) msg.obj;} else if (msg.obj instanceof ArgsObj) {ArgsObj argsObj = (ArgsObj) msg.obj;taskProxy = argsObj.taskProxy;args = argsObj.args;}if (taskProxy == null) {throw new RuntimeException("msg.obj not instanceof TaskProxy");}try {switch (msg.what) {case MSG_WHAT_ON_START: {taskProxy.task.onStart();break;}case MSG_WHAT_ON_FINISH: {taskProxy.task.onFinished(taskProxy.result);break;}case MSG_WHAT_ON_ERROR: {try {taskProxy.task.onError(taskProxy.exception, false);} catch (Throwable ignored) {}break;}case MSG_WHAT_ON_UPDATE: {taskProxy.task.onUpdate(msg.arg1, args);break;}case MSG_WHAT_ON_CANCEL: {taskProxy.task.onCancelled(taskProxy.cancelledException);break;}default: {break;}}} catch (Throwable ex) {taskProxy.setState(State.Error);if (msg.what != MSG_WHAT_ON_ERROR) {taskProxy.task.onError(ex, true);} else {ex.printStackTrace();}}}}
}
这里是包含优先级的线程对应的基类,可以通过线程工厂类 创建一个优先级的线程类
/*** 任务线程优先级*/
public class TaskPriorityRunnable implements Runnable {public final TaskPriority priority;private final Runnable runnable;private String name;private String oldThreadName;public TaskPriorityRunnable(TaskPriority priority, String name, Runnable runnable) {this.priority = priority == null ? TaskPriority.DEFAULT : priority;this.runnable = runnable;this.name = name == null ? null : new StringBuilder().append("Executor#").append(name).toString();}@Overridepublic final void run() {// run会进来多次,防止多次设置线程名称引起问题,故添加对name、oldThreadName判空判断if (!TextUtils.isEmpty(name) && TextUtils.isEmpty(oldThreadName)){oldThreadName = Thread.currentThread().getName();Thread.currentThread().setName(name);}this.runnable.run();// oldThreadName不为空时才进入设置线程名称if (!TextUtils.isEmpty(oldThreadName)){Thread.currentThread().setName(oldThreadName);oldThreadName = null;}}
}

三:其他相关类

这里面还有一些业务类的封装,这里就不再一一贴出来了(如有人需要,可以整个贴出来)

public abstract class Task<ResultType> {/*package*/ final AtomicReference<State> state = new AtomicReference<>(State.Null);/*package*/ TaskProxy taskProxy = null;protected abstract ResultType doBackground() throws Exception;protected abstract void onFinished(ResultType result);protected abstract void onError(Throwable ex, boolean isCallbackError);protected void onStart() {}protected void onUpdate(int flag, Object... args) {}protected void onCancelled(CancelledException cex) {}public final void update(int flag, Object... args) {if (taskProxy != null) {taskProxy.onUpdate(flag, args);}}public final void cancel() {this.state.set(State.Cancelled);if (taskProxy != null) {taskProxy.cancel();}}public final State getState() {return state.get();}public final boolean isStopped() {return this.state.get().value() > State.Running.value();}public TaskPriority getPriority() {return null;}public Executor getExecutor() {return null;}public static class CancelledException extends RuntimeException {public CancelledException(String detailMessage) {super(detailMessage);}}public static enum State {Null(0), Waiting(1), Running(2), Finished(3), Cancelled(4), Error(5);private final int value;private State(int value) {this.value = value;}public int value() {return value;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/643139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++中vector的sort用法

在C中&#xff0c;std::vector对象可以通过std::sort函数来排序。std::sort是定义在<algorithm>头文件中的一个通用函数&#xff0c;可以对几乎所有类型的容器进行排序。它不是std::vector的成员函数&#xff0c;但可以直接作用于std::vector。 基本用法很简单&#xff…

Ubuntu findfont: Font family ‘SimHei‘ not found.

matplotlib中文乱码显示 当我们遇到这样奇怪的问题时, 结果往往很搞笑 尝试1不行 Stopping Jupyter Installing font-manager: sudo apt install font-manager Cleaning the matplotlib cache directory: rm ~/.cache/matplotlib -fr Restarting Jupyter. 尝试2 This work fo…

springboot(ssm动漫手办商城 动漫周边商系统Java系统

springboot(ssm动漫手办商城 动漫周边商系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xf…

Kafka-服务端-副本机制

Kafka从0.8版本开始引入副本(Replica)的机制&#xff0c;其目的是为了增加Kafka集群的高可用性。 Kafka实现副本机制之后&#xff0c;每个分区可以有多个副本&#xff0c;并且会从其副本集合(Assigned Replica,AR)中选出一个副本作为Leader副本&#xff0c;所有的读写请求都由…

stm32f103c8r6 串口2数据DMA的接收

#define USART_REC_LEN 16 //定义最大接收字节数 16 extern u8 USART2_RX_BUF[USART_REC_LEN]; u8 USART2_RX_BUF[USART_REC_LEN]; void uart2_init(u32 bound) { //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef U…

Bluetooth Device Address(BD_ADDR) - 2

蓝牙核心规范&#xff1a;Core v5.3中关于蓝牙地址的其他说明 Vol 3: Host, Part C: Generic Access Profile 3 User interface aspects 3.2 Representation of Bluetooth parameters 3.2.1 Bluetooth Device Address (BD_ADDR) BD_ADDR 是蓝牙设备使用的地址。在设备发现过…

IPoE技术汇总

在国内并没有遇到这么多的IPoE&#xff08;IP over Ethernet&#xff09;技术&#xff0c;可能也是因为我来日本多年了&#xff0c;没有接触国内的IPv4 over IPv6的技术&#xff0c;感觉国内IPv4地址紧张&#xff0c;用的传统NAT和PPPoE非常多&#xff0c;大多数设备还是建立在…

使用 YApi 管理 API 文档,测试, mock

随着互联网的发展&#xff0c;API变的至关重要。根据统计&#xff0c;目前市面上有上千万的开发者&#xff0c;互联网项目超过10亿&#xff0c;保守统计涉及的 API 数量大约有 100 亿。这么大基数的API&#xff0c;只要解决某些共有的痛点&#xff0c;将会是非常有意义的事情。…

开发实战角度:distinct实现原理及具体优化总结

1.背景 Distinct是一种常用的操作&#xff0c;在所有数据库的SQl语言中都是一个非常重要的操作&#xff0c;在Hive中&#xff0c;Distinct去重原理是通过MapReduce来实现的&#xff0c;Distinct操作可以应用于单个列&#xff0c;亦可以应用于多个列。基本原理是将输入的数据集…

机器学习期末复习总结笔记(李航统计学习方法)

文章目录 模型复杂度高---过拟合分类与回归有监督、无监督、半监督正则化生成模型和判别模型感知机KNN朴素贝叶斯决策树SVMAdaboost聚类风险PCA深度学习范数计算梯度下降与随机梯度下降SGD线性回归逻辑回归最大熵模型适用性讨论 模型复杂度高—过拟合 是什么&#xff1a;当模型…

MySQL修炼手册14:用户权限管理:安全保障与数据隔离

目录 写在开头1 用户与权限的关系1.1 用户的创建与删除1.1.1 创建新用户1.1.2 批量创建用户1.1.3 安全删除用户 1.2 授予与撤销权限1.2.1 授予权限1.2.2 批量授予权限1.2.3 撤销权限 2 角色的应用2.1 创建与管理角色2.1.1 创建角色2.1.2 管理角色 2.2 将权限赋予角色2.2.1 将权…

golang通过go-git下载gitlab源码

1 申请令牌 方法1&#xff1a;具体项目下申请&#xff1a; 方法2&#xff1a;全局申请 2 获取token 3 下载代码 package mainimport ("fmt""os""github.com/go-git/go-git/v5" )func main() {_, err : git.PlainClone("/tmp/foo",…

java web mvc-07-Vaadin 入门介绍

拓展阅读 Spring Web MVC-00-重学 mvc mvc-01-Model-View-Controller 概览 web mvc-03-JFinal web mvc-04-Apache Wicket web mvc-05-JSF JavaServer Faces web mvc-06-play framework intro web mvc-07-Vaadin web mvc-08-Grails 开源 The jdbc pool for java.(java …

MySQL函数—数值函数,随机数验证码生成

MySQL函数—日期函数 函数功能CEIL(x)向上取整FLOOR(x)向下取整MOD(x,y)返回x/y的模&#xff08;取余&#xff09;RAND()返回0-1的随机数ROUND(x,y)求参数x的四舍五入&#xff0c;保留y位小数 1、向上取整&#xff1a;CEIL。只要小数点后的数字大于0就取整。 select CEIL(1.2…

Qt解析含颜色的QString字符串显示到控件

1、需求 开发接收含颜色字符串显示到窗口&#xff0c;可解析字符串颜色配置窗口属性&#xff0c;且分割字符串显示。 mprintf(“xxxxxx”)&#xff1b;打印的xxxxxx含有颜色配置。 2、实现方法 2.1、条件 选用Qt的PlainTextEdit控件显示字符串&#xff0c;配置为只读模式 …

pytestallure分析redis的数据并动态生成testCase报告

1.pytest.mark.parametrize pytest.mark.parametrize 是一个pytest的装饰器&#xff0c;它可以用于将参数传递给测试函数。使用 pytest.mark.parametrize 装饰器时&#xff0c;需要在装饰器中指定参数名称和参数值。对于多个参数&#xff0c;可以使用多个装饰器。 下面是一些…

数据链路层——笔记·续

使用集线器的星形拓扑 传统以太网传输媒体&#xff1a;粗同轴电缆 -> 细同轴电缆 -> 双绞线。 采用双绞线的以太网采用星形拓扑。 在星形的中心则增加了一种可靠性非常高的设备&#xff0c;叫做集线器 (hub)。 传统以太网使用同轴电缆&#xff0c;采用总线形拓扑结构&am…

嵌入式软件工程师面试题——2025校招社招通用(计算机网络篇)(二十八)

说明&#xff1a; 面试群&#xff0c;群号&#xff1a; 228447240面试题来源于网络书籍&#xff0c;公司题目以及博主原创或修改&#xff08;题目大部分来源于各种公司&#xff09;&#xff1b;文中很多题目&#xff0c;或许大家直接编译器写完&#xff0c;1分钟就出结果了。但…

计算机网络 第3章(数据链路层)

系列文章目录 计算机网络 第1章&#xff08;概述&#xff09; 计算机网络 第2章&#xff08;物理层&#xff09; 计算机网络 第3章&#xff08;数据链路层&#xff09; 文章目录 系列文章目录1. 数据链路层概述1.1 概述1.2 三个重要问题 2. 封装成帧2.1 介绍2.2 透明传输2.3 总…

归一化是是什么意思,为什么要归一化

归一化 归一化是指将数据转换为标准尺度或相对比例的过程。在数据处理中&#xff0c;归一化的目标是使数据具有统一的尺度&#xff0c;以便更好地适应模型的训练和提高模型性能。归一化通常是通过线性变换将数据映射到一个特定的范围或分布。 为什么要进行归一化&#xff1f; …