四大组件 - ContentProvider

参考:Android 这 13 道 ContentProvider 面试题,你都会了吗?

参考:《Android 开发艺术探索》 第 9.5 节(ContentProvider 的工作过程)

参考:内容提供者程序

参考:<provider>>

1. ContentProvider 概述

ContentProvider 是一种内容共享型组件,实现了不同 App 进程之间的数据共享。

Messenger 一样,基于 ContentProvider 的进程间通信的底层实现同样也是 Binder

ContentProvider 所在的进程启动时,ContentProvider 组件会同时启动并被发布到 AMS 进程中。

1.1 ContentProvider 组件的优势

相比于直接访问数据库,ContentProvider 具有如下优势:

  1. ContentProvider 对外提供统一的 CRUD 接口,使得外界可以按照统一的方式访问不同来源提供的数据,而不用关心这些数据是怎么来的。(数据可以来源于数据库、xml 文件、网络请求等)

  2. ContentProvider 提供一种可跨进程的数据共享方式。

  3. ContentProvider 提供了数据更新时的通知机制。即:当 ContentProvider 所在进程中的数据更新时,访问这些数据的其他进程可以通过 ContentProvider 收到相关的通知。

1.2 ContentProvider & ContentResolver

ContentProvider 组件主要是用于进程间的数据共享的。其中:

  1. 提供数据的 App 进程中通过 ContentProvider 向其他 App 进程提供访问数据的 API 方法;

  2. 访问数据的 App 进程中通过 ContentResolver 访问提供数据的 App 进程中的 ContentProvider

1.3 ContentProvider 对外提供的数据形式 & 内部的数据存储方式

1.3.1 ContentProvider 以表格的形式对外提供数据

ContentProvider 主要以表格的形式来组织数据,并且可以包含多个表,这点和数据库很类似。

ContentProvider 可以对外提供文件数据,通过将文件的句柄保存在表格中提供给外界。从而让外界可以根据查询到的文件句柄来访问 ContentProvider 所提供的文件。

Android 系统所提供的 MediaStore 功能就是包含文件类型数据的 ContentProvider,详细实现可以参考 MediaStore

1.3.2 ContentProvider 对内部的数据存储方式没有任何要求

虽然 ContentProvider 对外提供的数据看起来像是来自数据库中的数据,但是 ContentProvider 对内部的数据存储方式没有任何要求:我们既可以使用 SQLite 数据库;也可以使用普通的文件;甚至可以采用内存中的一个对象来进行数据的存储。

也就是说,ContentProvider 对外提供的数据,可以是数据库数据、文件数据、内存数据等。

2. ContentProvider 的使用方式

2.1 自定义 ContentProvider 的子类

/* ContentProvider.java */
/*
    onCreate 代表 ContentProvider 的创建,
    一般来说我们需要在 onCreate 中做一些初始化工作,如获取数据库实例。
    注意:
        1. onCreate 方法在主线程中执行,不允许执行耗时操作,否则会导致 App 进程的启动时间延长。
        2. ContentProvider 的 onCreate 方法的调用时机先于 Application 的 onCreate 方法。
*/
public abstract boolean onCreate();

// 返回一个 Uri 请求所对应的 MIME 类型(媒体类型)。比如:图片("image/png")、视频("video/mp4")等。
// 如果 ContentProvider 提供的数据不需要配置 MIME 类型,那么可以返回 null 或者 "*/*" 
public abstract String getType(Uri uri);

public abstract Uri insert(Uri uri, ContentValues values);

public abstract int delete(Uri uri, String selection, String[] selectionArgs);

public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);

public abstract Cursor query(Uri uri, String[] projection,
            String selection, String[] selectionArgs,
            String sortOrder);

自定义 ContentProvider 子类时需要重写如上的抽象方法,其中 insert/delete/update/query 方法对应于 CRUD 操作,即实现对数据表的增删改查功能。

2.2 注册 ContentProvider 子类 & 相关属性解读

