深入探索Android Service:后台服务的终极指南(中)


引言


在深入探索了Service的基本概念和生命周期管理后,本文将聚焦于Android Service的进阶应用,包括前台服务的运用、Android 5.0以上版本中隐式启动Service的问题、确保Service稳定性的策略,以及Service在进程间通信和复杂后台任务处理中的高效利用。我们将通过实际代码示例,展示如何在实际开发中应用这些高级特性。


一、前台服务与通知

在Android中,前台服务(Foreground Service)是一种用户明确感知到的服务,比如音乐播放器。前台服务通过在状态栏中显示一个持久的通知来告知用户服务正在进行中。系统倾向于保留前台服务,使其不易被系统在内存不足时杀死。


以下是如何创建和启动一个前台服务的代码示例:

1、创建通知

首先,你需要创建一个通知,该通知将显示在状态栏中,告知用户服务正在运行。

public class MyForegroundService extends Service {private static final int NOTIFICATION_ID = 1;@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {// 创建通知Notification notification = new NotificationCompat.Builder(this, "channel_id") // 对于Android 8.0以上需要一个通知渠道.setContentTitle("Foreground Service").setContentText("Service is running in the background").setSmallIcon(R.drawable.ic_notification) // 状态栏图标.setChannelId("channel_id") // Android 8.0以上需要设置通知渠道的ID.setOngoing(true) // 通知是持久的.build();// 启动前台服务startForeground(NOTIFICATION_ID, notification);// 服务的其他逻辑...return START_STICKY; // 或 START_REDELIVER_INTENT,根据服务需求选择}@Overridepublic void onDestroy() {// 停止前台服务stopForeground(true);super.onDestroy();}@Overridepublic IBinder onBind(Intent intent) {// 如果服务不需要绑定,则返回nullreturn null;}
}

2、在AndroidManifest.xml中声明Service


确保你的AndroidManifest.xml文件中已经声明了服务,并且如果你打算在Android 8.0(API级别26)以上使用通知渠道,还需要在清单中声明一个通知渠道。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.myapp"><application... ><service android:name=".MyForegroundService" /><!-- Android 8.0以上版本需要的通知渠道 --><service android:name=".NotificationChannelService"><intent-filter><action android:name="android.intent.action.MAIN" /></intent-filter></service></application>
</manifest>

3、启动前台服务

最后,在Activity或其他组件中,使用Intent启动前台服务。

Intent serviceIntent = new Intent(this, MyForegroundService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {startForegroundService(serviceIntent); // Android 8.0及以上使用startForegroundService
} else {startService(serviceIntent);
}

4、注意事项

  • 从Android 8.0(API级别26)开始,所有通知必须通过通知渠道发出。确保你已经创建了至少一个通知渠道。
  • 前台服务的通知应该是用户可感知的,避免滥用前台服务。
  • 当服务不再需要运行在前台时,应该调用stopForeground(true)来停止前台状态并移除通知。之后,服务可以继续作为一个普通后台服务运行。

通过上述步骤,你可以在Android应用中创建和管理前台服务。


二、 Android 5.0以上隐式启动问题

在Android 5.0(API级别21)及以上版本中,使用隐式Intent启动Service是一个不安全的做法,因为Google为了提高系统的安全性,限制了隐式Intent启动Service的能力。以下是如何在Android 5.0及以上版本中解决隐式启动Service问题的具体方法和代码示例。


1、隐式启动Service的问题

在Android 5.0之前,你可以使用一个隐式Intent来启动Service,如下所示:

Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.myapp.SERVICE_ACTION");
startService(serviceIntent);

但是,在Android 5.0及以上版本中,上述代码会抛出SecurityException异常,因为系统不允许通过隐式Intent启动Service。


2、解决方案

为了解决这个问题,你可以采取以下两种方法之一:


方法1:设置Action和PackageName

在Intent中显式地设置Action和当前应用的PackageName,如下所示:

Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.myapp.SERVICE_ACTION");
serviceIntent.setPackage(getPackageName());
startService(serviceIntent);

这里,getPackageName()是当前应用的包名,而"com.example.myapp.SERVICE_ACTION"是你Service的Action字符串。


方法2:将隐式Intent转换为显式Intent

通过将隐式Intent转换为显式Intent,可以确保Intent安全地启动Service。以下是如何进行转换的示例代码:

public static Intent getExplicitIntent(Context context, Intent implicitIntent) {// 检索所有匹配给定Intent的服务PackageManager pm = context.getPackageManager();List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);// 确保只找到一个匹配的服务if (resolveInfo == null || resolveInfo.size() != 1) {return null;}// 获取组件信息并创建ComponentNameResolveInfo serviceInfo = resolveInfo.get(0);String packageName = serviceInfo.serviceInfo.packageName;String className = serviceInfo.serviceInfo.name;ComponentName component = new ComponentName(packageName, className);// 创建一个新的Intent,使用旧的Intent来重用extras等Intent explicitIntent = new Intent(implicitIntent);// 设置组件为显式的explicitIntent.setComponent(component);return explicitIntent;
}

使用上述方法,你可以这样启动Service:

Intent mIntent = new Intent(); // 辅助Intent
mIntent.setAction("com.example.myapp.SERVICE_ACTION");
final Intent serviceIntent = new Intent(getExplicitIntent(this, mIntent));
if (serviceIntent != null) {startService(serviceIntent);
}

在上述代码中,我们首先创建了一个隐式Intent(mIntent),然后调用getExplicitIntent方法将其转换为显式Intent(serviceIntent),最后使用显式Intent安全地启动Service。


3、注意事项

  • 确保Service的Action字符串是唯一的,以避免与其他应用的Service冲突。
  • 使用显式Intent而不是隐式Intent,可以提高应用的安全性和稳定性。
  • 在清单文件中为Service指定正确的权限,如果需要的话。

通过上述方法,你可以确保在Android 5.0及以上版本中安全地启动Service。


三、 保证Service不被杀死的策略

在Android中,保证Service不被系统杀死主要涉及到Service的生命周期管理和一些特定的策略。以下是一些常用的策略和相应的代码示例。

1、使用START_STICKY返回值

onStartCommand方法中返回START_STICKY,这样当Service因为内存不足而被系统杀死后,系统会尝试重新启动Service。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {// 处理服务任务return START_STICKY;
}


2、使用START_REDELIVER_INTENT

返回START_REDELIVER_INTENT,这样当Service被杀死时,系统会尝试重新传递最后一个Intent给Service。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {// 处理服务任务return START_REDELIVER_INTENT;
}

3、创建前台Service

将Service设置为前台Service,这样可以提高Service的优先级,减少被系统杀死的风险。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {Notification notification = new NotificationCompat.Builder(this, "channel_id").setContentTitle("Service Title").setContentText("Service is running").build();startForeground(NOTIFICATION_ID, notification);// 处理服务任务return START_STICKY;
}

4、监听系统广播重启Service

onDestroy方法中发送一个自定义广播,然后在一个BroadcastReceiver中监听这个广播并重启Service。

@Override
public void onDestroy() {// 发送重启Service的广播Intent restartIntent = new Intent(this, ServiceRestartReceiver.class);sendBroadcast(restartIntent);super.onDestroy();
}public static class ServiceRestartReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {// 重启Servicecontext.startService(new Intent(context, MyService.class));}
}

确保在AndroidManifest.xml中注册ServiceRestartReceiver

<receiver android:name=".ServiceRestartReceiver" />

5、监控Service状态并在需要时重启

创建一个辅助的Service或BroadcastReceiver来监控你的主Service状态,并在它被杀死时重启。

public class ServiceWatcher extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {if (isServiceRunning(context, MyService.class)) {// Service is running, no action needed} else {// Service is not running, restart itcontext.startService(new Intent(context, MyService.class));}}public boolean isServiceRunning(Context context, Class<?> serviceClass) {ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {if (serviceClass.getName().equals(service.service.getClassName())) {return true;}}return false;}
}

并在AndroidManifest.xml中注册ServiceWatcher

<receiver android:name=".ServiceWatcher" />

使用这些策略时,需要考虑应用的用户体验和系统资源的合理使用。滥用这些机制可能会导致系统资源紧张,影响设备的整体性能,甚至可能导致应用被系统或用户杀死。因此,合理使用这些策略,并且只在确实需要时使用它们。


四、通过Service进行有效的进程间通信(IPC)

在Android中,Service可以实现进程间通信(IPC),允许不同应用或同一应用的不同组件之间交换信息。IPC通常通过Binder机制实现。以下是通过Service进行进程间通信的步骤和代码示例。


1、定义Binder类

首先,你需要定义一个Binder类,它是Service和客户端之间通信的桥梁。

public class MyBinder extends Binder {public MyService getService() {return MyService.this;}
}public class MyService extends Service {private final IBinder binder = new MyBinder();// ...
}

2、实现Service的onBind方法

在Service中,重写onBind方法并返回Binder对象。

@Override
public IBinder onBind(Intent intent) {return binder;
}

3、在客户端绑定Service

客户端使用bindService方法绑定Service。

private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 获取Service的实例MyBinder myBinder = (MyBinder) service;MyService myService = myBinder.getService();// 现在可以调用Service的公共方法}@Overridepublic void onServiceDisconnected(ComponentName name) {// 处理Service连接断开的情况}
};Intent serviceIntent = new Intent(this, MyService.class);
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);

4、在Service中定义公共方法

在Service中,定义客户端可以调用的公共方法。

public class MyService extends Service {// ...public void doSomething() {// 执行一些操作}
}

客户端通过Binder调用这些方法:

myService.doSomething();

5、处理跨进程通信

如果Service需要跨进程通信,可以使用Messenger或AIDL(Android接口定义语言)。


(1)、使用Messenger

第一步,在Service中创建一个Handler和一个Messenger

public class MyMessengerService extends Service {private final Messenger messenger = new Messenger(new IncomingHandler());@Overridepublic IBinder onBind(Intent intent) {return messenger.getBinder();}private class IncomingHandler extends Handler {public void handleMessage(Message msg) {// 处理消息// 可以选择回复消息Message replyMsg = Message.obtain();// 设置回复消息并发送try {messenger.send(replyMsg);} catch (RemoteException e) {e.printStackTrace();}}}
}

第二步、在客户端发送消息给Service。

private ServiceConnection serviceConnection = new ServiceConnection() {// ...
};Intent serviceIntent = new Intent(this, MyMessengerService.class);
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);// 发送消息
Message message = Message.obtain();
message.what = SOME_MSG;
// 可以设置更多信息,如arg1, arg2, obj等
messenger.send(message);

(2)、使用AIDL

第一步,定义AIDL接口。

// ISomeService.aidl
package com.example.myapp;interface ISomeService {void doSomething();
}

第二步,实现AIDL接口。

public class SomeService extends Service {private final ISomeService.Stub binder = new ISomeService.Stub() {@Overridepublic void doSomething() {// 实现方法}};@Overridepublic IBinder onBind(Intent intent) {return binder;}
}

第三步,客户端绑定Service并调用方法。

private ServiceConnection serviceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {ISomeService someService = ISomeService.Stub.asInterface(service);// 调用远程方法try {someService.doSomething();} catch (RemoteException e) {e.printStackTrace();}}@Overridepublic void onServiceDisconnected(ComponentName name) {// ...}
};Intent serviceIntent = new Intent(this, SomeService.class);
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);

使用这些方法,你可以实现Service的进程间通信。记得在AndroidManifest.xml中声明Service,并为跨进程通信的Service添加android:exported="true"属性。


五、 实战案例:利用Service实现复杂的后台任务处理

在Android中,使用Service执行文件下载任务是常见的应用场景。以下是一个使用Service进行文件下载的示例,包括Service的创建、绑定,以及在客户端启动下载任务和接收下载进度更新。


1、创建下载Service

首先,创建一个Service类来处理下载任务。

public class DownloadService extends Service {public static final String ACTION_DOWNLOAD = "com.example.DOWNLOAD";public static final String EXTRA_URL = "com.example.EXTRA_URL";private DownloadManager downloadManager;private Handler handler;@Overridepublic void onCreate() {super.onCreate();downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);handler = new Handler();}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {String downloadUrl = intent.getStringExtra(EXTRA_URL);if (downloadUrl != null && !downloadUrl.isEmpty()) {startDownload(downloadUrl);}return START_NOT_STICKY;}private void startDownload(String url) {// 创建下载请求DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);request.setTitle("Downloading...");request.setDescription("Downloading file...");request.setDestinationInExternalFilesDir(this, "Download", "example.apk");// 将下载请求加入下载队列downloadManager.enqueue(request);}@Nullable@Overridepublic IBinder onBind(Intent intent) {return null; // 如果不需要绑定,则返回null}
}

2、在AndroidManifest.xml中声明Service

在应用的AndroidManifest.xml文件中声明Service。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.myapp"><application ... ><service android:name=".DownloadService" /></application>
</manifest>

3、启动下载Service

在应用的其他组件中,如Activity中,使用startService方法启动下载Service。

Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setAction(DownloadService.ACTION_DOWNLOAD);
downloadIntent.putExtra(DownloadService.EXTRA_URL, "http://example.com/file.apk");
startService(downloadIntent);

4、接收下载进度

为了接收下载进度,可以利用广播接收器。

在Service中发送进度更新广播:

private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);// 检查下载是否成功boolean success = queryDownloadStatus(downloadId);if (success) {// 下载成功,可以在这里安装文件或执行其他操作}}}
};private boolean queryDownloadStatus(long downloadId) {DownloadManager.Query query = new DownloadManager.Query();Cursor cursor = downloadManager.query(query);boolean success = false;if (cursor.moveToFirst()) {int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));success = DownloadManager.STATUS_SUCCESSFUL == status;}cursor.close();return success;
}@Override
public void onDestroy() {// 注销广播接收器unregisterReceiver(downloadReceiver);super.onDestroy();
}// 在startDownload方法中注册广播接收器
Intent filter = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
filter.setPackage(getPackageName());
registerReceiver(downloadReceiver, new IntentFilter(filter));

