【Android插件化框架】插件APK中的动态代理

news2024/9/20 22:39:46

在 Android 中实现插件化框架,需要解决的问题主要如下:

  • 资源和代码的加载
  • Android 生命周期的管理和组件的注册
  • 宿主 APK 和插件 APK 资源引用的冲突解决

下面分析几个目前主流的开源框架

DL 动态加载框架 ( 2014 年底)

是基于代理的方式实现插件框架; 对 App 的表层做了处理,通过在 Manifest 中注册代理组件,当启动插件组件时,首先启动一个代理组件,然后通过这个代理组件来构建,启动插件组件;需要按照一定的规则来开发插件 APK,插件中的组件需要实现经过改造后的 Activity、FragmentActivity、Service 等的子类

优点如下:

  • 插件需要遵循一定的规则,因此安全方面可控制
  • 方案简单,适用于自身少量代码的插件化改造

缺点如下:

  • 不支持通过 This 调用组件的方法,需要通过 that 去调用
  • 由于 APK 中的 Activity 没有注册,不支持隐式调用 APK 内部的 Activity
  • 插件编写和改造过程中,需要考虑兼容性问题比较多,联调起来会比较费时费力

DroidPlugin ( 2015 年 8 月)

DroidPlugin 是 360 手机助手实现的一种插件化框架; 它可以直接运行第三方的独立 APK 文件,完全不需要对 APK 进行修改或安装;一种新的插件机制,一种免安装的运行机制,是一个沙箱(但是不完全的沙箱;就是对于使用者来说,并不知道他会把 apk 怎么样), 是模块化的基础

实现原理:

共享进程: 为android提供一个进程运行多个 apk 的机制,通过 API 欺骗机制瞒过系统

占坑: 通过预先占坑的方式实现不用在 manifest 注册,通过一带多的方式实现服务管理

Hook 机制:

动态代理实现函数 hook ,Binder 代理绕过部分系统服务限制,IO 重定向(先获取原始 Object –> Read ,然后动态代理 Hook Object 后–> Write 回去,达到瞒天过海的目的)

插件 Host 的程序架构:

img

优点如下:

  • 支持 Android 四大组件,而且插件中的组件不需要在宿主 APK 中注册
  • 支持 Android 2.3 及以上系统,支持所有的系统 API
  • 插件与插件之间,插件与宿主之间的代码和资源完全隔阂
  • 实现了进程管理,插件的空进程会被及时回收,占用内存低

缺点如下:

  • 插件 APK 中不支持自定义资源的 Notification,通知栏限制
  • 插件 APK 中无法注册具有特殊的 IntentFilter 的四大组件
  • 缺乏对 Native 层的 Hook 操作,对于某些带有 Native 代码的插件 APK 支持不友好,可能无法正常运行
  • 由于插件与插件,插件与宿主之间的代码完全隔离,因此,插件与插件,插件与宿主之间的通信只能通过 Android 系统级别的通信方式
  • 安全性担忧(可以修改,hook一些重要信息)
  • 机型适配(不是所有机器上都能行,因为大量用反射相关,如果rom厂商深度定制了framework层,反射的方法或者类不在,容易插件运用失败)

Small ( 2015 年底)

Small 是一种实现轻巧的跨平台插件化框架,具有“轻量、透明、极小化、跨平台”的理念

实现原理

动态加载类:

我们知道插件化很多都从 DexClassLoader 类有个 DexPathList 清单,支持 dex/jar/zip/apk 文件格式,却没有支持 .so 文件格式,因此 Small 框架则是把 .so 文件包装成 zip 文件格式,插入到 DexPathList 集合中,改写动态加载的代码

资源分段:

由于 Android 资源的格式是 0xPPTTNNNN ,PP 是包 ID ,00-02 是属于系统,7f 属于应用程序,03-7e 则保留,可以在这个范围内做文章 , TT 则是 Type 比如,attr 、layout 、string 等等,NNNN 则是资源全局 ID;那么这个框架则是对资源包进行重新打包,每个插件重新分配资源 ID ,这样就保证了宿主和插件的资源不冲突