<provider android:authorities="list"
          android:directBootAware="true|false"
          android:enabled="true|false"
          android:exported="true|false"
          android:grantUriPermissions="true|false"
          android:icon="drawable resource"
          android:initOrder="integer"
          android:label="string resource"
          android:multiprocess="true|false"
          android:name="string"
          android:permission="string"
          android:process="string"
          android:readPermission="string"
          android:syncable="true|false"
          android:writePermission="string">

    <grant-uri-permission android:path="string"
                      android:pathPattern="string"
                      android:pathPrefix="string" />

</provider>

其中:

  1. <provider>

    包含于 <application> 标签中,用来注册自定义的 ContentProvider。
    
  2. android:authorities

    是 ContentProvider 的唯一标识,提供给外界用来访问当前注册的 ContentProvider。
    建议命名时加上包名前缀。
    当声明了多个唯一标识时,用分号 ";" 分隔开
    
  3. android:directBootAware

    ContentProvider 是否可感知直接启动(direct-boot);即,它是否可以在用户解锁设备之前运行。
    
  4. android:enabled

    系统是否可以实例化 ContentProvider。true 可以;false 不可以。默认 true。
    
  5. android:permission

    其他 App 需要申请该属性指定的权限后才能读写 ContentProvider 提供的数据
    
  6. android:readPermission

    其他 App 需要申请该属性指定的权限后才能读 ContentProvider 提供的数据
    当 permission 和 readPermission 同时存在时,读权限取决于 readPermission;写权限取决于 permission
    
  7. android:writePermission

    其他 App 需要申请该属性指定的权限后才能写 ContentProvider 提供的数据
    当 permission 和 writePermission 同时存在时,写权限取决于 writePermission;读权限取决于 permission
    
  8. android:exported

    true 表示允许其他 App 在申请了 permission/readPermission/writePermission 指定权限的情况下访问 ContentProvider
    
    false 表示不允许其他 App 访问 ContentProvider。只能在如下情况下访问 ContentProvider:
        1. 同一个 App 进程中;
        2. 其他具有相同 userId 的进程中;
        3. 注册 ContentProvider 时设置了属性 android:grantUriPermissions = true,
            使得其他 App 进程可以获取一次性访问 ContentProvider 的临时权限。
    
  9. android:grantUriPermissions

    true 表示允许其他 App 获取一次性访问此 ContentProvider 所提供的所有数据的临时权限。
        所谓临时权限,就如打开刚安装的 App 时,会弹框向用户询问是否允许某个权限,并为用户提供了几种选项:
                     1. 始终允许;2. 使用期间允许;3. 仅本次使用时允许;3. 禁止。(不同手机可能存在区别)
            而临时权限就相当于选择了 "2. 仅本次使用时允许",即在本次访问 ContentProvider 之后,就不再拥有权限了,
            当下次再访问时,仍然会询问用户是否允许权限。即:临时权限就是允许用户一次性地访问。
    
    false 不允许其他 App  获取一次性访问此 ContentProvider 所提供的所有数据的临时权限。(默认值)
        但是,此情况下,我们还可以通过 <provider> 下的子标签 <grant-uri-permission> 声明
        指定路径下的数据是可以让其他 App 在临时权限下一次性访问的。
    
  10. <grant-uri-permission>

    在 android:grantUriPermissions = false 的情况下,
    可以通过此标签来声明指定路径下的某些或某一数据仍然可以让其他 App 在临时权限下一次性访问的。
    此标签下的三个属性都是用来指定数据的路径的,但一个 <grant-uri-permission> 标签只能指定一个路径,
    也就是说,一个标签中只能使用以下三个属性中的一个:
    1. android:path:某一数据的完整路径
    2. android:pathPrefix:某些数据的公共路径
    3. pathPattern:包含通配符的完整路径
    

2.3 其他进程中通过 ContentResolver 来访问 ContentProvider

/* Context.java */
public abstract ContentResolver getContentResolver();

ContentResolver 提供的用于访问 ContentProvider 的方法,与 ContentProvider 子类重写的方法基本一样。事实上,ContentResolver 内部就是通过基于 binder 机制的跨进程通信来调用 ContentProvider 对应的方法的。

/* ContentResolver.java */
String getType(Uri url)

Uri insert(Uri url, ContentValues values)

