红橙Darren视频笔记 热更新 bsdiff bspatch 在Android设备上的应用 架构篇1完结篇

概述
当时红橙的视频讲解就差不多90分钟,但是真正自己做出来热更新的demo还是花了八九个晚上,期间遇到各种各样的问题,什么叫台上一分钟 台下十年功是深有体会了。
本节会涉及一部分NDK的知识 推荐阅读Android官方的NDK简介
https://developer.android.com/ndk/guides
上一节我们学习了如何在Linux下使用bspath和bsdiff 这次我们学习一下如何在Android设备上使用该第三方库
我们的热更新的一般流程是这样的
在这里插入图片描述
当然我们这里只是写个demo 大致了解热更新的原理,所以会省掉很多步骤 比如查询是否需要更新新版本,比如下载patch等
我们接着需要设计服务端和客户端 本来我其实想把客户端的部分省掉的 直接使用Linux系统编译patch写demo的结果真正使用的时候 在bspatch.c中报错 “Corrupt patch” 猜测可能是Linux的文件编码与Android设备的不同 他们的patch不是通用的。只能老老实实的搭建服务端。
由于要写NDK的代码 需要预先安装CMake NDK。

服务端搭建

1.1在src/main/创建jni文件夹

我们需要下载bspatch的源代码 并将其放到该目录,另外 在上一篇中 我们知道该第三方框架依赖了biz2的库 因此我们也要将其下载下来。在如下链接可以下载bspath和bzip2的源码
https://src.fedoraproject.org/lookaside/pkgs/bsdiff/bsdiff-4.3.tar.gz/e6d812394f0e0ecc8d5df255aa1db22a/
https://www.sourceware.org/bzip2/downloads.html
下载完毕后 将两个压缩包里面的.c和.h文件都拷贝到jni的目录下

1.2.创建mk文件

在jni目录创建如下两个文件
Android.mk

#注释部分解释来自 https://developer.android.com/ndk/guides/android_mk
LOCAL_PATH := $(call my-dir) # 此变量表示源文件在开发树中的位置 my-dir 将返回Android.mk所在当前目录
# CLEAR_VARS 变量,其值由构建系统提供 LEAR_VARS 变量指向一个特殊的 GNU Makefile,后者会为您清除许多 LOCAL_XXX 变量
include $(CLEAR_VARS)
# LOCAL_MODULE 变量存储您要构建的模块的名称 每个模块名称必须唯一,且不含任何空格 如下会生成名为 libnative-lib.so
LOCAL_MODULE    :=bsdiff
# 列举源文件,以空格分隔多个文件
LOCAL_SRC_FILES :=bsdiff.c
# 此变量列出了在构建共享库或可执行文件时使用的额外链接器标记
LOCAL_LDLIBS := -ljnigraphics -llog
# BUILD_SHARED_LIBRARY 变量指向一个 GNU Makefile 脚本,该脚本会收集您自最近 include 以来在 LOCAL_XXX 变量中定义的所有信息。此脚本确定要构建的内容以及构建方式
include $(BUILD_SHARED_LIBRARY)

Application.mk

APP_ABI := armeabi-v7a armeabi x86_64 x86	#表示 编译目标 ABI(应用二进制接口)
APP_PLATFORM := android-9

1.3.修改moudle的build.gradle