动态代理注册:

在 Android 中要使用四大组件,都是需要在 manifest 清单中注册,这样才可以使用,那如何在不注册情况也能使用呢,这里就是用到动态代理机制进行 Hook ,在发送 AMS 之前用占坑的组件来欺骗系统,通过认证后,再把真正要调用的组件还原回来,达到瞒天过海目的

架构图

img

优点如下:

  • 所有插件支持内置宿主包中
  • 插件的编码和资源文件的使用与普通开发应用没有差别
  • 通过设定 URI ,宿主以及 Native 应用插件,Web 插件,在线网页等能够方便进行通信。 支持 Android 、 iOS 、和 Html5 ,三者可以通过同一套 Javascript 接口实现通信

缺点如下:

  • 暂不支持 Service 的动态注册,不过这个可以通过将 Service 预先注册在宿主的 AndroidManifest.xml 文件中进行规避,因为 Service 的更新频率通常非常低

与其他主流框架的区别:

<pre mdtype="fences" cid="n94" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">DyLA  : Dynamic-load-apk          @singwhatiwanna  
DiLA  : Direct-Load-apk           @FinalLody  
APF   : Android-Plugin-Framework  @limpoxe  
ACDD  : ACDD                      @bunnyblue  
DyAPK : DynamicAPK                @TediWang  
DPG   : DroidPlugin               @cmzy, 360
  </pre>

功能

img

透明度

img

VirtualAPK (2017年 6 月 )

VirtualAPK 是滴滴开源的一套插件化框架,支持几乎所有的 Android 特性,四大组件方面

架构图:

img

实现思路:

VirtualAPK 对插件没有额外的约束,原生的 apk 即可作为插件。插件工程编译生成 apk后,即可通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象

如下图所示,通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的 App 一样运行

合并宿主和插件的ClassLoader 需要注意的是,插件中的类不可以和宿主重复

合并插件和宿主的资源 重设插件资源的 packageId,将插件资源和宿主资源合并 去除插件包对宿主的引用 构建时通过 Gradle 插件去除插件对宿主的代码以及资源的引用

img

特性如下:

四大组件均不需要在宿主manifest中预注册,每个组件都有完整的生命周期

  • Activity:支持显示和隐式调用,支持Activity的theme和LaunchMode,支持透明主题
  • Service:支持显示和隐式调用,支持Service的start、stop、bind和unbind,并支持跨进程bind插件中的Service
  • Receiver:支持静态注册和动态注册的Receiver; ContentProvider:支持provider的所有操作,包括CRUD和call方法等,支持跨进程访问插件中的Provider
  • 自定义View:支持自定义 View,支持自定义属性和style,支持动画
  • PendingIntent:支持PendingIntent以及和其相关的Alarm、Notification和AppWidget; 支持插件Application以及插件manifest中的meta-data; 支持插件中的so

优秀的兼容性

  • 兼容市面上几乎所有的Android手机,这一点已经在滴滴出行客户端中得到验证
  • 资源方面适配小米、Vivo、Nubia 等,对未知机型采用自适应适配方案
  • 极少的 Binder Hook,目前仅仅 hook了两个Binder:AMS和IContentProvider,hook 过程做了充分的兼容性适配
  • 插件运行逻辑和宿主隔离,确保框架的任何问题都不会影响宿主的正常运行

入侵性极低

  • 插件开发等同于原生开发,四大组件无需继承特定的基类
  • 精简的插件包,插件可以依赖宿主中的代码和资源,也可以不依赖
  • 插件的构建过程简单,通过 Gradle 插件来完成插件的构建,整个过程对开发者透明

如下是 VirtualAPK 和主流的插件化框架之间的对比

img

项目介绍:

speed-tools 是一款基于代理模式的动态部署apk热更新框架、插件化开发框架;