int delete(Uri url, String where, String[] selectionArgs)

int update(Uri uri, ContentValues values, String where, String[] selectionArgs)

Cursor query( // query(uri, projection, selection, selectionArgs, sortOrder, null);
        Uri uri, 
        String[] projection, 
        String selection, String[] selectionArgs, 
        String sortOrder)

Cursor query(
        Uri uri, 
        String[] projection, 
        String selection, String[] selectionArgs, 
        String sortOrder, 
        CancellationSignal cancellationSignal) {

    Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
    return query(uri, projection, queryArgs, cancellationSignal);
}

Cursor query(
        Uri uri, 
        String[] projection, 
        Bundle queryArgs, 
        CancellationSignal cancellationSignal)

2.4 ContentProvider 的数据更新通知机制

2.4.1 在其他 App 中通过 ContentResolver 监听数据的更新
/* ContentResolver.java */
/*
    当参数 Uri 标识的 ContentProvider 中的与 Uri 的 path 部分匹配的数据更新时,
    参数 observer 的 onChange 方法就会回调。

    参数 notifyForDescendants 为 false 表示监听与 Uri 的 path 路径完全匹配的数据的更新,
        或者与 path 路径的父路径匹配的数据的更新。
    参数 notifyForDescendants 为 true 表示还会监听与 Uri 的 path 下的子路径匹配的数据的更新。
*/
void registerContentObserver(Uri uri, boolean notifyForDescendants, ContentObserver observer)

void unregisterContentObserver(ContentObserver observer)

ContentProvider 中的数据发生变更时(如调用了 ContentProviderinsert/delete/update),需要在 ContentProvider 中调用 ContentResolvernotifyChange 方法。于是,其他 App 中通过 ContentResolverregisterContentObserver 方法注册的 ContentObserver 才会收到通知(即才会回调 onChange 方法)。

/* ContentObserver.java */
/*
    回调方法 onChange 会在 Handler 线程中执行,
    如果传入 null,不指定 Handler,那么回调方法 onChange 会在 Binder 线程中执行。
*/
public ContentObserver(Handler handler)

/*
    当在 ContentProvider 中调用 ContentResolver.nofityChange(Uri, null) 方法后,
    其他 App 中的注册了的对 Uri 进行监听的 ContentObserver 的 onChange 方法就会回调。

    ContentObserver 提供了如下三个重载的 onChange 方法:
    1. 首先回调的是 onChange(selfChange, uri, userId) 
    2. 在 onChange(selfChange, uri, userId) 中只是调用了 onChange(selfChange, uri) 
    3. 在 onChange(selfChange, uri) 中只是调用了 onChange(selfChange) 
    需要用到哪些参数,就重写带哪些参数的 onChange 方法。
*/

public void onChange(boolean selfChange, Uri uri, int userId)

public void onChange(boolean selfChange, Uri uri)

public void onChange(boolean selfChange)
2.4.2 在 ContentProvider 中通过 ContentResolver 发送数据更新的通知

在调用 ContentProviderupdateinsertdelete 方法后,一般会引起数据源的改变。
此时,可以在 ContentProvider 中调用 ContentResolvernotifyChange 方法向其他 App 进程发出数据更新的通知。

可以通过 ContentProvider 组件中的上下文 Context 对象获取 ContentResolver 实例。

即在 ContentProvider 中调用 getContext().getContentResolver() 方法获取 ContentResolver 实例。

/* ContentResolver.java */
/*
    此方法一般在 ContentProvider 中的 insert、update、delete 方法内操作完数据后调用,
    用于向其他 App 进程发出数据更新的通知。
	
    参数 observer 一般传 null,表示其他 App 进程中只要调用 ContentResolver.registerContentObserver 方法,
    注册了的监听此 Uri 的 ContentObserver 都会收到通知,即都会回调 onChange 方法。
*/
public void notifyChange(Uri uri, ContentObserver observer)

3. ContentProviderUri 结构 & 通过 UriMatcher 管理多个 path

3.1 Uri 结构 & 操作 UriAPI 方法

通常的 Uri 的结构如下所示:

在这里插入图片描述

