Android手写占位式插件化框架之Activity通信、Service通信和BroadcastReceiver通信

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。
👉点击跳转到教程

前言:
1、什么是插件化?
能运行的宿主APP去加载没有下载的APK文件,并使用APK文件里面的功能,这就叫插件化。
2、插件化的使用场景?
很多大厂APP内会有很多功能模块,但是包体积却很小,那么就用到了插件化技术,点击某个模块后,从服务器获取对应的APK文件,并使用其内部的功能。

实现后的效果图如下:

在这里插入图片描述

接下来手写实现占位式插件化框架之Activity之间的通信
在这里插入图片描述
根据上图首先定义一个项目叫PluginProject,之后再新建一个Android Library库名为:stander,然后再定义一个插件包名为:plugin_package

项目目录如下:

在这里插入图片描述
一、首先在stander库中,定义一个接口名为ActivityInterface,ServiceInterface,ReceiverInterface三个接口
1.1、ActivityInterface接口

/**
 * @Author: ly
 * @Date: 2023/7/14
 * @Description: 定义的Activity标准接口,需要什么方法可以再加
 */
public interface ActivityInterface {
    /**
     * 把宿主(app)的环境给插件
     *
     * @param appActivity 宿主的Activity
     */
    void insertAppContext(Activity appActivity);

    void onCreate(Bundle savedInstanceState);

    void onStart();

    void onResume();

    void onPause();

    void onStop();

    void onDestroy();
}

1.2 ServiceInteface接口

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 宿主与插件间进行Service通信,标准接口
 */
public interface ServiceInterface {
    /**
     * 把宿主(app)的环境给插件
     *
     * @param service 宿主的Service
     */
    void insertAppContext(Service service);

    void onCreate();

    int onStartCommand(Intent intent, int flags, int startId);

    void onDestroy();
}

1.3、ReceiverInterface接口

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 宿主与插件间进行广播通信标准接口
 */
public interface ReceiverInterface {
    void onReceive(Context context, Intent intent);
}

二、在宿主APP中,定义插件管理类PluginManager

/**
 * @Author: ly
 * @Date: 2023/7/14
 * @Description: 插件管理类,获取插件中的资源Resources和类加载器DexClassLoader
 */
public class PluginManager {
    private static final String TAG = PluginManager.class.getSimpleName();
    private static PluginManager pluginManager;
    private Context context;
    //Activity class
    private DexClassLoader dexClassLoader;
    private Resources resources;

    private PluginManager(Context context) {
        this.context = context;
    }

    public static PluginManager getInstance(Context context) {
        if (pluginManager == null) {
            synchronized (PluginManager.class) {
                if (pluginManager == null) {
                    pluginManager = new PluginManager(context);
                }
                return pluginManager;
            }
        }
        return pluginManager;
    }