plugins {id 'com.android.application'
}android {compileSdkVersion 30buildToolsVersion "30.0.3"// 指定ndk路径ndkPath "20.0.5594570"defaultConfig {applicationId "com.example.hotupdatediff"minSdkVersion 24targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"// ndk 编译生成.so文件ndk {moduleName "bspatch"         //生成的so名字abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"  //输出指定abi体系结构下的so库}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}sourceSets {main {jni.srcDirs = []// 设置禁止gradle生成Android.mkjniLibs.srcDirs = ['src/main/libs']}}externalNativeBuild {ndkBuild {// 指定mk文件路径path "src/main/jni/Android.mk"}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}dependencies {implementation 'androidx.appcompat:appcompat:1.3.0'implementation 'com.google.android.material:material:1.3.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

1.4.创建jni方法

在MainActivity同级目录创建DiffUtils

public class DiffUtils {static {System.loadLibrary("bsdiff");}/*** @param oldApkPath 旧版本的apk* @param newApkPath 合并后新的apk路径* @param patchPath  差分包路径*/public static native void diff(String oldApkPath, String newApkPath, String patchPath);
}

1.5 生成头文件

cd到能找到对应文件的目录 以便生成头文件
D:\testDarren\learn_darren_eassy_joke\hotupdatediff\src\main> javah -d jni -classpath ./java com.example.hotupdatediff.DiffUtils
PS: 如果遇到如下报错:

ndk-build.cmd finished with non-zero exit value 2

需要找到下载的ndk 里面的ndk-build.cmd
编辑该文件

@echo off
%~dp0\build\ndk-build.cmd %*

删除上面的第二行
最后将生成的.h文件移动到上面创建的jni目录

1.6 轻微修改bsdiff.c

在头部加上依赖文件(尝试编译的时候会报各种错 一般都是缺少依赖 一个个添加吧)

#include "crctable.c"
#include "compress.c"
#include "decompress.c"
#include "randtable.c"
#include "blocksort.c"
#include "huffman.c"
#include "bzlib.c"
#include "bzlib.h"
#include "com_example_hotupdatediff_DiffUtils.h"

滑到该文件的最下面的方法 修改main方法为diff
在该文件的最后添加一个方法 该方法是jni方法的具体实现 实际调用了c文件的diff方法

JNIEXPORT void JNICALL
Java_com_example_hotupdatediff_DiffUtils_diff(JNIEnv *env, jclass clazz, jstring old_apk_path,jstring new_apk_path, jstring patch_path) {// 1.封装参数int argc = 4;char *argv[4];// 1.1 转换  jstring -> char*char *old_pak_cstr = (char *) (*env)->GetStringUTFChars(env, old_apk_path, NULL);char *new_apk_cstr = (char *) (*env)->GetStringUTFChars(env, new_apk_path, NULL);char *patch_cstr = (char *) (*env)->GetStringUTFChars(env, patch_path, NULL);// 第0的位置随便给argv[0] = "diff";argv[1] = old_pak_cstr;argv[2] = new_apk_cstr;argv[3] = patch_cstr;// 2.调用上面的方法  int argc,char * argv[]diff(argc, argv);// 3.释放资源(*env)->ReleaseStringUTFChars(env, old_apk_path, old_pak_cstr);(*env)->ReleaseStringUTFChars(env, new_apk_path, new_apk_cstr);(*env)->ReleaseStringUTFChars(env, patch_path, patch_cstr);
}

1.7 在Java文件中调用该方法

public class MainActivity extends AppCompatActivity {private static int READ_EXTERNAL_STORAGE_REQUEST_CODE = 1;private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 2;private String mPatchPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "1_2.patch";private String mNewApkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "2.0.apk";private String mOldApkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "1.0.apk";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (!checkPermission(READ_EXTERNAL_STORAGE)) {requestPermission(this, READ_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE_REQUEST_CODE);}if (!checkPermission(WRITE_EXTERNAL_STORAGE)) {requestPermission(this, WRITE_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE_REQUEST_CODE);}}public void requestPermission(Activity activity, String permission, int requestCode) {//第一次申请权限被拒后每次进入activity就会调用,或者用户之前允许了,之后又在设置中去掉了该权限if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {Toast.makeText(activity, permission + " permission needed. Please allow in App Settings for additional functionality.", Toast.LENGTH_LONG).show();} else {ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);}}public boolean checkPermission(String permission) {int result = ContextCompat.checkSelfPermission(getApplicationContext(), permission);return result == PackageManager.PERMISSION_GRANTED;}public void diff(View view) {if (!new File(mOldApkPath).exists()) {Toast.makeText(MainActivity.this, "hotUpdateDiff: mOldApkPath doesn't exist", Toast.LENGTH_SHORT).show();return;}if (!new File(mNewApkPath).exists()) {Toast.makeText(MainActivity.this, "hotUpdateDiff: mNewApkPath doesn't exist", Toast.LENGTH_SHORT).show();return;}// 下面是一个耗时操作 为了示意简便 就不开异步操作了(实际就是懒)DiffUtils.diff(mOldApkPath, mNewApkPath, mPatchPath);}
}

我们看到上面的代码中申请了读写外部存储的权限 因为需要读取1.0.apk与2.0.apk并生成1_2.apk 因此需要读写权限
另外还要再清单文件中声明

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

至此 服务端搭建完毕 可以尝试编译一下 查看生成的apk中是否有so文件
如果存在 基本就算成功了。

客户端搭建

客户端建立的步骤和服务端基本类似 有些许地方有些不同

2.1在src/main/创建jni文件夹

这一步和服务端操作完全一致

2.2.创建mk文件

基本和服务端一致 除了Android.mk中构建的模块从bsdiff修改为bspatch

2.3.修改moudle的build.gradle

这一步也是基本和服务端一致 主要差别是配置了release相关的编译
因为之后生成的apk需要调用FileProvider的相关方法安装apk
在Android 8.0以后(不清楚是不是更早版本)Android不支持直接安装debug版本的apk(adb install可以)
试图安装debug版本的apk会提示apk not installed 因此需要编译release版本的apk
关于签名文件.jks的生成 可以参考
https://blog.csdn.net/u011109881/article/details/113948590

plugins {id 'com.android.application'
}android {signingConfigs {release {storeFile file('..\\joke.jks')storePassword '12345678'keyAlias 'joke'keyPassword '12345678'}}compileSdkVersion 30buildToolsVersion "30.0.3"// 指定ndk路径ndkPath "20.0.5594570"defaultConfig {applicationId "com.example.hotupdate"minSdkVersion 24targetSdkVersion 30versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"// ndk 编译生成.so文件ndk {moduleName "bspatch"         //生成的so名字abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"  //输出指定abi体系结构下的so库}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'signingConfig signingConfigs.release}}sourceSets {main {jni.srcDirs = []// 设置禁止gradle生成Android.mkjniLibs.srcDirs = ['src/main/libs']}}
//    以下注释部分代码不生效 可能低版本gradle是生效的 至少gralde 6.5 无效
//    task ndkBuild(type: Exec) {//设置新的so的生成目录
//        commandLine "C:\\codebase\\android-sdk_60_windows\\ndk\\20.0.5594570\\ndk-build.cmd",
//                'NDK_PROJECT_PATH=build/aaa/ndk',
//                'NDK_LIBS_OUT=libs'
//                'APP_BUILD_SCRIPT=src/main/jni/Android.mk'
//                'NDK_APPLICATION_MK=src/main/jni/Application.mk'
//    }
//    tasks.withType(JavaCompile) {
//        compileTask -> compileTask.dependsOn ndkBuild
//    }// 上面注释的部分 用下面的替代externalNativeBuild {ndkBuild {// 指定mk文件路径path "src/main/jni/Android.mk"}}compileOptions {sourceCompatibility JavaVersion.VERSION_1_8targetCompatibility JavaVersion.VERSION_1_8}
}dependencies {implementation 'androidx.appcompat:appcompat:1.3.0'implementation 'com.google.android.material:material:1.3.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.4'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

2.4.创建jni方法

在MainActivity同级目录创建PatchUtils

public class PatchUtils {static {System.loadLibrary("bspatch");}/*** @param oldApkPath 旧版本的apk* @param newApkPath 合并后新的apk路径* @param patchPath  差分包路径*/public static native void combine(String oldApkPath, String newApkPath, String patchPath);
}

2.5 生成头文件

cd到能找到对应文件的目录 以便生成头文件
D:\testDarren\learn_darren_eassy_joke\hotupdate\src\main> javah -d jni -classpath ./java com.example.hotupdate.PatchUtils
最后将生成的.h文件移动到上面创建的jni目录

2.6 轻微修改bspatch.c

在头部加上依赖文件

#include "crctable.c"
#include "compress.c"
#include "decompress.c"
#include "randtable.c"
#include "blocksort.c"
#include "huffman.c"
#include "bzlib.c"
#include "com_example_hotupdate_PatchUtils.h"

滑到该文件的最下面的方法 修改main方法为combine
在该文件的最后添加一个方法 该方法是jni方法的具体实现 实际调用了c文件的combine方法 该方法也和上面的十分类似

JNIEXPORT void JNICALL Java_com_example_hotupdate_PatchUtils_combine(JNIEnv *env, jclass jclz, jstring old_pak_path, jstring new_apk_path, jstring patch_path) {// 1.封装参数int argc = 4;char *argv[4];// 1.1 转换  jstring -> char*char *old_pak_cstr = (char *) (*env)->GetStringUTFChars(env, old_pak_path, NULL);char *new_apk_cstr = (char *) (*env)->GetStringUTFChars(env, new_apk_path, NULL);char *patch_cstr = (char *) (*env)->GetStringUTFChars(env, patch_path, NULL);// 第0的位置随便给argv[0] = "combine";argv[1] = old_pak_cstr;argv[2] = new_apk_cstr;argv[3] = patch_cstr;// 2.调用上面的方法  int argc,char * argv[]combine(argc, argv);// 3.释放资源(*env)->ReleaseStringUTFChars(env, old_pak_path, old_pak_cstr);(*env)->ReleaseStringUTFChars(env, new_apk_path, new_apk_cstr);(*env)->ReleaseStringUTFChars(env, patch_path, patch_cstr);
}

2.7 在Java文件中调用该方法

public class MainActivity extends AppCompatActivity {private static int READ_EXTERNAL_STORAGE_REQUEST_CODE = 1;private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE = 2;private static final String TAG = "hjcai";private String mPatchPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "1_2.patch";private String mNewApkPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+ File.separator + "new.apk";protected void hotUpdate() {// 1.使用当前版本查询是否需要更新// 2.如果需要更新版本// a 部分更新 下载patch// b 下载全部apk// 3.下载完差分包之后, 调用native方法去合并生成新的apk// 是一个耗时操作 开线程, Handler, AsyncTask 这里就不开线程了// 获取本地的getPackageResourcePath()apk路径if (!new File(mPatchPath).exists()) {Log.e(TAG, "hotUpdate: mPatchPath doesn't exist");return;}// 本地apk路径怎么来,已经被安装了  1.0String oldApkPath = getPackageResourcePath();//获取当前apk的路径// 需要申请sdcard读写权限PatchUtils.combine(oldApkPath, mNewApkPath, mPatchPath);// 4.删除patchdelFile(mPatchPath);Log.e(TAG, "hotUpdate: patch deleted");// 5.需要校验签名    就是获取本地apk的签名,与我们新版本的apk作对比// 6.安装最新版本
// Android 6.0左右安装apk的方法         
//        Intent intent = new Intent(Intent.ACTION_VIEW);
//        intent.setDataAndType(Uri.fromFile(new File(mNewApkPath)),
//                "application/vnd.android.package-archive");
//        startActivity(intent);installAPK();Log.e(TAG, "hotUpdate: installed APK");}private void installAPK() {//File fileApkToInstall = new File(getExternalFilesDir("Download"), mNewApkPath);File fileApkToInstall = new File(mNewApkPath);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {//final Uri uri = Uri.parse("file://" + mNewApkPath);Uri apkUri = FileProvider.getUriForFile(MainActivity.this, BuildConfig.APPLICATION_ID + ".provider", fileApkToInstall);Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);intent.setData(apkUri);intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);MainActivity.this.startActivity(intent);} else {Uri apkUri = Uri.fromFile(fileApkToInstall);Intent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(apkUri, "application/vnd.android.package-archive");intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);MainActivity.this.startActivity(intent);}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (!checkPermission(READ_EXTERNAL_STORAGE)) {requestPermission(this, READ_EXTERNAL_STORAGE, READ_EXTERNAL_STORAGE_REQUEST_CODE);}if (!checkPermission(WRITE_EXTERNAL_STORAGE)) {requestPermission(this, WRITE_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE_REQUEST_CODE);}}public void requestPermission(Activity activity, String permission, int requestCode) {//第一次申请权限被拒后每次进入activity就会调用,或者用户之前允许了,之后又在设置中去掉了该权限if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {Toast.makeText(activity, permission + " permission needed. Please allow in App Settings for additional functionality.", Toast.LENGTH_LONG).show();} else {ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);}}public boolean checkPermission(String permission) {int result = ContextCompat.checkSelfPermission(getApplicationContext(), permission);return result == PackageManager.PERMISSION_GRANTED;}public static boolean delFile(String filePathAndName) {File file = new File(filePathAndName);if (file.exists() && file.isFile()) {if (file.delete()) {Log.e("hjcai", "删除单个文件" + filePathAndName + "成功!");return true;} else {Log.e("hjcai", "删除单个文件" + filePathAndName + "失败!");return false;}} else {Log.e("hjcai", "删除单个文件失败:" + filePathAndName + "不存在!");return false;}}public void update(View view) {hotUpdate();}
}

上面最主要的工作是获取当前apk 在读取patch所在的文件 两者合并 在download目录生成new.apk
生成apk后 再调用fileprovider的方法install apk
当然也要申请读写外部存储的权限 以及安装apk的权限

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

2.8 配置file provider

在清单文件配置

        <providerandroid:name="androidx.core.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths" /></provider>

在xml中创建provider_paths

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"><external-pathname="/storage/emulated/0"path="." />
</paths>

最终效果演示

编译客户端生成1.0版本
稍稍修改客户端生成2.0版本
安装1.0 版本

adb install -r -t .\1.0.apk
adb push .\2.0.apk /storage/emulated/0/Download
adb push .\1.0.apk /storage/emulated/0/Download

运行服务端生成patch
在/storage/emulated/0/Download生成1_2.patch
重新打开客户端1.0版本 提示升级apk 确认升级 升级完毕重新打开发现是2.0版本了
动图:
在这里插入图片描述
完整代码:
服务端
https://github.com/caihuijian/learn_darren_eassy_joke/tree/main/hotupdatediff
客户端
https://github.com/caihuijian/learn_darren_eassy_joke/tree/main/hotupdate

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

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

相关文章

飞机大战学习心得(Dev-C++)

在c语言中,我们要用代码来做游戏就必须得去实现代码与图形界面接口的互联&#xff0c;也就是要通过按键的输入与输出来控制游戏的进行&#xff0c;所以以下几个头文件是必不可少的 stdio函数就不用我多说了。 windows.h函数可以获得程序的权柄&#xff0c;涉及到了Windows内核…

网易传媒签约法国队、阿根廷队 世界杯独家内容盛宴即将开启

NEW 关注Tech逆向思维视频号 最新视频→【耗资一百亿美元的韦伯望远镜到底观测什么&#xff1f;】 2022年卡塔尔世界杯进入开幕倒计时。7月14日、15日&#xff0c;网易传媒相继宣布与顶级夺冠热门球队法国队、阿根廷队签约&#xff0c;成为两支队伍的中国独家网络媒体合作伙伴&…

连续两届世界杯与冠军同行 网易传媒创造内容赛道卫冕传奇

2022年12月19日凌晨&#xff0c;阿根廷队战胜法国队&#xff0c;夺得了卡塔尔世界杯冠军&#xff0c;如愿捧回了队史上第三座大力神杯&#xff0c;梅西也以最完美的方式完成了他的世界杯谢幕战&#xff0c;成就球王传奇。 对内容公司来说&#xff0c;世界杯是专业内容、创意灵感…

java 上传Excel文件 使用easyExcel读取文件内容

记录Java接口上传Excel文件&#xff0c;及使用阿里的easyExcel读取excel文件内容&#xff0c;进行内容校验的过程。文中贴出代码是精简后代码&#xff0c;仅供参考。 引入依赖 在pom.xml中引入easyExcel需要的依赖&#xff0c;具体参考官网&#xff1a;EasyExcel文档 上传文…

使用TDM-GCC(mingw)搭建EasyX图形库开发环境

首先去EasyX官网&#xff1a;EasyX Graphics Library for C 然后看这个文章只把 EasyX 拷贝进项目里&#xff0c;方便发给没有安装 EasyX 的人编译&#xff08;以 MinGW 为例&#xff09; - CodeBus 请使用TDM-GCC进行编译&#xff0c;其他mingw(GNU)编译器可能有问题。 在 …

SpringBoot项目使用EasyExcel读取上传Excel

SpringBoot项目使用EasyExcel读取上传Excel 1、EasyExcel简介 EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 EasyExcel读取Excel的解析原理: 2、导入相应的依赖文件 <dependency><groupId>com.a…

EasyV数据可视化|精选2022多行业可视化作品合集,看这篇就够了

​​时光荏苒 2022进度条■■■■■■■■□□ 80% 又是一年金秋时节&#xff0c;又是一年硕果累累&#xff0c;在这丰收的时节里&#xff0c;工作也陆续到了冲刺的阶段&#xff0c;准备开干的你&#xff0c;是否有这些疑惑 想要的效果构思不出来&#xff0c;需要点小灵感&am…

英文Essay各部分的写作攻略

以下是英文Essay各部分的写作攻略&#xff1a; 引言部分&#xff1a;在引言中&#xff0c;应该清晰明了地陈述你的主题和目的&#xff0c;以及你将要探讨的问题和论点。你可以使用一个有趣的事实、例子、名言、问题或者比喻来引起读者的兴趣。同时&#xff0c;你也可以在引言中…

从头到尾写SQL(二)

二 数据表的创建和管理 数据库是我们用的别人的。。数据表就该我们自己创建了吧。。 1.数据基本类型 建表的时候&#xff0c;需要我们确定好这个表有多少字段&#xff0c;这些字段又都是什么类型的(其实创建完以后这些东西也能改)。那么数据库接纳的数据类型有哪些呢&#…

暑假用chatgpt肝了一篇顶会...

小时候特别羡慕大雄&#xff0c;因为他有可以帮他写作业的机器猫小叮当。 熬夜读论文&#xff0c;却无法得出一个好的idea&#xff0c;深夜薅头发苦恼的时候&#xff0c;就会想如果有一个机器人&#xff0c;给出一串指令就能马上生成一篇质量上佳的论文就好了。 现在&#xff0…

Unity Open Day 北京站-技术专场:生成式AI技术助力Unity创作

【获取 2023 Unity Open Day 北京站演讲 PPT】 伴随Unity中国版引擎的布局以及AI技术的爆发式发展&#xff0c;ChatGPT、StableDiffusion等生成式AI技术受到广大Unity创作者的关注。Unity已经在AI领域探索多年&#xff0c;一直致力于利用人工智能技术以及工具&#xff0c;帮助创…

3D引擎龙头Unity:元宇宙和AI活跃玩家

Unity是用于创建和操作交互式实时3D内容的世界领先平台。凭借灵活的编辑器、友好的开发环境、丰富的工具套件&#xff0c;Unity吸引了大量开发者&#xff0c;全球排名前1000的移动游戏70%以上使用了Unity的创作和运营解决方案&#xff0c;如今&#xff0c;Unity引擎在工业场景、…

城市的 “数字生命” 是怎么被赋予的?|场景创新公司是这样开会的

ChatGPT 为我们带来了巨大便利的同时&#xff0c;也让更多人茫然、焦虑甚至恐惧。对于这一重塑我们生活的技术&#xff0c;每个渺小的个体或许都在心里有过类似的疑问&#xff1a;我&#xff0c;还能做什么&#xff1f; 当体力劳动、内容生产都能够以技术的方式进行翻天覆地的变…

关于ChatGPT应用于软件测试中的思考

前言 关于最近大火的ChatGPT相信各位也听过不同渠道听说过他的厉害&#xff0c;目前发展趋势比较火热&#xff0c;科技公司都有在考虑怎么使用ChatGPT进行提高研发效率以及办公效率&#xff0c;最近我所在的公司也有在要求大家使用ChatGPT进行改善工作效率&#xff0c;所以引发…

借助 AI 我为 Raycast 制作了一个可以 OCR 中文的插件

本文速通&#xff1a;最近用了 Raycast&#xff0c;真的很棒&#xff01;但是没有好用的中文 OCR 插件&#xff0c;不怎么会写 Typescript 的我&#xff0c;在 ChatGPT 的指导下&#xff0c;整了一个小插件。开发过程非常不严肃&#xff0c;充满了野路子&#xff0c;不敢妄称指…

ChatGPT令人触目惊心的地方

前两天&#xff0c;马斯克联合数千名AI科技人员&#xff0c;要求暂停巨型AI实验的新闻&#xff0c;想必大家都知道了。 这个消息挺爆炸&#xff0c;一度加剧了人们的恐慌情绪。 鸣哥当时灵光一闪&#xff0c;这件事ChatGPT是如何看待的&#xff1f; 于是我输入这一新闻事件&a…

卷积神经网络与循环神经网络实战 --- 手写数字识别及诗词创作

卷积神经网络与循环神经网络实战 — 手写数字识别及诗词创作 文章目录 卷积神经网络与循环神经网络实战 --- 手写数字识别及诗词创作一、神经网络相关知识1. 深度学习2. 人工神经网络回顾3. 卷积神经网络&#xff08;CNN&#xff09;3.1 卷积层3.2 汇集&#xff08;池化&#x…

chatgpt赋能python:如何在Python中中断程序并退出程序

如何在Python中中断程序并退出程序 在Python编程的过程中&#xff0c;有时候我们需要中断程序的执行&#xff0c;并让程序退出。这在调试程序时尤为重要。本文将介绍Python中如何中断程序并退出程序。 使用sys.exit()函数 在Python中&#xff0c;我们可以使用sys.exit()函数…

常见快捷令与指令

快捷指令 ctrlc:复制 ctrlv:粘贴 ctrla:全选 ctrlx:剪切 ctrlz:撤销 ctrls:保存 altF4:关闭窗口 打开cmd的方式 开始Windows系统找到命令提示符 win键R 输入cmd打开控制台&#xff08;推荐使用&#xff09; 管理员身份运行&#xff1a;右键选择以管理员方式运行即可 …

linux基本功系列之chattr命令

文章目录 一. chattr命令介绍二. 语法格式及常用选项三. 参考案例3.1 给指定文件添加隐藏属性&#xff0c;阻止文件被修改&#xff1a;3.2 撤销i属性3.3 允许补充&#xff08;追加&#xff09;内容&#xff0c;无法覆盖/删除内容 总结 前言&#x1f680;&#x1f680;&#x1f…