对于唯一标识 ContentProviderUri,由上图中的三部分组成:

  1. scheme

    ContentProvider 的 Uri 的 scheme 固定为 content。即:ContentProvider 的 Uri 的开头部分固定为 "content://"
    
  2. authority

    ContentProvider 的 Uri 的 authority 在注册 ContentProvider 时通过属性 android:authorities 声明
    
  3. path

    ContentProvider 的 Uri 的 path 部分,用来指定所要访问的数据具体是哪些数据。
    
    path 部分可以指定访问一张表中的所有数据,如访问 user 表的 Uri 为:
        "content://authority/user" 
    
    path 部分也可以指定访问一张表中的一条数据,如访问 user 表中 id 为 1 的那条数据的 Uri 为:
        "content://authority/user/1" 
    
    总之,path 部分是完全自定义的,从 ContentResolver 传过来的 Uri 中将 path 解析出来后,
    与我们在 ContentProvider 中已自定义好的 path 常量进行匹配,即可知道外界需要访问的是什么数据。
    

Uri.java 提供了如下的 API 来对 Uri 进行操作:

/* Uri.java */
/*
    将符合 Uri 结构的字符串转换成 Uri 对象
*/
public static Uri parse(String uriString)

/*
    返回表示一个文件的 Uri 对象
*/
public static Uri fromFile(File file)

/* 
    获取 Uri 中的 scheme 部分
*/
public abstract String getScheme();

/* 
    获取 Uri 中的 authority 部分
*/
public abstract String getAuthority();

/* 
    获取 Uri 中的 path 部分
*/
public abstract String getPath();

3.2 通过 UriMatcher 管理多个 path

UriMatcher 的作用就是对 Uri 中的 path 部分进行标识,用标识码(code)来表示不同的 path

使用 UriMatcher 可以简化对 Uri 的解析,直接将 Uri 传给 UriMatcher 就能获取到对应不同 path 的标识码(code)。

于是,根据自定义的标识码(code)在 ContentProvider 内部查询对应的数据即可。

UriMatcher.java 提供的 API 方法如下:

/* UriMatcher.java */
/*
    传入 UriMatcher.NO_MATCH 即可。(NO_MATCH = -1)
    参数 code 表示当调用 match(Uri) 方法时,如果 Uri 中不存在 authority 和 path 部分,则返回此 code
*/
public UriMatcher(int code)

/*
    将参数 authority 和 path 注册到 UriMatcher 对象中,并用参数 code 标记这条注册记录。

    当调用 match(Uri) 方法时,
        如果传入的 Uri 的 authority 和 path 与已注册的 authority 和 path 匹配,则返回匹配的注册记录的标记 code
        如果传入的 Uri 的 authority 和 path 不与任何一条注册记录匹配,则返回 -1(即返回 NO_MATCH)
*/
public void addURI(String authority, String path, int code)

public int match(Uri uri)

4. ContentProvider 的生命周期方法:onCreate

ContentProvider 只有一个 onCreate() 生命周期方法。

ContentProvider.onCreate 方法会在 ContentProvider 所在的进程启动时调用一次。且调用时机优先于 Application.onCreate 方法。

注意:ContentProvideronCreate 方法在整个进程的生命周期中只会被回调一次。

5. ContentProvider 的工作线程

5.1 onCreate 方法在主线程中执行

ContentProvideronCreate 在主线程中执行,且优先于 ApplicationonCreate 方法执行,所以在 ContentProvideronCreate 方法中不能做耗时操作,否则会使 App 的启动变慢。

5.2 query/update/insert/delete/getTypeBinder 线程中执行

由于 query/update/insert/delete/getType 是在 Binder 线程中执行的,所以调用这些方法并不会阻塞 ContentProvider 所在进程的主线程。

但是,如果其他 App 进程中通过 ContentResolverquery/update/insert/delete/getType 方法访问 ContentProvider 时,是在主线程中执行的,那么其他 App 进程的主线程就可能会阻塞。

因此,其他 App 进程中通过 ContentResolver 访问 ContentProvider 时仍然需要在子线程中进行访问。

也就是说,使用 ContentResolver 发起的 query/update/insert/delete/getType 这些 binder 调用,是会阻塞线程的。