    /**
     * 加载插件(2.1 Activity class, 2.2 layout)
     */
    public void loadPlugin() {
        try {
            //getExternalFilesDir:表示应用程序的私有目录
            File privateDir = context.getExternalFilesDir(null);
            //路径: /storage/emulated/0/Android/data/com.example.pluginproject/files
            Log.i(TAG, "privateDir: " + privateDir.getAbsolutePath());
            File file = new File(privateDir.getAbsolutePath() + File.separator + "p.apk");
            if (!file.exists()) {
                Log.d(TAG, "插件包,不存在");
                return;
            }
            String pluginPath = file.getAbsolutePath();
            //下面是加载插件里面的class
            //dexClassLoader 需要一个缓存目录 /data/data/当前应用的包名/pDir
            File fileDir = context.getDir("pDir", Context.MODE_PRIVATE);
            //fileDir.getAbsolutePath(): /data/user/0/com.example.pluginproject/app_pDir
            Log.d(TAG, "fileDir: " + fileDir.getAbsolutePath());
            //pluginPath:插件文件的路径,表示插件APK文件的位置。
            //fileDir.getAbsolutePath():表示应用程序的私有目录路径,作为DexClassLoader的第二个参数传递,用于指定Dex文件的输出目录。
            //null:表示没有指定库(Native Library)的路径,如果插件中有依赖的库文件,可以传入库目录的路径。
            //context.getClassLoader():获取应用程序的类加载器作为DexClassLoader的父类加载器。
            dexClassLoader = new DexClassLoader(pluginPath, fileDir.getAbsolutePath(), null, context.getClassLoader());
            //下面是加载插件里面的layout文件
            //加载资源
            AssetManager assetManager = AssetManager.class.newInstance();
            //我们执行此方法,为了把插件包的路径添加进去
            // public int addAssetPath(String path)
            Method method = assetManager.getClass().getMethod("addAssetPath", String.class);//类类型Class
            method.invoke(assetManager, pluginPath);//插件包的路径,pluginPath
            Resources r = context.getResources();//宿主的资源配置信息
            //特殊的resource,加载插件里面的资源的resource
            this.resources = new Resources(assetManager, r.getDisplayMetrics(), r.getConfiguration());//参数二和参数三,配置信息
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public ClassLoader getClassLoader() {
        return dexClassLoader;
    }

    public Resources getResources() {
        return resources;
    }
}

2.1然后在MainActivity定义两个按钮,分别为加载插件,和启动插件里面的Activity

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    /**
     * 加载插件
     *
     * @param view
     */
    public void loadPlugin(View view) {
        PluginManager.getInstance(this).loadPlugin();
    }

    /**
     * 启动插件里面的Activity
     *
     * @param view
     */
    public void startPluginActivity(View view) {
        File privateDir = getExternalFilesDir(null);
        File file = new File(privateDir.getAbsolutePath() + File.separator + "p.apk");
        String path = file.getAbsolutePath();
        File file1 = new File(path);
        if (!file1.exists() || file1.isFile()) {
            Log.i("TAG", "插件包路径无效");
        }
        Log.i("TAG", "path: " + path);
        //获取插件包里面的Activity
        PackageManager packageManager = getPackageManager();
        PackageInfo packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
        ActivityInfo activityInfo = packageInfo.activities[1];
        //占位 代理Activity
        Intent intent = new Intent(this, ProxyActivity.class);
//        intent.putExtra("className", "com.example.plugin_package.PluginActivity");
        intent.putExtra("className", activityInfo.name);
        startActivity(intent);
    }
}

2.2 写代理类ProxyActivity,用代理类的上下文环境,实现插件包页面正常加载

/**
 * @Author: ly
 * @Date: 2023/7/14
 * @Description: 代理的Activity,代理/占位 插件里面的Activity
 */
public class ProxyActivity extends Activity {
    private static final String TAG = "ProxyActivity";

    @Override
    public Resources getResources() {
        return PluginManager.getInstance(this).getResources();
    }