speed-tools这个名字主要指的快速迭×××发工具集的意思

功能与特性:

1、支持Android 2.3 以上版本

2、支持R文件资源直接调用

3、开发过程中无发射调用

4、apk无需安装直接调用

5、代理模式对代码侵入性少

6、使用简单,只需要继承简单的类即可

项目实现

添加依赖:

<pre mdtype="fences" cid="n15" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">compile 'com.liyihangjson:speed_tools:1.0.3'</pre>

首先看看项目结构:

img

lib_speed_tools: 插件化核心功能library module_host_main:宿主工程主工程,负责加载部署apk module_client_one:测试业务apk 1 module_client_two:测试业务apk 2 lib_img_utils:测试imageloader图片框架

注意:需要使用speed tools 只需要依赖lib_speed_tools即可,然后开始配置插件化步骤:

首先在module_client_one中创建业务逻辑类:TestClass.java

<pre mdtype="fences" cid="n25" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">/**
 *  by liyihang
 */
public class TestClass extends SpeedBaseInterfaceImp {

 private Activity activity;

 @Override
 public void onCreate(Bundle savedInstanceState, final Activity activity) {
 this.activity=activity;

 activity.setContentView(R.layout.activity_client_main);

 activity.findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() {
 @Override
 public void onClick(View v) {
 SpeedUtils.goActivity(activity,"first_apk", "two_class");
 }
 });

 ImageView imageView= (ImageView) activity.findViewById(R.id.img_view);
 imageView.setVisibility(View.VISIBLE);
 ImgUtils.getInstance(activity).showImg("https://img-my.csdn.net/uploads/201309/01/1378037235_3453.jpg", imageView);

 }
}</pre>

SpeedBaseInterfaceImp业务组件中业务activity代理类,他是实现了主要的生命周期方法,相当于组件的activity类

然后创建hock类每个业务组件中只创建一个:ClientMainActivity.java

<pre mdtype="fences" cid="n31" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">public class ClientMainActivity extends SpeedClientBaseActivity {

 @Override
 public SpeedBaseInterface getProxyBase() {
 return new TestClass();
 }

}</pre>

这个类在组件中是唯一的,作用就是hock在独立测试时候使用

接下来配置配置组件的AndroidManifest.xml

<pre mdtype="fences" cid="n37" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> <application
 android:allowBackup="true"
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"
 android:supportsRtl="true"
 android:theme="@style/SpeedTheme">

 <!--必须设置root_class-->
 <meta-data
 android:name="root_class"
 android:value="com.example.clientdome.TestClass" />

 <meta-data
 android:name="two_class"
 android:value="com.example.clientdome.TwoClass" />

 <activity
 android:name=".ClientMainActivity"
 android:theme="@style/SpeedTheme">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />

 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>

 <!--组件意图-->
 <intent-filter>
 <data android:scheme="speed_tools" android:host="sijienet.com" android:path="/find_class"/>
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 </intent-filter>
 </activity>
 </application></pre>

组件意图写死保持一直,root_class 是调用死后使用对于配置的com.example.clientdome.TestClass业务类,这样业务组件配置完成

接下来配置宿主工程module_host_main;

创建宿主工程唯一hock类:ApkActivity.java

<pre mdtype="fences" cid="n44" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">/**
 *  by liyihang
 *  blog http://sijienet.com/
 */
public class ApkActivity extends SpeedHostBaseActivity {

 @Override
 public String getApkKeyName() {
 return HostMainActivity.FIRST_APK_KEY;
 }

 @Override
 public String getClassTag() {
 return null;
 }
}</pre>

整个宿主工程创建一个类即可,用户是hock activity;然后创建一个开屏页apk第一次加载时候需要一些时间,放入开屏等待页面是非常合适的

HostMainActivity.java

<pre mdtype="fences" cid="n52" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;">/**
 *  by liyihang
 *  blog http://sijienet.com/
 */