AndroidManifest.xml中为下载完成的广播添加一个动态权限:

<manifest ... ><uses-permission android:name="com.android.providers.downloads.permission.DOWNLOAD_COMPLETED"/>...
</manifest>

请注意,从Android 8.0(API级别26)开始,对于通过DownloadManager下载的文件,系统会自动进行验证和扫描。确保你的应用遵守了相关的权限和行为准则。

通过这种方式,你可以利用Service实现文件下载任务,并在下载完成后接收通知。这种方法可以确保下载任务在后台独立运行,即使用户切换到其他应用或锁屏也不会中断。


六、结语

Service作为Android平台的基石之一,其强大的后台处理能力和灵活的进程间通信机制,为开发者提供了广阔的应用空间。然而,Service的稳定性和效率仍然是开发者面临的挑战。在未来的技术探索中,我们将进一步讨论Service的最佳实践,包括如何优化Service的性能,以及如何通过Service实现更高效的系统级操作。敬请期待我们的下一篇深度解析文章,带你进入Service的高效能开发世界。


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

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

相关文章

iptables实现docker容器动态端口映射实操

背景 之前在《Docker 动态修改容器端口映射的方法》一文中&#xff0c;说明了如何使用修改配置和加防火墙规则实现动态端口映射。但是没有具体分享加防火墙实现动态端口映射的实际案例。今天就分享一下实际操作案例&#xff0c;供大家参考。 分析 动态端口映射的用途 容器端口…