5.3 ContentProvider 的线程安全问题

queryupdateinsertdelete 这些方法是在 Binder 线程池中提供的 Binder 线程中执行的,因此会存在多线程并发访问的情况,所以这些方法内部需要做好线程同步。

ContentProvider 中的数据都保存在一个 SQLiteDatabase 中时,因为 SQLiteDatabase 内部对数据库的操作是有同步处理的,所以可以正确应对多线程的情况。

但是如果通过多个 SQLiteDatabase 对象来操作数据库就无法保证线程同步,因为 SQLiteDatabase 对象之间无法进行线程同步。

如果 ContentProvider 中的数据是保存在内存中的,如保存在同一个 List 容器中,那么这种情况下对 List 容器的遍历、插入、删除操作就需要进行线程同步,否则就会引起多线程并发问题。

6. 其他 API

6.1 ContentValues

6.2 Cursor

7. 示例

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Maya------显示隐藏提取复制刺破面

alth<--->ctrlshifth 补洞后刺破面&#xff0c;防止多边面的产生&#xff01;

数据探索与可视化:可视化分析数据关系-中

目录 一、前言 二、介绍 Ⅰ.一个分类变量和一个连续变量 Ⅱ.两个分类变量的一个连续变量 Ⅲ.两个分类变量和两个连续变量 Ⅳ.一个分类变量和多个连续变量 ①.平行坐标轴 ②.矩阵散点图 三、结语 一、前言 在做数据分析的时候&#xff0c;很少会遇到连续变量和分类变量…

(每日持续更新)jdk api之NotActiveException基础、应用、实战

博主18年的互联网软件开发经验&#xff0c;从一名程序员小白逐步成为了一名架构师&#xff0c;我想通过平台将经验分享给大家&#xff0c;因此博主每天会在各个大牛网站点赞量超高的博客等寻找该技术栈的资料结合自己的经验&#xff0c;晚上进行用心精简、整理、总结、定稿&…

重写Sylar基于协程的服务器(2、配置模块的设计)

重写Sylar基于协程的服务器&#xff08;2、配置模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、日志模…

idea搭建spring5.3.x源码环境

1.写在前面的话 碰到了不少想阅读或者学习spring源码的同学&#xff0c;但是第一步搭建这个源码阅读环境就能难倒了一大批人。下面我就以spring5.3.x这个源码分支&#xff0c;来具体演示一下搭建过程。 2. 下载源码 下载源码这一步&#xff0c;说实话&#xff0c;由于某些原…

Django模板(一)