public class HostMainActivity extends AppCompatActivity implements Runnable,Handler.Callback, View.OnClickListener {

 public static final String FIRST_APK_KEY="first_apk";
 public static final String TWO_APK_KEY="other_apk";

 private Handler handler;

 private TextView showFont;
 private ProgressBar progressBar;
 private Button openOneApk;
 private Button openTwoApk;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_host_main);

 showFont= (TextView) findViewById(R.id.show_font);
 progressBar= (ProgressBar) findViewById(R.id.progressbar);
 openOneApk= (Button) findViewById(R.id.open_one_apk);
 openTwoApk= (Button) findViewById(R.id.open_two_apk);

 handler=new Handler(this);
 new Thread(this).start();
 }

 @Override
 public void run() {
 String s = "module_client_one-release.apk";
 String dexOutPath="dex_output2";
 File nativeApkPath = SpeedUtils.getNativeApkPath(getApplicationContext(), s);
 SpeedApkManager.getInstance().loadApk(FIRST_APK_KEY, nativeApkPath.getAbsolutePath(), dexOutPath, this);

 String s2 = "module_client_two-release.apk";
 String dexOutPath3="dex_output3";
 File nativeApkPath2 = SpeedUtils.getNativeApkPath(getApplicationContext(), s2);
 SpeedApkManager.getInstance().loadApk(TWO_APK_KEY, nativeApkPath2.getAbsolutePath(), dexOutPath3, this);

 handler.sendEmptyMessage(0x78);
 }

 @Override
 public boolean handleMessage(Message message) {
 showFont.setText("当前是主宿主apk\n插件apk完毕");
 progressBar.setVisibility(View.GONE);
 openOneApk.setVisibility(View.VISIBLE);
 openTwoApk.setVisibility(View.VISIBLE);
 openOneApk.setOnClickListener(this);
 openTwoApk.setOnClickListener(this);
 return false;
 }

 @Override
 public void onClick(View v) {
 if (v.getId()==R.id.open_one_apk)
 {
 SpeedUtils.goActivity(this, FIRST_APK_KEY, null);
 }
 if (v.getId()==R.id.open_two_apk)
 {
 SpeedUtils.goActivity(this, TWO_APK_KEY, null);
 }
 }
}</pre>

加载apk核心代码是:

<pre mdtype="fences" cid="n58" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;"> String s = "module_client_one-release.apk";
 String dexOutPath="dex_output2";
 File nativeApkPath = SpeedUtils.getNativeApkPath(getApplicationContext(), s);
 SpeedApkManager.getInstance().loadApk(FIRST_APK_KEY, nativeApkPath.getAbsolutePath(), dexOutPath, this);</pre>

业务apk都是放在assets目录中

最后配置AndroidManifest.xml文件:

<pre mdtype="fences" cid="n63" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit;"><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 package="com.example.hostproject">

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

 <application
 android:allowBackup="true"
 android:icon="@mipmap/ic_launcher"
 android:label="@string/app_name"
 android:supportsRtl="true"
 android:theme="@style/SpeedTheme">

 <!--启动activity 加载apk-->
 <activity android:name=".HostMainActivity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />

 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter>
 </activity>

 <!--组件hack-->
 <activity
 android:name=".ApkActivity"
 android:label="@string/app_name"
 android:theme="@style/SpeedTheme" >
 <intent-filter>
 <data android:scheme="speed_tools" android:host="sijienet.com" android:path="/find_class"/>
 <action android:name="android.intent.action.VIEW"/>
 <category android:name="android.intent.category.DEFAULT"/>
 </intent-filter>
 </activity>
 </application>

</manifest></pre>

这样所有配置结束,插件化实现

小结

正如开头所说,要实现插件化的框架,无非就是解决那典型的三个问题:插件代码如何加载、插件中的组件生命周期如何管理、插件资源和宿主资源冲突怎么办;

每个框架针对这三个问题,都有不同的解决方案,同时呢,根据时间顺序,后出来的框架往往都会吸收已经出的框架精髓,进而修复那些比较有里程碑意义框架的不足;