(2024)Visual Studio的介绍、安装与使用

Visual Studio介绍 1.Visual Studio是什么&#xff1f; Visual Studio是微软公司推出的一款开发工具包系列产品&#xff0c;它是一个基本完整的开发工具集&#xff0c;为软件开发者提供了整个软件生命周期中所需的大部分工具。 2.Visual Studio的定义 Visual Studio是美国微软公…

初级银行从业资格证知识点(一)

从支持角度来看&#xff0c;GDP由 消费、投资和净出口三大部分构成。 宏观经济发展的 总体目标&#xff1a; 经济增长、充分就业、物价稳定、国际收支平衡。 国际货币基金组织将金融危机分为&#xff1a; 货币危机、银行危机、外债危机、系统性金融危机。随着经济全球化及金融创…

网盘_游戏_博客自动化部署(Nginx多项目部署)

目录 一.前提介绍 二.环境介绍 三.自述&#xff08;脚本&#xff09; 四.关于Nginx多项目部署 一.前提介绍 在我之前的博客里详细介绍了上述项目的部署&#xff0c;那么如何使用简单脚本自动部署和使用Nginx多项目部署是本文来介绍的基础篇章。 二.环境介绍 CentOS Linux…

fawawf

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

【Linux】文件目录及路径表示

1. Linux目录结构 在 Linux 系统中&#xff0c;有几个目录是比较重要的&#xff0c;平时需要注意不要误删除或者随意更改内部文件。 /etc&#xff1a; 这个是系统中的配置文件&#xff0c;如果更改了该目录下的某个文件可能会导致系统不能启动。 /bin, /sbin, /usr/bin, /usr…