    @Override
    public ClassLoader getClassLoader() {
        return PluginManager.getInstance(this).getClassLoader();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //真正的加载,插件里面的Activity
        String className = getIntent().getStringExtra("className");
        Log.i(TAG, "className: " + className);
        try {
            Class<?> pluginActivityClass = getClassLoader().loadClass(className);
            //实例化插件包里面的Activity
            Constructor<?> constructor = pluginActivityClass.getConstructor(new Class[]{});
            Object pluginActivity = constructor.newInstance(new Object[]{});
            ActivityInterface activityInterface = (ActivityInterface) pluginActivity;
            //注入
            activityInterface.insertAppContext(this);
            Bundle bundle = new Bundle();
            bundle.putString("appName", "我是宿主传递过来的信息");
            //执行插件里面的onCreate()方法
            activityInterface.onCreate(bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent proxyIntent = new Intent(this, ProxyActivity.class);
        proxyIntent.putExtra("className", className);//包名TestActivity
        //要给TestActivity进栈
        super.startActivity(proxyIntent);
    }

    @Override
    public ComponentName startService(Intent service) {
        String className = service.getStringExtra("className");
        Intent intent = new Intent(this, ProxyService.class);
        intent.putExtra("className", className);//ProxyService全类名
        return super.startService(intent);
    }

    @Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter intentFilter) {
        //MyReceiver全类名
        String pluginReceiverName = receiver.getClass().getName();
        //在宿主app注册广播
        return super.registerReceiver(new ProxyReceiver(pluginReceiverName), intentFilter);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        super.sendBroadcast(intent);//发送广播到ProxyReceiver
    }
}

2.3、ProxyService类

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 代理Service类,代理/占位插件中的Service
 */
public class ProxyService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String className = intent.getStringExtra("className");
        //com.example.plugin_package.TestService
        try {
            Class<?> testServiceClass = PluginManager.getInstance(this).getClassLoader().loadClass(className);
            Object testService = testServiceClass.newInstance();
            ServiceInterface serviceInterface = (ServiceInterface) testService;
            serviceInterface.insertAppContext(this);
            serviceInterface.onStartCommand(intent, flags, startId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

ProxyService需要在AndroidManifest.xml中注册

 <service android:name=".ProxyService" />

2.4、ProxyReceiver类

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 能接收的广播接收者, 代理/占位,插件里面的BroadcastReceiver
 */
public class ProxyReceiver extends BroadcastReceiver {
    /**
     * 插件里面的MyReceiver全类名
     */
    private String pluginReceiverName;

    public ProxyReceiver(String pluginReceiverName) {
        this.pluginReceiverName = pluginReceiverName;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        //加载插件里面的MyReceiver
        try {
            Class myReceiverClass = PluginManager.getInstance(context).getClassLoader().loadClass(pluginReceiverName);
            //实例化Class
            Object myReceiver = myReceiverClass.newInstance();
            ReceiverInterface receiverInterface = (ReceiverInterface) myReceiver;
            receiverInterface.onReceive(context, intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三、插件包plugin_package中,首先实现BaseActivity类

/**
 * @Author: ly
 * @Date: 2023/7/14
 * @Description: 插件包中Activity基础类, 拿到宿主的上下文环境
 */
public class BaseActivity extends Activity implements ActivityInterface {
    private static final String TAG = "BaseActivity";
    /**
     * 宿主的环境
     */
    public Activity appActivity;

    @Override
    public void insertAppContext(Activity appActivity) {
        this.appActivity = appActivity;
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        String appName = savedInstanceState.getString("appName");
        Log.i(TAG, "appName: " + appName);
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    public void setContentView(int resId) {
        appActivity.setContentView(resId);
    }

    public View findViewById(int id) {
        return appActivity.findViewById(id);
    }

    /**
     * 启动插件包内的第二个Activity:TestActivity
     *
     * @param intent 意图数据
     */
    public void startActivity(Intent intent) {
        Intent newIntent = new Intent();
        newIntent.putExtra("className", intent.getComponent().getClassName());
        appActivity.startActivity(newIntent);
    }

    public ComponentName startService(Intent serviceIntent) {
        Intent newIntent = new Intent();
        //serviceIntent.getComponent().getClassName() 这里拿到的是TestService的全类名
        newIntent.putExtra("className", serviceIntent.getComponent().getClassName());
        return appActivity.startService(newIntent);
    }

    /**
     * 注册广播
     *
     * @param receiver
     * @param intentFilter
     * @return
     */
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter intentFilter) {
        return appActivity.registerReceiver(receiver, intentFilter);
    }

    /**
     * 发送广播
     *
     * @param intent
     */
    public void sendBroadcast(Intent intent) {
        appActivity.sendBroadcast(intent);
    }
}

3.2 插件包中首页PluginActivity,代码如下

/**
 * 首先加载该页面PluginActivity
 */
public class PluginActivity extends BaseActivity {
    private static final String TAG = "PluginActivity";
    private static final String ACTION = "com.example.plugin_package.ACTION";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String appName = savedInstanceState.getString("appName");
        Log.i(TAG, "appName: " + appName);
        setContentView(R.layout.activity_plugin);
        Toast.makeText(appActivity, "我是插件", Toast.LENGTH_SHORT).show();
        //点击按钮跳转到TestActivity
        findViewById(R.id.btn_start_activity).setOnClickListener(v -> {
            startActivity(new Intent(appActivity, TestActivity.class));
        });
        //点击按钮跳转到TestService
        findViewById(R.id.btn_start_service).setOnClickListener(v -> {
            startService(new Intent(appActivity, TestService.class));
        });
        //插件内部注册插件的广播接收者
        findViewById(R.id.btn_register_receiver).setOnClickListener(v -> {
            IntentFilter filter = new IntentFilter();
            filter.addAction(ACTION);
            registerReceiver(new MyReceiver(), filter);
        });
        //插件内部发送插件的广播接收者
        findViewById(R.id.btn_send_receiver).setOnClickListener(v -> {
            Intent intent = new Intent();
            intent.setAction(ACTION);
            sendBroadcast(intent);
        });
    }
}

3.3 点击PluginActivity中的按钮,可以跳转到TestActivity,代码如下:

public class TestActivity extends BaseActivity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
    }
}

3.4、BaseService类

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 基础Service继承标准库中ServiceInterface接口
 */
public class BaseService extends Service implements ServiceInterface {
    private Service appService;

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

    @Override
    public void insertAppContext(Service appService) {
        this.appService = appService;
    }

    @Override
    public void onCreate() {

    }

    @SuppressLint("WrongConstant")
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return 0;
    }

    @Override
    public void onDestroy() {

    }
}

3.5、TestService类

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 插件中的Service
 */
public class TestService extends BaseService {
    private static final String TAG = "TestService";

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //开启子线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        Log.i(TAG, "插件里面的服务正在执行中!");
                    }
                }
            }
        }).start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