但这些框架的核心思想都是用到了代理模式,有的在表面层进行代理,有的则在系统应用层进行代理,通过代理达到替换和瞒天过海,最终让 Android 系统误以为调用插件功能 和调用原生开发的功能是一样的,进而达到插件化和原生兼容编程的目的

文末

我从去年开始接触Android开发,以下结合自己的一点项目经验,同时参考了Google优化文档和网上的诸多技术大牛给出的意见,整理出这份电子手册 《Android 开发学习笔记》私信发送 “进阶” 即可 直达获取;这份笔记汇总了我从事 Android 开发行业以来遇到的 技术难点问题,相信可以帮助到大家

整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下~

你的支持,就是我的动力;祝各位前程似锦

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

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

相关文章

渗透测试 | APP信息收集

0x00 免责声明 本文仅限于学习讨论与技术知识的分享&#xff0c;不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;本文作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担…

【学习笔记76】认识ajax和ajax的请求

一、认识前后端交互 1、前后端交互 前端向后端发送请求, 索要数据因为前端没有办法存储大量数据, 所以数据都存储在后端当前端需要数据时, 需要向后端发送请求, 得到想要的数据 2、什么是ajax ajax全名async javascript and XML(异步JavaScript和XML)是前后台交互的能⼒&#…

手摸手带你撸一个拖拽效果

目录 前言 准备 创建所需要结构 编写样式 js编写拖拽效果 解释方法 所有代码 结尾 前言 最近看见一个拖拽效果的视频&#xff0c;看好多人评论说跟着敲也没效果&#xff0c;还有就是作者也不回复大家提出的一些疑问&#xff0c;本着知其然必要知其所以然的心理&#xf…

【疯狂世界杯】css 动画实现跳动的足球

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;…

python T检验

T检验通常分为三种&#xff1a;单样本T检验、双样本T检验、配对样本T检验原理可以参考&#xff1a;一文详解t检验本文主要介绍使用python实现T检验的过程&#xff0c;内容主要是参考这篇博文&#xff1a;利用python库stats进行t检验 文章目录一、单样本T检验二、独立样本t检验&…

二、Git本地仓库基本操作——创建Git仓库、提交更新或删除文件

1. 创建本地工作仓库 创建本地工作仓库有两种方法&#xff1a; git init 在本地初始化一个git仓库git clone 直接克隆一个远程的git仓库 方法一&#xff1a; 我们在其中一个目录下&#xff0c;点击鼠标右键&#xff0c;然后启动git bash。输入下面命令&#xff1a; git in…

【实战案例】Python 信用卡欺诈检测其实特简单

当我们在网上购买产品时&#xff0c;很多人喜欢使用信用卡。但信用卡欺诈常常会在身边发生&#xff0c;网络安全正成为我们生活中至关重要的一部分。 为了解决这个问题&#xff0c;我们需要利用机器学习算法构建一个异常行为的识别系统&#xff0c;如果发现可疑&#xff0c;中…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java高校饭堂管理系统8gmjo

这个选题的话其实有很多的&#xff0c;就看你自己能接受怎么样的&#xff0c;比如可以做网站类、系统类、小程序类、安卓app、大数据类等等&#xff0c;这个也要看你个人能力和技术问题&#xff0c;如果技术小白或者有一点点基础的话建议选择网站类和系统类的&#xff0c;如果有…

微服务框架 SpringCloud微服务架构 10 使用Docker 10.2 镜像命令练习

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构10 使用Docker10.2 镜像命令练习10.2.1 练习10 使用Docker 10.2 镜像命令…

Spring boot 自动装配原理

Spring boot 为了解决Bean的复杂配置&#xff0c;引入了自动装配机制&#xff1b;那么什么是自动装配&#xff0c;它的原理又是什么呢&#xff1f;我们先通过以下例子来了解以一下什么是自动装配。 Spring boot 集成 redis 引入依赖包 <dependency><groupId>org…

