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

news2024/11/18 15:31:50

前些天发现了一个蛮有意思的人工智能学习网站,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?
因为插件APK是没有安装和运行的一个APK文件,是没有上下文环境的,所以不能使用自身的this,因为自身的this为空。
2、为什么要有代理的Activity?
因为插件APK是没有安装和运行的一个APK文件,是没有上下文环境的,所以插件内部的Activity也就无法直接运行,只能通过代理的Activity,做为其上下文,并加载布局进行显示。
3、这种插件化,在写插件开发的时候,有什么要注意的事项?
一定要注意上下文的问题,凡是涉及到上下文的时候,一定要使用代理类的上下文环境,否则会报错。

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

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

相关文章

pdf转word文档怎么转?这三个实用方法给你。

PDF (Portable Document Format) 是一种广泛应用于电子文档分发的文件格式&#xff0c;它可以跨平台和保持文档格式的一致性。然而&#xff0c;有时我们需要对PDF文档进行编辑和修改&#xff0c;这时将其转换为可编辑的Word文档就变得至关重要。转换PDF为Word文档可以让我们轻松…

[Halcon3D] 3D手眼标定理论与示例解析

&#x1f4e2;博客主页&#xff1a;https://loewen.blog.csdn.net&#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;本文由 丶布布原创&#xff0c;首发于 CSDN&#xff0c;转载注明出处&#x1f649;&#x1f4e2;现…

NSSCTF刷web(2)

[NISACTF 2022]bingdundun~ bingdundun处感觉像文件包含,改upload为index 发现确实,猜测会补一个后缀.php 那常规文件包含都不行了,这里还有一个文件上传的功能,考虑phar协议 <?php$phar new Phar("test.phar"); $phar->startBuffering(); $phar->setStu…

第十一章:GCN——图卷积神经网络:全面回顾

0.摘要 图在许多应用领域中自然出现&#xff0c;包括社交分析、生物信息学和计算机视觉。图的独特能力使其能够捕捉数据之间的结构关系&#xff0c;从而可以比单独分析数据获得更多的见解。然而&#xff0c;在图上解决学习问题往往非常具有挑战性&#xff0c;因为 (1)许多类型的…

1、Java入门教程【基础】

1、环境搭建 首先java的产品叫JDK&#xff0c;必须安装JDK才能使用Java。 Java的发展史&#xff1a; 其中&#xff0c;LTS是比较稳定的版本&#xff0c;推荐比较新的LTS17版本。 JDK下载&#xff1a;JDK Development Kit 17.0.7 downloads 下载完成后&#xff0c;我们双击安…

第十一章:MULTI-SCALE CONTEXT AGGREGATION BY DILATED CONVOLUTIONS——通过膨胀卷积的多尺度上下文聚合

0.摘要 目前用于语义分割的先进模型是基于最初设计用于图像分类的卷积网络的改进。然而&#xff0c;像语义分割这样的密集预测问题在结构上与图像分类不同。在这项工作中&#xff0c;我们开发了一个专门为密集预测设计的新的卷积网络模块。所提出的模块使用膨胀卷积来系统地聚合…

【深度学习 | 计算机视觉】Focal Loss原理及其实践(含源代码)

参考文献&#xff1a; https://www.jianshu.com/p/437ce8ed0413文章目录 一、导读二、Focal Loss 原理三、实验对比3.1 使用交叉熵损失函数3.2 使用Focal Loss 损失函数3.3 总结 一、导读 Focal Loss 是一个在交叉熵&#xff08;CE&#xff09;基础上改进的损失函数&#xff…

Java正则表达式MatchResult的接口、Pattern类、Matcher类

Java正则表达式MatchResult的接口 java.util.regex.MatchResult接口表示匹配操作的结果。 此接口包含用于确定与正则表达式匹配的结果的查询方法。可以看到匹配边界&#xff0c;组和组边界&#xff0c;但不能通过MatchResult进行修改。 接口声明 以下是java.util.regex.Matc…

spring复习:(34)配置文件的方式创建ProxyFactoryBean

一、配置文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:c"http://www.springframework.org/s…

vscode 无法格式化python代码、无法格式化C++代码(vscode格式化失效)另一种解决办法:用外部工具yapf格式化(yapf工具)