3.6、插件中的广播接收者MyReceiver

/**
 * @Author: ly
 * @Date: 2023/7/15
 * @Description: 插件中的广播接收者
 */
public class MyReceiver extends BroadcastReceiver implements ReceiverInterface {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "我是插件里面的广播接收者,我收到了广播!", Toast.LENGTH_SHORT).show();
    }
}

一、这个是宿主APP启动插件Activity流程:

在这里插入图片描述
二、这个是插件中启动Activity的流程如下图:

在这里插入图片描述
三、下面是插件中启动Service的流程图如下:

在这里插入图片描述
四、插件中启动BroadcastReceiver

在这里插入图片描述
编写代码后,将plugin_package包手动放到宿主app的私有目录下,便可以正常运行,在公司项目中会将插件包放到服务器用户使用某个功能模块的时候,会下载到本地。

在这里插入图片描述

具体问题思考
1、为什么在插件中不能使用this?
因为插件是没有在手机上安装的,是无法拥有组件环境的。
2、为什么要有代理的Activity?
由于插件中的Activity并不是一个能够运行的组件,所以需要代理的Activity去代替插件中的Activity(例如Activity进出栈)
3、这种插件化,在写插件开发的时候,有什么要注意的事项?
所有关于操作组件环境的地方,都必须使用宿主的环境。

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

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

相关文章

SAP从放弃到入门系列之生产订单拆分

文章目录 一、概述二、订单拆分功能前世今生三、订单拆分不同版本的差异3.1 版本 603 以下的订单拆分3.2 自604 起版本的订单拆分 四、订单拆分实例4.1 数据准备4.2 拆分操作-到仓库的分解&#xff08;SPLT_OS&#xff09;4.2 拆分操作-到其他物料的分解&#xff08;SPLT_DP&am…

【STM32MP135】修改10.1寸屏1280x800分辨率配置,解决fb_size过小导致运行崩溃

文件路径&#xff1a;u-boot-stm32mp-v2021.10-stm32mp1-r1/configs/stm32mp13_defconfig

基于深度学习的高精度工人安全帽检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度工人安全帽检测识别系统可用于日常生活中或野外来检测与定位工人安全帽目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的工人安全帽目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用…

使用Dreambooth LoRA微调SDXL 0.9

本文将介绍如何通过LoRA对Stable Diffusion XL 0.9进行Dreambooth微调。DreamBooth是一种仅使用几张图像(大约3-5张)来个性化文本到图像模型的方法。 本教程基于通过LoRA进行Unet微调&#xff0c;而不是进行全部的训练。LoRA是在LoRA: Low-Rank Adaptation of Large Language …

2023届网络安全岗秋招面试题及面试经验分享

Hello&#xff0c;各位小伙伴&#xff0c;我作为一名网络安全工程师曾经在秋招中斩获&#x1f51f;个offer&#x1f33c;&#xff0c;并在国内知名互联网公司任职过的职场老油条&#xff0c;希望可以将我的面试的网络安全大厂面试题和好运分享给大家~ 转眼2023年秋招已经到了金…

Python应用实例(一)外星人入侵(八)

外星人入侵&#xff08;八&#xff09; 1.添加Play按钮1.1 创建Button类1.2 在屏幕上绘制按钮1.3 开始游戏1.4 重置游戏1.5 将play按钮切换到非活动状态1.6 隐藏鼠标光标 我们添加一个Play按钮&#xff0c;用于根据需要启动游戏以及在游戏结束后重启游戏&#xff0c;还会修改这…

剖析C语言字符串函数(超全)

目录 前言&#xff1a; 一、strlen函数 功能&#xff1a; 参数和返回值&#xff1a; 注意事项&#xff1a; 返回值是无符号的易错点&#xff1a; strlen函数的模拟实现 1、计数器算法 2、递归算法 3、指针减去指针 二、strcpy函数 功能&#xff1a; 参数和返回值 …

124、仿真-基于51单片机智能电表系统设计(Proteus仿真+程序+原理图+配套资料等)

方案选择 单片机的选择 方案一&#xff1a;STM32系列单片机控制&#xff0c;该型号单片机为LQFP44封装&#xff0c;内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ&#xff0c;在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…

盖子的c++小课堂——第十八讲:栈

