Android 文件带进度的下载功能实现与封装

网络框架

现在基本都是okhttp3+rotrofit同时你可以加入rxjava3,今天就讲一下这几个结合实现简单的下载功能
先定义接口,下面两个区别就是一个可以断点续传而已

    /*** 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入** @param fileUrl 文件路径地址* @return 观察者*/@Streaming@GETObservable<ResponseBody> downloadFile(@Url String fileUrl);/*** 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入** @param fileUrl 文件路径地址* @return 观察者*/@Streaming@GETObservable<ResponseBody> downloadFile(@Header("Range") String range, @Url String fileUrl);

创建retrofit对象

和普通接口差不多

 private static final int TIME_OUT_SECOND = 120;private static Retrofit builder;private static final Interceptor headerInterceptor = chain -> {Request originalRequest = chain.request();Request.Builder requestBuilder = originalRequest.newBuilder().addHeader("Accept-Encoding", "gzip").method(originalRequest.method(), originalRequest.body());Request request = requestBuilder.build();return chain.proceed(request);};private static Retrofit getDownloadRetrofit() {OkHttpClient.Builder mBuilder = new OkHttpClient.Builder().connectTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).readTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).writeTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).addInterceptor(headerInterceptor);if (builder == null) {builder = new Retrofit.Builder().baseUrl(PropertiesUtil.getInstance().loadConfig(BaseApplication.getInstance()).getBaseUrl()).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).client(mBuilder.build()).build();} else {builder = builder.newBuilder().client(mBuilder.build()).build();}return builder;}

编写rxjava3代码

    public static Observable<File> enqueue(String url,String saveBasePath) {File tempFile = CommonUtil.getTempFile(url, saveBasePath);return getDownloadRetrofit(interceptor).create(BaseApiService.class).downloadFile("bytes=" + tempFile.length() + "-", url).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());}

完成初步代码