文章目录 我真的解决方法&#xff1a;用yapfyapf工具使用方法示例格式化单个文件&#xff08;格式化前先用-d参数预先查看格式化更改内容&#xff0c;以决定是否要更改&#xff09;格式化某个目录递归格式化某个目录 我真的 神马情况&#xff0c;我的vscode死活不能格式化pyth…

路径规划算法:基于减法平均优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于减法平均优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于减法平均优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

用Python自动化处理Excel表格详解

Excel表格基础知识 Excel表格可以帮助用户创建、编辑、格式化和计算数据&#xff0c;并生成各种图表和报表。Excel表格通常用于商业、金融、科学、教育等领域。 Excel表格的常用操作 Excel表格的常用操作包括插入、删除、移动、复制、粘贴、排序和筛选、图表等。这些操作可以…

node操作MySQL数据库

本文节选自我的博客&#xff1a;node 操作 MySQL 数据库 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是MilesChen&#xff0c;偏前端的全栈开发者。&#x1f4dd; CSDN主页&#xff1a;爱吃糖的猫&#x1f525;&#x1f4e3; 我的博客&#xff1a;爱吃糖的猫&…

集群基础4——haproxy负载均衡mariadb

文章目录 一、环境说明二、安装配置mariadb三、安装配置haproxy四、验证 一、环境说明 使用haproxy对mysql多机单节点进行负载均衡。 主机IP角色安装服务192.168.161.131后端服务器1mariadb&#xff0c;3306端口192.168.161.132后端服务器2mariadb&#xff0c;3306端口192.168.…

【2023 年第二届钉钉杯大学生大数据挑战赛初赛】 初赛 A:智能手机用户监测数据分析 问题一Python代码分析

2023 年第二届钉钉杯大学生大数据挑战赛初赛 初赛 A&#xff1a;智能手机用户监测数据分析 问题一Python代码分析 1 题目 2023 年第二届钉钉杯大学生大数据挑战赛初赛题目 初赛 A&#xff1a;智能手机用户监测数据分析 一、问题背景 近年来&#xff0c;随着智能手机的产生&a…

STM32F10x外部中断/事件控制器(EXTI)应用

往期文章&#xff1a; STM32F1x固件库函数学习笔记&#xff08;一&#xff09; 文章目录 一、EXTI简介二、EXTI初始化结构体详解三、外部中断&#xff08;EXTI&#xff09;编程要点及例程参考文献 一、EXTI简介 外部中断/事件控制器&#xff0c;简称&#xff1a;EXTI&#x…

Jenkins打包、发布、部署

目录 前言 一、安装jdk 二、安装maven 三、安装git 四、安装jenkins 五、访问jenkins 六、创建用户 七、配置jenkins 八、执行 总结 前言 服务器&#xff1a;CentOS 7.9 64位 jdk&#xff1a;1.8 maven&#xff1a;3.9.1 git&#xff1a;git version 1.8.3.1 jenkins&a…

计算机中的数制与编码(二进制转换)

一、进制表示 1. 十进制表示 使用&#xff08;0&#xff0c;1&#xff0c;2&#xff0c;…&#xff0c;9&#xff09;十位数字表示&#xff0c;十进制运算时逢十进一。 2. 二进制表示 使用(0&#xff0c;1)两个数字表示&#xff0c;二进制运算时逢二进一。 3. 十六进制表示…

AIGC文生图:stable-diffusion-webui部署及使用

1 stable-diffusion-webui介绍 Stable Diffusion Web UI 是一个基于 Stable Diffusion 的基础应用&#xff0c;利用 gradio 模块搭建出交互程序&#xff0c;可以在低代码 GUI 中立即访问 Stable Diffusion Stable Diffusion 是一个画像生成 AI&#xff0c;能够模拟和重建几乎…

宝塔面板清理

查看磁盘使用情况时发现/dev/sda1满了&#xff0c;重启服务器也不行&#xff0c;瞎折腾了半天&#xff0c;才发现是宝塔的回收站占了较大的磁盘&#xff0c;于是按以下操作清理了下&#xff0c;就可以了 1、清除系统监控记录。打开宝塔面板后台&#xff0c;找到监控&#xff0c…