CTFShow pwn07 (ret2libc-64bit

用ROPgabdet 找到pop rdi地址和ret地址&#xff1a; 来自ctfshow pwn7&#xff1a; 64位程序是需要栈平衡的&#xff0c;而且前六个寄存器用完了才会用栈传参 %rdi&#xff0c;%rsi&#xff0c;%rdx&#xff0c;%rcx&#xff0c;%r8&#xff0c;%r9 用作函数参数&#xff0c;依…

rust编程-rust所有权理解(chapter 4.1)

目录 1. 什么是所有权 1.1 堆与栈 1.2 变量作用域 1.3 String类型 1.4 内存和分配 1.5 变量和数据交互的方式 1.5 所有权和函数 1.6 返回值和作用域 所有权 是Rust独有的特性&#xff0c;该设计开创了编程语言中的先河。所有权使得Rust能保证内存的安全&#xff0c;且不…

游程编码(Run Length Coding)

游程编码游程编码基本介绍示例1示例2游程编码适用的场景游程编码 游程编码&#xff08;Run Length Coding&#xff0c;简称RLC&#xff09;又称游程编码、行程长度编码、变动长度编码 等&#xff0c;是一种统计编码。主要技术是检测重复的比特或字符序列&#xff0c;并用它们的…

亚马逊云科技re:Invent 2022 Ruba Borno主题演讲

2022亚马逊云科技re:Invent全球大会精彩内容应接不暇&#xff0c;亚马逊云科技全球渠道与联盟副总裁Ruba Borno在2022亚马逊云科技re:Invent大会的全球合作伙伴峰会上&#xff0c;为合作伙伴带来一系列全新的合作伙伴创新服务。 云上发展持续加速的当下&#xff0c;上云好比一场…

使用Fiddler对手机App抓包

目录 一、查看 Fiddler 的 ip 地址 二、设置 Fiddler 允许远程连接 三、进行手机端 App 的抓包 3.1.准备工作 3.2.手机设置 3.3.安装根证书 四、可能会遇到的问题 一、查看 Fiddler 的 ip 地址 有两种方法都可以查询到。 第一种方法&#xff1a; 打开 Fiddler&#xff…

[附源码]计算机毕业设计springboot在线招聘网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

一分钟带你了解音视频开发进阶(先收藏了)

FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发学习路线 随着基础设施的完善&#xff08;光纤入户、wifi覆盖、5G普及&#xff09;的影响&#xff0c;将短视频、直播、视频会议、在线教育、在线医疗瞬间推到了顶峰&#xff0c;人们对音视频的需求和要求也越来越强烈 音视…

实现java项目idea打包发布至服务器(完整版)

问题&#xff1a;如何快速部署本地代码到服务器&#xff1f; 今天介绍的是使用idea的一款插件(Alibaba Cloud Toolkit)实现&#xff0c;首先需要在自己的服务器上安装运行环境&#xff0c;包括&#xff1a;jdk、maven、mysql等&#xff0c;这些操作就不详细说了&#xff0c;可…

Inductive Representation Learning on Large Graphs 论文/GraphSAGE学习笔记

1 动机 1.1 过去的方法 现存的方法大多是transductive的&#xff0c;也就是说&#xff0c;在训练图的时候需要将整个图都作为输入&#xff0c;为图上全部节点生成嵌入&#xff0c;每个节点在训练的过程中都是可知的。举个例子&#xff0c;上一次我学习了GCN模型&#xff0c;它…

考研数据结构大题整合_组三(LZH组)

考研数据结构大题整合 目录考研数据结构大题整合三、LZH组LZH 组一LZH 组二LZH 组三LZH 组四LZH 组五LZH 组七三、LZH组 LZH 组一 给出如图所示的无向图G的邻接矩阵和邻接表两种存储结构. &#xff08;2&#xff09;解答下面的问题&#xff08;6分&#xff09; &#xff08;…