其实写到这里基本就已经完成了90%了因为如果你不要进度的话,你这样可以直接拿到ResponseBody然后就是读写文件了
tempFile为本地保存的文件名,可以直接读取url

        try (BufferedSource source = responseBody().source()) {long totalByte = responseBody().contentLength();long downloadByte = 0;if (!tempFile.getParentFile().exists()) {boolean mkdir = tempFile.getParentFile().mkdirs();}byte[] buffer = new byte[1024 * 4];RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rwd");long tempFileLen = tempFile.length();randomAccessFile.seek(tempFileLen);while (true) {int len = responseBody().byteStream().read(buffer);if (len == -1) {break;}randomAccessFile.write(buffer, 0, len);}randomAccessFile.close();} catch (IOException e) {}

完善进度功能

进度也就是根据已下载的文件大小/文件总大小,至于文件总大小后台可以返回也可以读responseBody().contentLength(),已下载的大小总和 所以我们只需要修改下上面的方法就可以得到进度

try (BufferedSource source = responseBody().source()) {long totalByte = responseBody().contentLength();long downloadByte = 0;if (!tempFile.getParentFile().exists()) {boolean mkdir = tempFile.getParentFile().mkdirs();}byte[] buffer = new byte[1024 * 4];RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rwd");long tempFileLen = tempFile.length();randomAccessFile.seek(tempFileLen);while (true) {int len = responseBody().byteStream().read(buffer);if (len == -1) {break;}randomAccessFile.write(buffer, 0, len);downloadByte += len;int progress = (int) ((downloadByte * 100) / totalByte);}randomAccessFile.close();} catch (IOException e) {}

这样进度就有了现在我们可以加上监听或者回调或者直接用通知发送出去,监听回调就不说了,我们介绍下通知

进度通知

编写一个通知工具类,这个不用解释吧,记得创建渠道

public class DownloadNotificationUtil extends ContextWrapper {private final NotificationManager mManager;private NotificationCompat.Builder mBuilder;public DownloadNotificationUtil(Context context) {super(context);mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);}/*** 显示通知栏** @param id 通知消息id*/public void showNotification(int id) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {createNotificationChannel();}mBuilder = new NotificationCompat.Builder(this, ConstantsHelper.DOWNLOAD_CHANNEL_ID);mBuilder.setTicker("开始下载");mBuilder.setOngoing(true);mBuilder.setContentTitle("开始下载");mBuilder.setProgress(100, 0, false);mBuilder.setContentText(0 + "%");mBuilder.setSmallIcon(AppManager.getAppManager().getAppIcon(this));
//        mBuilder.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.ic_launcher));mManager.notify(id, mBuilder.build());}@TargetApi(Build.VERSION_CODES.O)public void createNotificationChannel() {NotificationChannel channel = new NotificationChannel(ConstantsHelper.DOWNLOAD_CHANNEL_ID,ConstantsHelper.DOWNLOAD_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);channel.enableVibration(false);channel.enableLights(true);channel.setSound(null, null);if (notificationManager != null) {notificationManager.createNotificationChannel(channel);}}public long lastClick = 0;/*** [防止快速点击]** @return false --> 快读点击*/public boolean fastClick(long intervalTime) {if (System.currentTimeMillis() - lastClick <= intervalTime) {return true;}lastClick = System.currentTimeMillis();return false;}/*** 更新通知栏进度条** @param id       获取Notification的id* @param progress 获取的进度*/public void updateNotification(int id, int progress, String fileName) {if (fastClick(300) && progress != 100) {return;}if (mBuilder != null) {mBuilder.setContentTitle(fileName);mBuilder.setSmallIcon(AppManager.getAppManager().getAppIcon(this));mBuilder.setProgress(100, progress, false);mBuilder.setContentText(progress + "%");mManager.notify(id, mBuilder.build());}}public void sendNotificationFullScreen(int notifyId, String title, String content, File apkFile) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {NotificationChannel channel = new NotificationChannel(ConstantsHelper.DOWNLOAD_CHANNEL_ID,ConstantsHelper.DOWNLOAD_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);mManager.createNotificationChannel(channel);PendingIntent fullScreenPendingIntent = null;if (apkFile != null) {Intent i = new Intent(Intent.ACTION_VIEW);i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_GRANT_WRITE_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件Uri apkFileUri = FileProvider.getUriForFile(getApplicationContext(),getPackageName() + ".FileProvider", apkFile);i.setDataAndType(apkFileUri, "application/vnd.android.package-archive");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {fullScreenPendingIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_IMMUTABLE);} else {fullScreenPendingIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);}}NotificationCompat.Builder notificationBuilder =new NotificationCompat.Builder(this, ConstantsHelper.DOWNLOAD_CHANNEL_ID).setContentTitle(title).setTicker(content).setContentText(content).setAutoCancel(true).setSmallIcon(AppManager.getAppManager().getAppIcon(this)).setDefaults(Notification.DEFAULT_ALL).setPriority(NotificationCompat.PRIORITY_MAX).setCategory(Notification.CATEGORY_CALL).setFullScreenIntent(fullScreenPendingIntent, true);mManager.notify(notifyId, notificationBuilder.build());}}public void clearAllNotification() {if (mManager == null) {return;}mManager.cancelAll();}/*** 取消通知栏通知*/public void cancelNotification(int id) {if (mManager == null) {return;}mManager.cancel(id);}}

调用通知

downloadNotificationUtil.showNotification(fileUrl.hashCode())
downloadNotificationUtil.updateNotification(fileUrl.hashCode(),progress, FileUtils.getFileName(fileUrl))
downloadNotificationUtil.cancelNotification(fileUrl.hashCode())

提出问题

问题来了,读写文件肯定不能在主线程里操作对吧,那么我们同样也不能再subscribe操作符中写,因为大部分他都要回调主线程的,所以我们结合rxjava3的知识可以利用flatmap等操作符切换到子线程操作,那么我们可以直接修改一下方法

    public static Observable<File> enqueue(String url,String saveBasePath) {File tempFile = CommonUtil.getTempFile(url, saveBasePath);DownloadInterceptor interceptor = new DownloadInterceptor();return getDownloadRetrofit(interceptor).create(BaseApiService.class).downloadFile("bytes=" + tempFile.length() + "-", url).subscribeOn(Schedulers.io()).flatMap(responseBody ->//这里需要返回一个ObservableOnSubscribe那么我们新建一个对象).observeOn(AndroidSchedulers.mainThread());}
public class DownloadObservable implements ObservableOnSubscribe<File> {private final File tempFile;private final DownloadNotificationUtil downloadNotificationUtil;private final String fileUrl;public DownloadObservable( String fileUrl, File tempFile) {this.tempFile = tempFile;this.fileUrl = fileUrl;downloadNotificationUtil = new DownloadNotificationUtil(BaseApplication.getInstance());downloadNotificationUtil.showNotification(fileUrl.hashCode());}@Overridepublic void subscribe(ObservableEmitter<File> emitter) throws Exception {try (BufferedSource source = responseBody().source()) {long totalByte = responseBody().contentLength();long downloadByte = 0;if (!tempFile.getParentFile().exists()) {boolean mkdir = tempFile.getParentFile().mkdirs();}byte[] buffer = new byte[1024 * 4];RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rwd");long tempFileLen = tempFile.length();randomAccessFile.seek(tempFileLen);while (true) {int len = responseBody().byteStream().read(buffer);if (len == -1) {break;}randomAccessFile.write(buffer, 0, len);downloadByte += len;int progress = (int) ((downloadByte * 100) / totalByte);downloadNotificationUtil.updateNotification(fileUrl.hashCode(),progress, FileUtils.getFileName(fileUrl));}randomAccessFile.close();emitter.onNext(tempFile);emitter.onComplete();} catch (IOException e) {downloadNotificationUtil.cancelNotification(fileUrl.hashCode());emitter.onError(e);emitter.onComplete();}}
}

然后我们完善enqueue

public static Observable<File> enqueue(String url,String saveBasePath) {File tempFile = CommonUtil.getTempFile(url, saveBasePath);return getDownloadRetrofit().create(BaseApiService.class).downloadFile("bytes=" + tempFile.length() + "-", url).subscribeOn(Schedulers.io()).flatMap(responseBody ->Observable.create(new DownloadObservable(interceptor, url, tempFile, saveBasePath))).observeOn(AndroidSchedulers.mainThread());}

然后你就实现了有进度的下载通知功能

新的问题出现了

你会发现通知和吐司什么的报错,因为他不能在子线程操作UI,但是文件下载又必须在子线程,所以你可以直接创建一个handler去切换到主线程

    private final Handler mainHandler = new Handler(Looper.getMainLooper());

调用

 Disposable disposable = DownloadManger.getInstance().download(this, binding.editUrl.getText().toString().trim()).subscribe(file -> {LogUtil.show(ApiRetrofit.TAG,"下载成功:"+file.getAbsolutePath());showToast("下载成功!");}, throwable -> {if (throwable instanceof BaseException baseException) {showToast(baseException.getErrorMsg());return;}showToast(throwable.getMessage());});

这里基本算完成95%了,正常情况下你没问题,问题出在下载地址上,因为有的下载地址没有文件名

http://192.168.1.1:8089/api/download/file/168878445

这样就没了所以你没办法判断他的文件名和文件类型也就没办法写成文件,因为不知道写成什么类型的文件
所以我们需要读取文件请求头中的信息去获取文件类型和文件名

优化

先读写成.tmp的临时文件

也就是前面提到的File tempFile = CommonUtil.getTempFile(url, saveBasePath);方法就不提供了,就是在缓存目录下读写一个.tmp的临时文件,下载完读取请求头中的文件类型和文件名后重命名一下文件

工具类

因为有的部分大小写不一样,所以我们需要忽略大小写

 /*** 忽略大小写查找请求头参数*/private static String findHeaderIgnoreCase(Headers headers, String headerName) {for (String name : headers.names()) {if (name.equalsIgnoreCase(headerName)) {return headers.get(name);}}return null;}private static String getFileNameFromForceDownloadHeader(String contentDispositionHeader) {if (TextUtils.isEmpty(contentDispositionHeader)) {return "unknown";}// 匹配Content-Disposition中的filename属性Pattern pattern = Pattern.compile(".*filename=\"?([^\\s;]+)\"?.*");Matcher matcher = pattern.matcher(contentDispositionHeader.toLowerCase());if (matcher.matches()) {return matcher.group(1);}return "unknown";}

写完临时文件后重命名

这个时候你会发现读取文件类型和文件名需要返回信息中的header信息,但是请求指定写

    @Streaming@GETObservable<ResponseBody> downloadFile(@Header("Range") String range, @Url String fileUrl);

你不可以吧ResponseBody写成Response系统不支持会报错

思考一下:那么我们怎么解决问题了?

哪里有返回的header信息?

当然要找retrofit了,那retrofit又如何获取header呢?那就是拦截器
所以我们需要再创建retrofit创建的时候给他设置一个拦截器,获取headerbody

public class DownloadInterceptor implements Interceptor {private Headers headers;private ResponseBody responseBody;public Headers getHeaders() {return headers;}public ResponseBody getResponseBody() {return responseBody;}public DownloadInterceptor() {}@Overridepublic Response intercept(@NonNull Chain chain) throws IOException {Response originalResponse = chain.proceed(chain.request());headers = originalResponse.headers();return originalResponse.newBuilder().body(responseBody = originalResponse.body()).build();}
}

有了拦截器我们在下载的时候吧header传过去即可

注意

每次下载都要创建新的拦截器,不然他获取的header``body就是上次的

最后提供下完整代码

public class DownloadRetrofitFactory {private static final int TIME_OUT_SECOND = 120;private static Retrofit builder;private static final Interceptor headerInterceptor = chain -> {Request originalRequest = chain.request();Request.Builder requestBuilder = originalRequest.newBuilder().addHeader("Accept-Encoding", "gzip").method(originalRequest.method(), originalRequest.body());Request request = requestBuilder.build();return chain.proceed(request);};private static Retrofit getDownloadRetrofit(DownloadInterceptor downloadInterceptor) {OkHttpClient.Builder mBuilder = new OkHttpClient.Builder().connectTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).readTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).writeTimeout(TIME_OUT_SECOND, TimeUnit.SECONDS).addInterceptor(headerInterceptor).addInterceptor(downloadInterceptor);if (builder == null) {builder = new Retrofit.Builder().baseUrl(PropertiesUtil.getInstance().loadConfig(BaseApplication.getInstance()).getBaseUrl()).addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava3CallAdapterFactory.create()).client(mBuilder.build()).build();} else {builder = builder.newBuilder().client(mBuilder.build()).build();}return builder;}/*** 取消网络请求*/public static void cancel(Disposable d) {if (null != d && !d.isDisposed()) {d.dispose();}}public static Observable<File> enqueue(String url,String saveBasePath) {File tempFile = CommonUtil.getTempFile(url, saveBasePath);DownloadInterceptor interceptor = new DownloadInterceptor();return getDownloadRetrofit(interceptor).create(BaseApiService.class).downloadFile("bytes=" + tempFile.length() + "-", url).subscribeOn(Schedulers.io()).flatMap(responseBody ->Observable.create(new DownloadObservable(interceptor, url, tempFile, saveBasePath))).observeOn(AndroidSchedulers.mainThread());}
}
public class DownloadObservable implements ObservableOnSubscribe<File> {private final File tempFile;private final DownloadInterceptor interceptor;private final DownloadNotificationUtil downloadNotificationUtil;private final String saveBasePath;private final String fileUrl;private final Handler mainHandler = new Handler(Looper.getMainLooper());public DownloadObservable(DownloadInterceptor interceptor, String fileUrl, File tempFile, String saveBasePath) {this.tempFile = tempFile;this.interceptor = interceptor;this.saveBasePath = saveBasePath;this.fileUrl = fileUrl;downloadNotificationUtil = new DownloadNotificationUtil(BaseApplication.getInstance());mainHandler.post(() -> downloadNotificationUtil.showNotification(fileUrl.hashCode()));}@Overridepublic void subscribe(ObservableEmitter<File> emitter) throws Exception {try (BufferedSource source = interceptor.getResponseBody().source()) {long totalByte = interceptor.getResponseBody().contentLength();long downloadByte = 0;if (!tempFile.getParentFile().exists()) {boolean mkdir = tempFile.getParentFile().mkdirs();}byte[] buffer = new byte[1024 * 4];RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rwd");long tempFileLen = tempFile.length();randomAccessFile.seek(tempFileLen);while (true) {int len = interceptor.getResponseBody().byteStream().read(buffer);if (len == -1) {break;}randomAccessFile.write(buffer, 0, len);downloadByte += len;int progress = (int) ((downloadByte * 100) / totalByte);mainHandler.post(() -> downloadNotificationUtil.updateNotification(fileUrl.hashCode(),progress, FileUtils.getFileName(fileUrl)));}randomAccessFile.close();String fileName;MediaType mediaType = interceptor.getResponseBody().contentType();String contentDisposition = findHeaderIgnoreCase(interceptor.getHeaders(), "Content-Disposition");//获取请求头中的Content-Disposition,有值的话说明指定了文件名和后缀名if (mediaType != null && !TextUtils.isEmpty(contentDisposition)) {fileName = FileUtils.autoRenameFileName(saveBasePath, getFileNameFromForceDownloadHeader(contentDisposition));} else {fileName = FileUtils.autoRenameFileName(saveBasePath, FileUtils.getFileNameByUrl(fileUrl));}File newFile = new File(saveBasePath + fileName);boolean renameSuccess = tempFile.renameTo(newFile);mainHandler.post(() -> downloadNotificationUtil.cancelNotification(fileUrl.hashCode()));if (renameSuccess) {mainHandler.post(() -> Toast.makeText(BaseApplication.getInstance(), "文件已保存至" + newFile.getAbsolutePath(), Toast.LENGTH_SHORT).show());emitter.onNext(newFile);} else {mainHandler.post(() -> Toast.makeText(BaseApplication.getInstance(), "文件已保存至" + tempFile.getAbsolutePath(), Toast.LENGTH_SHORT).show());emitter.onNext(tempFile);}emitter.onComplete();} catch (IOException e) {mainHandler.post(() -> downloadNotificationUtil.cancelNotification(fileUrl.hashCode()));emitter.onError(e);emitter.onComplete();}}/*** 忽略大小写查找请求头参数*/private static String findHeaderIgnoreCase(Headers headers, String headerName) {for (String name : headers.names()) {if (name.equalsIgnoreCase(headerName)) {return headers.get(name);}}return null;}private static String getFileNameFromForceDownloadHeader(String contentDispositionHeader) {if (TextUtils.isEmpty(contentDispositionHeader)) {return "unknown";}// 匹配Content-Disposition中的filename属性Pattern pattern = Pattern.compile(".*filename=\"?([^\\s;]+)\"?.*");Matcher matcher = pattern.matcher(contentDispositionHeader.toLowerCase());if (matcher.matches()) {return matcher.group(1);}return "unknown";}
}
public class DownloadManger {/*** 进度条与通知UI刷新的handler和msg常量*/private static volatile DownloadManger updateManger;private final List<String> downloadMap = new ArrayList<>();private DownloadManger() {}public static DownloadManger getInstance() {if (updateManger == null) {synchronized (DownloadManger.class) {if (updateManger == null) {updateManger = new DownloadManger();}}}return updateManger;}/*** 下载文件** @param mContext     当前视图* @param fileUrl      下载文件路径* @param saveBasePath 保存文件路径默认文件路径为RxNet.PATH,*/public Observable<File> download(Activity mContext, String fileUrl, String saveBasePath) {if (TextUtils.isEmpty(fileUrl)) {return Observable.error(new BaseException(BaseException.DOWNLOAD_URL_404_MSG, BaseException.DOWNLOAD_URL_404));}if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {if (ContextCompat.checkSelfPermission(mContext, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {//申请WRITE_EXTERNAL_STORAGE权限ActivityCompat.requestPermissions(mContext, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},0x02);return Observable.error(new BaseException(BaseException.DOWNLOAD_NOT_PERMISSION_MSG, BaseException.DOWNLOAD_NOT_PERMISSION));}}if (downloadMap.contains(fileUrl)) {return Observable.error(new BaseException(BaseException.DOWNLOADING_ERROR_MSG, BaseException.DOWNLOADING_ERROR));}downloadMap.add(fileUrl);if (TextUtils.isEmpty(saveBasePath)) {saveBasePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() +File.separator + FileUtils.getDefaultBasePath(mContext) + File.separator;}return DownloadRetrofitFactory.enqueue(fileUrl, saveBasePath).map(file -> {downloadMap.remove(fileUrl);return file;});}public Observable<File> download(Activity mContext, String fileUrl) {return download(mContext, fileUrl,Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() +File.separator + FileUtils.getDefaultBasePath(mContext) + File.separator);}}

调用

 Disposable disposable = DownloadManger.getInstance().download(this, binding.editUrl.getText().toString().trim()).subscribe(file -> {LogUtil.show(ApiRetrofit.TAG,"下载成功:"+file.getAbsolutePath());showToast("下载成功!");}, throwable -> {if (throwable instanceof BaseException baseException) {showToast(baseException.getErrorMsg());return;}showToast(throwable.getMessage());});

没了!!!
甩个github地址:https://github.com/fzkf9225/mvvm-componnent-master/blob/master/common/src/main/java/pers/fz/mvvm/util/update/DownloadManger.java

拓展

上面只有单文件下载,其实多文件也一样,利用rxjava的特性

    /*** RxJava方式下载附件,需要自己判断权限*/public Single<List<File>> download(Activity mContext, List<String> urlString) {return download(mContext, urlString, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() +File.separator + FileUtils.getDefaultBasePath(mContext) + File.separator);}/*** RxJava方式下载附件,需要自己判断权限*/public Single<List<File>> download(Activity mContext, List<String> urlString, String saveBasePath) {return Observable.fromIterable(urlString).flatMap((Function<String, ObservableSource<File>>) filePath -> download(mContext, filePath, saveBasePath)).toList().subscribeOn(Schedulers.io());}

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

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

相关文章

Linux相关概念和易错知识点(19)(HDD、Block group)

目录 1.HDD &#xff08;1&#xff09;HDD存储描述 &#xff08;2&#xff09;HDD结构图 &#xff08;3&#xff09;磁盘管理的分治思想 &#xff08;4&#xff09;硬盘中文件系统的整体划分图 2.Block group &#xff08;1&#xff09;文件管理 ①文件属性的存储 ②in…

WWDC24(Xcode 16)中全新的 Swift Testing 使用进阶

概述 WWDC 24 祭出的全新单元测试系统着实让苹果开发者们眼前一亮。“原来测试还可以这么爽&#xff01;&#xff1f;”&#xff0c;日渐逼近蟋蟀发型的某位码农如是说。 Swift Testing 在简洁性以及灵活性全面超越老大哥 XCTest 的同时&#xff0c;也让秃头码农们真正见识到了…

k8s-service、endpoints、pod之间是怎么进行网络互通的

k8s-service、endpoints、pod之间是怎么进行网络互通的 1、service2、endpoints3、service、endpoints、pod通信图4、不同服务pod内部间访问 1、service 在K8S中&#xff0c;Service是一种抽象&#xff0c;定义了一组Pod的逻辑集合和访问这些Pod的策略。首先&#xff0c;我们需…

命令行工具PowerShell使用体验

命令行工具PowerShell使用 PowerShell是微软开发的一种面向对象的命令行Shell和脚本语言环境&#xff0c;它允许用户通过命令行的方式管理操作系统。相较于传统CMD&#xff0c;PowerShell增加了面向对象的程序设计框架&#xff0c;拥有更强大的功能和扩展性。使用PowerShell可…

企业IT架构转型之道:阿里巴巴中台战略思想与架构实战感想

文章目录 第一章&#xff1a;数据库水平扩展第二章&#xff1a;中台战略第三章&#xff1a;阿里分布式服务架构HSF&#xff08;high speed Framework&#xff09;、早期Dubbo第四章&#xff1a;共享服务中心建设原则第五章&#xff1a;数据拆分实现数据库能力线性扩展第六章&am…

【优选算法篇】微位至简,数之恢宏——解构 C++ 位运算中的理与美

文章目录 C 位运算详解&#xff1a;基础题解与思维分析前言第一章&#xff1a;位运算基础应用1.1 判断字符是否唯一&#xff08;easy&#xff09;解法&#xff08;位图的思想&#xff09;C 代码实现易错点提示时间复杂度和空间复杂度 1.2 丢失的数字&#xff08;easy&#xff0…

在 WPF 中,绑定机制是如何工作的?WPF数据绑定机制解析

在WPF&#xff08;Windows Presentation Foundation&#xff09;中&#xff0c;数据绑定机制是其核心功能之一&#xff0c;广泛用于连接应用程序的UI&#xff08;用户界面&#xff09;和应用程序的业务逻辑层。数据绑定允许你将UI元素与数据源&#xff08;如对象、集合或其他数…

基于 STM32 的天气时钟项目中添加天气数据的网络获取功能

基于 STM32 的天气时钟项目中添加天气数据的网络获取功能&#xff0c;您需要确保您的开发环境具备网络连接能力。这里以 ESP8266 Wi-Fi 模块为例&#xff0c;详细说明如何实现网络获取天气数据的功能。 1. 硬件连接 连接 ESP8266 模块 请参考以下连接方式&#xff0c;将 ESP82…

晓宇电视 1.9 | 电视直播软件,几千频道,高清秒播

晓宇电视是一款电视直播软件&#xff0c;提供数千个高清频道&#xff0c;支持秒播。最大的特色是没有广告&#xff0c;且不需要用户手动更新源地址。安装后即可使用&#xff0c;频道节目丰富&#xff0c;包括影视剧轮播专区&#xff0c;用户可以轻松观看喜爱的电影和电视剧。软…

双指针算法的妙用:提高代码效率的秘密(2)

双指针算法的妙用&#xff1a;提高代码效率的秘密&#xff08;2&#xff09; 前言&#xff1a; 小编在前几日讲述了有关双指针算法两道题目的讲解&#xff0c;今天小编继续进行有关双指针算法习题的讲解&#xff0c;老规矩&#xff0c;今天还是两道题目的讲解&#xff0c;希望…

MySQL:客户端工具创建数据库

MySQL 是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;用于存储、管理和检索数据。MySQL是基于SQL语言的&#xff0c;它具有高效、可靠、易用的特点。 客户端工具 这个mysqld.exe就在计算机安装的数据可服务&#xff0c;启动之后&#xff0c;mys…

使用python向钉钉群聊发送消息

使用python向钉钉群聊发送消息 一、在钉钉群中新建机器人二、使用代码发送消息 一、在钉钉群中新建机器人 在群设置中添加机器人 选择自定义 勾选对应的安全设置 完成后会展示webhook&#xff0c;将地址复制出来&#xff0c;并记录&#xff0c;后面会用到 二、使用代码发送消…

【芯智雲城】Sigmastar星宸科技图传编/解码方案

一、图传技术简介 图传是指将图像或媒体内容从一个设备传输到另外一个设备的技术&#xff0c;传输的媒介可以是无线电波、光纤、以太网等。图传系统主要由图像采集设备、传输设备和接收设备组成&#xff0c;图像采集设备负责采集实时图像&#xff0c;传输设备将采集到的图像转…

sql专题 之 常用命令

文章目录 查询基础语法查询全表查询选择查询&#xff1a;常量和运算&#xff1a; 条件查询where运算符&#xff1a;、 !、<、>空值&#xff1a;null模糊查询&#xff1a;like逻辑运算&#xff1a;and or not 去重&#xff1a;distinct排序&#xff1a;order by截断和偏移…

Linux学习笔记之定时任务调度

crond 任务调度 任务调度&#xff1a;指系统在某个时间执行的特定的命令或程序 任务调度分类&#xff1a;1.系统工作&#xff1a;有些重要的工作必须周而复始地执行&#xff0c;如病毒扫描等。 2.个别用户工作&#xff1a;个别用户可能希望执行某些程序&#xff0c;如对mysql数…

MyBatisPlus 用法详解

文章目录 一、快速入门1.1 引入依赖&#xff1a;1.2 定义 Mappper&#xff1a;1.3 使用演示&#xff1a;1.4 常见注解&#xff1a;1.4.1 TableName:1.4.2 TableId&#xff1a;1.4.3 TableField&#xff1a; 1.5 常见配置&#xff1a; 二、核心功能2.1 条件构造器&#xff1a;2.…

C++ -- 多态与虚函数

多态 概念 多态&#xff08;polymorphishm&#xff09;&#xff1a;通常来说&#xff0c;就是指事物的多种形态。在C中&#xff0c;多态可分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;&#xff0c;这里我们重点讲的是运行时多…

ORU 的 Open RAN 管理平面 (M 平面)

[TOC](ORU 的 Open RAN 管理平面 (M 平面)) ORU 的 Open RAN 管理平面 (M 平面) https://www.techplayon.com/open-ran-management-plane-m-plane-for-open-radio-unit/ ORU M 平面 在 ORAN 中&#xff0c;设置参数的 O-RU 管理功能是通过 M-Plane 完成的。管理功能包括 O-…

使用Go语言编写一个简单的NTP服务器

NTP服务介绍 NTP服务器【Network Time Protocol&#xff08;NTP&#xff09;】是用来使计算机时间同步化的一种协议。 应用场景说明 为了确保封闭局域网内多个服务器的时间同步&#xff0c;我们计划部署一个网络时间同步服务器&#xff08;NTP服务器&#xff09;。这一角色将…

电信网关配置管理系统 upload_channels.php 文件上传致RCE漏洞复现

0x01 产品简介 中国电信集团有限公司(英文名称“China Telecom”、简称“中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远…