java泛型介绍

Java 泛型是 JDK 5 引入的一个特性&#xff0c;它允许我们在定义类、接口和方法时使用类型参数&#xff0c;从而使代码更加灵活和类型安全。泛型的主要目的是在编译期提供类型参数&#xff0c;让程序员能够在编译期间就捕获类型错误&#xff0c;而不是在运行时才发现。这样做提…

小程序AI智能名片S2B2C商城系统:解锁内容深耕新境界,助力品牌企业高效定制内容策略

在数字化时代&#xff0c;内容营销已成为品牌企业获取市场份额、增强用户黏性的关键武器。然而&#xff0c;面对海量的互联网信息和复杂多样的社交媒体平台&#xff0c;如何有效地深耕内容&#xff0c;成为众多品牌企业面临的难题。 传统的内容分类与识别方式&#xff0c;往往依…

【数据分析面试】28. 20个Python问答题 (入门级考察:基础操作、数据处理与分析统计)

今天的20个问题考察了 Python 的基础能力&#xff0c;包括数据结构、基本操作、数据处理、数据分析和统计等方面。无论是从事数据分析、机器学习还是其他数据相关工作&#xff0c;这些都是必不可少的基础技能。 数据结构与基础操作&#xff1a; 什么是 Pandas 库&#xff1f;它…