目录 前言 栈的定义 栈&#xff0c;是什么&#xff1f; 例1-弹夹 问题 例2-停车场 问题 栈的概念 空栈 进栈、出栈 特点 例题 车厢调度 如何操作 数组模拟栈 入栈 出栈 栈的基本操作 判断空栈 求栈的元素数量 读栈顶元素 总结 前言 OK呀&#xff0c;说到做…

银河麒麟服务器v10 sp1 部署 redis 及redis gui 客户端工具

上一篇&#xff1a;银河麒麟服务器v10 sp1 redis开机自动启动_csdn_aspnet的博客-CSDN博客 本文介绍另一种redis安装方式及客户端工具安装。 Redis 是一种内存数据模型存储&#xff0c;可用作数据库、缓冲区和消息传递中继。它是开源的&#xff08;BSD 许可&#xff09;。字符…

大模型基础:理论与技术的演进概述

大模型基础&#xff1a;理论与技术的演进概述 人工智能发展历程 人工智能发展历程可以概括为以下几个主要阶段: 起源阶段(1956-1980年代)&#xff0c;这一时期被称为人工智能的“黄金时代”, 达特茅斯会议首次提出人工智能概念, 开发出传统人工智能系统, 如ELIZA、深蓝等。知…

Java设计模式之行为型-命令模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色设计 四、案例分析 1、基本实现 2、点餐案例 五、总结 一、基础概念 1、将一个请求封装为一个对象&#xff0c;使您可以用不同的请求对客户进行参数化。 2、对请求排队或记录请求日志&#xff0c;以及支持可撤销的操作。 3、…

JAVA动态代理

动态代理是在运行时动态生成类字节码&#xff0c;并加载到 JVM 中 你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候&#xff0c;实际会调用到实现InvocationHandler 接口的类的 invoke()方法. 运行时的动作由invoke()方法决定控制。 其中运用了反射的相…

(vue)整个页面添加背景视频

(vue)整个页面添加背景视频 App.vue <template><div id"app" :class"[platform]"><video src"./assets/images/top/bg-video-711.mp4" autoplay muted loop class"bg"></video><router-view /></di…

关于Java的网络编程

网络的一些了解 网络通信协议 链路层&#xff1a;链路层是用于定义物理传输通道&#xff0c;通常是对某些网络连接设备的驱动协议&#xff0c;例如针对光纤、网线提供的驱动。网络层&#xff1a;网络层是整个TCP/IP协议的核心&#xff0c;它主要用于将传输的数据进行分组&…

你的隐私被泄漏了吗

近日&#xff0c;某高校毕业生在校期间窃取学校内网数据&#xff0c;收集全校学生个人隐私信息的新闻引发了人们对互联网生活中个人信息安全问题的再度关注。在大数据时代&#xff0c;算法分发带来了隐私侵犯&#xff0c;在享受消费生活等便捷权利的同时&#xff0c;似乎又有不…

按关键词全网采集

简数采集器支持按关键词全网采集&#xff0c;只需输入对应关键词&#xff0c;即可在全网采集相关数据&#xff0c;类似搜索引擎&#xff0c;无需用户配置采集规则。 简数采集器按关键词泛采集可用于舆情监控、市场研究分析等。 使用方法如下&#xff1a; 目录 1. 创建关键词…

MySQL为什么采用B+树作为索引底层数据结构?

索引就像一本书的目录&#xff0c;通过索引可以快速找到我们想要找的内容。那么什么样的数据结构可以用来实现索引呢&#xff1f;我们可能会想到&#xff1a;二叉查找树&#xff0c;平衡搜索树&#xff0c;或者是B树等等一系列的数据结构&#xff0c;那么为什么MySQL最终选择了…

【框架篇】对象注入的三种实现方式

对象注入的实现 一&#xff0c;实现方式的使用 对象注入也可被称为对象装配&#xff0c;是把Bean对象获取出来放到某个类中。 对象注入的实现方式有3种&#xff0c;分别为属性注入&#xff0c;Setter注入和构造方法注入。 为了更好地理解对象注入的实现方式&#xff0c;搞个…

Spring管理事务知识

目录 1.什么是事务 2.事务的特性ACID 3.Spring 管理事务的方式 4.Spring管理事务的体现&#xff1a;JDBCTemplate 5.声明式事务的属性有哪些 6.声明式事务属性---只读 7.声明式事务属性---超时 8.声明式事务属性---回滚策略 9.声明式事务属性---事务隔离级别 10.声明…