一、基本规则 作为一个Web框架,Django需要一种方便的方式来动态生成HTML。最常用的方法依赖于模板。模板包含所需HTML输出的静态部分以及描述如何插入动态内容的特殊语法 1.1、django默认模板 在settings中配置: TEMPLATES = [{BACKEND: django.template.backends.django.…

基于 Echarts 的 Python 图表库:Pyecahrts交互式的日历图和3D柱状图

文章目录 概述一、日历图和柱状图介绍1. 日历图基本概述2. 日历图使用场景3. 柱状图基本概述4. 柱状图使用场景 二、代码实例1. Pyecharts绘制日历图2. Pyecharts绘制2D柱状图3. Pyecharts绘制3D柱状图 总结 概述 本文将引领读者深入了解数据可视化领域中的两个强大工具&#…

数据解构+算法(第07篇):动态编程!黄袍加身!

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 学习必须往深处挖&…

力扣hot100 对称二叉树 递归

Problem: 101. 对称二叉树 文章目录 思路Code 思路 &#x1f468;‍&#x1f3eb; 参考 Code 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* …

【10秒开服】雾锁王国服务器全自动部署教程

你是火焰之子&#xff0c;一个濒死种族最后的希望火苗。苏醒吧&#xff0c;克服腐化一切的迷雾所裹挟的恐怖&#xff0c;重新夺回你的王国所失落的瑰丽。置身于广袤世界&#xff0c;战胜难以想象的强大Boss&#xff0c;修造宏伟厅堂&#xff0c;在这款至多16名玩家的合作类生存…

sectigo ip ssl证书有哪些

Sectigo是移交成立时间较久的CA认证机构&#xff0c;几十年来在全球颁发了各种各样的数字证书&#xff0c;例如&#xff0c;单域名SSL证书、多域名SSL证书、通配符SSL证书等域名SSL证书。Sectigo旗下也有一些不常见的数字证书&#xff0c;例如&#xff0c;代码签名证书、IP证书…

HTTP中传输协议的数据格式

HTTP 概述&#xff1a;超文本传输协议(Hyper Text Transfer Protocol) 传输协议&#xff1a;定义了客户端和服务器通信时&#xff0c;发送数据的格式 客户端和服务器端交互&#xff1a;客户端向服务器端发送请求&#xff0c;服务器端向客户端响应请求 HTTP特点&#xff1a;…

【RT-DETR有效改进】利用YOLO-MS的MSBlock模块改进ResNet中的Bottleneck(RT-DETR深度改进)

👑欢迎大家订阅本专栏,一起学习RT-DETR👑 一、本文介绍 本文给大家带来的改进机制是利用YOLO-MS提出的一种针对于实时目标检测的MSBlock模块(其其实不能算是Conv但是其应该是一整个模块),我们将其用于替换我们ResNet中Basic组合出一种新的结构,来替换我们网络中的…

【Bugs】Jmeter报错:NoSuchMethodError: org.apache.jmeter.samplers.

报错情况 Jmeter版本&#xff1a;5.4.3 报错场景&#xff1a;在线程组中添加了jpgc - PerfMon Metrics Collector性能监控组件后出现报错。 Jmeter中无法运行测试&#xff0c;cmd命令行中出现以下报错。 cmd报错详细内容&#xff1a; Uncaught Exception java.lang.NoSuchMe…

Mac 上终端配置

Mac 上终端配置 初始化了一下自己的 mac 笔记本&#xff0c;所以重新记一下终端配置&#xff0c;最终的完成版的需求是这样的&#xff1a; 存在的指令需要显示绿色进行提示&#xff1a; 不存在的指令则是显示红色进行提示&#xff1a; 同时具备对指令进行提示 一个看起来…

spark window源码探索

核心类&#xff1a; 1. WindowExec 物理执行逻辑入口&#xff0c;主要doExecute()和父类WindowExecBase 2. WindowFunctionFrame 窗框执行抽象&#xff0c;其子类对应sql语句的不同窗框 其中又抽象出BoundOrdering类, 用于判断一行是否在界限内(Bound), 分为RowBoundOrdering…

2024美赛MCM 问题 C 网球运动的动量(Momentum in Tennis)

2024 MCM Problem C: Momentum in Tennis In the 2023 Wimbledon Gentlemen’s final, 20-year-old Spanish rising star Carlos Alcaraz defeated 36-year-old Novak Djokovic. The loss was Djokovic’s first at Wimbledon since 2013 and ended a remarkable run for one o…

直播团队职责

一、内容策划 直播团队的内容策划人员是整个直播活动的核心&#xff0c;他们需要负责策划直播的主题、内容、形式以及时间安排等。同时&#xff0c;他们还需要负责邀请嘉宾、安排活动等&#xff0c;确保直播内容丰富、有趣、有价值。 二、主播管理 主播是直播活动的关键人物…

提升CKA考试胜算:一文带你全面了解RBAC权限控制!

RBAC概述 RBAC引入了四个新的顶级资源对象。Role、ClusterRole、RoleBinding、 ClusterRoleBinding。同其他 API 资源对象一样&#xff0c;用户可以使用 kubectl 或者 API 调用等 方式操作这些资源对象。kubernetes集群相关所有的交互都通过apiserver来完成&#xff0c;对于这…

计算机网络第4章(网络层)

4.1、网络层概述 简介 网络层的主要任务是实现网络互连&#xff0c;进而实现数据包在各网络之间的传输 这些异构型网络N1~N7如果只是需要各自内部通信&#xff0c;他们只要实现各自的物理层和数据链路层即可 但是如果要将这些异构型网络互连起来&#xff0c;形成一个更大的互…