中兴5G随身wifi怎么样?中兴5G随身wifiVS格行5G随身wifi对比测评!公认最好的随身WiFi的格行随身WiFi真实测评!随身WiFi哪个品牌好?

随着各大品牌5G随身wifi的横空出世&#xff0c;其中中兴和格行5G随身wifi的呼声越来越高&#xff0c;那么性能上谁更胜一筹&#xff1f;套餐费用谁更亲民&#xff1f;售后保障谁更到位&#xff1f;今天就来一个全方位测评对比&#xff01; 一&#xff0c;首先是设备的整体外观&…

uniapp:小白1分钟学会使用webSocket(可无脑复制)

uni.connectSocket() uni.$emit页面通信 项目中使用uni.connectSocket()创建webSocket的总结&#xff0c;代码可无脑复制&#xff0c;直接使用。 1、main.js 引入vuex import store from ./store; Vue.prototype.$store store;vuex中封装webSocket 2、vuex的&#xff1a;index…

python队列

1.三种实现 列表&#xff1a;list队列&#xff1a;Queue双端队列&#xff1a;deque 性能&#xff1a;从上往下依次变好&#xff0c;其中deque比Queue快10倍以上 1.1 list模拟队列 length 10 q []# 入队 for i in range(length):q.append(i) print(q) print(len(q))# 出队…

linux autogroup

一&#xff1a;概述 对于linux autogroup的作用&#xff0c;很多同学可能是听说过&#xff0c;但&#xff0c;并未验证过。 考虑下面场景&#xff0c;开两个terminal&#xff0c;T1和T2&#xff0c;在T1中运行进程P1&#xff0c;P1开启9个线程编译代码&#xff0c;在T2中运行…

yield函数怎么理解?

目录 白话系列&#xff1a; 例子&#x1f330;&#xff1a; 什么叫暂停 yield和next搭配使用 例子&#x1f330;&#xff1a; 白话系列&#xff1a; 可以暂停&#xff0c;可以生成&#xff0c;next一个&#xff0c;yield一个 例子&#x1f330;&#xff1a; def generat…

CUDA线程管理

核函数在主机端启动时&#xff0c;执行会转移到设备上&#xff0c;并且将控制权转移回主机。当核函数在GPU上运行时&#xff0c;主机可以运行其他函数。因此&#xff0c;主机与核函数是异步的。 此时&#xff0c;设备端也就是GPU上会产生大量的线程&#xff0c;并且每个线程都…

(七)小案例银行家应用程序-申请贷款-some方法和every方法

some方法 ● 我们先回顾一下includes方法 console.log(movements.includes(-130));只要数组中存在-130这个值&#xff0c;就会返回true&#xff0c;否则就会返回flase ● 而some方法只要达成某一个条件就会返回true&#xff0c;否则就返回flase const someMethod movement…

Unity常用射线检测接口用法及优缺点

在Unity中&#xff0c;射线检测是一种非常常见的技术&#xff0c;用于检测物体之间的碰撞、获取物体之间的距离等。今天我们来说一说射线检测的方法以及它们的优缺点&#xff1a; Physics.Raycast&#xff1a; using UnityEngine;public class RaycastExample : MonoBehaviou…

stm32开发之threadx之modulex模块文件的生成脚本项目

前言 为了保证在window上运行&#xff0c;且体积小的问题&#xff0c;所以采用c语言编写生成脚本,将相关路径由json文件进行配置,使用了一个cjson库进行解析项目构建使用的是cmake 项目代码 CMakeLists文件 cmake_minimum_required(VERSION 3.27) project(txm_bat_script C…

Day13-Java进阶-IO字节流及其练习题

1. IO流介绍 2. IO 流体系结构 字节流读取纯文本文件会出现乱码问题 2.1 FileOutputStream 字节输出流 package com.itheima.stream.output;import java.io.FileOutputStream; import java.io.IOException;public class FileOutputStreamDemo3 {/*IO流的异常处理方式: jdk7版本…

socket编程——tcp

在我这篇博客&#xff1a;网络——socket编程中介绍了关于socket编程的一些必要的知识&#xff0c;以及介绍了使用套接字在udp协议下如何通信&#xff0c;这篇博客中&#xff0c;我将会介绍如何使用套接字以及tcp协议进行网络通信。 1. 前置准备 在进行编写代码之前&#xff…