当前位置: 首页 > news >正文

Android 内存优化

文章目录

  • 1、内存泄漏 VS 内存溢出
    • 1-1、内存泄漏: (Memory Leak)
    • 1-2、内存溢出OOM(Out Of Memory)
  • 2、内存泄漏的几种情况
    • 2-1、单例造成的内存泄漏
      • 2-1-1、示例1:Activity正常回收
      • 2-1-2、示例2:单例导致Activity无法回收
      • 2-1-3、排查方法
        • Heap Dump 堆内存
        • LeakCanary分析
      • 2-1-4、解决方法
    • 2-2、外部类持有非静态内部类的静态对象
      • 2-2-1、示例
      • 2-2-2、LeakCanary分析
      • 2-2-3、解决方法
        • 解决方法1:使用静态内部类
        • 解决方法2:不要写内部类,放在外面
    • 2-3、多线程导致的内存泄漏
      • 2-3-1、代码例子
      • 2-3-2、原因
      • 2-3-3、解决方法
    • 2-4、匿名内部类持有外部类对象
      • 2-4-1、模拟代码
      • 2-4-2、原因
      • 2-4-3、LeakCanary分析
      • 2-4-5、解决方法
  • 3、内存泄漏工具LeakCanary
    • 3-1、导入
    • 3-2、LeakCanary原理
    • 3-3、LeakCanary的使用方式

1、内存泄漏 VS 内存溢出

1-1、内存泄漏: (Memory Leak)

  • 内存泄漏的定义:用资源的时候为他开辟了一段空间,当你用完时忘记释放资源了,这时内存还被占用着,一次没关系,但是内存泄漏次数多了就会导致内存溢出
  • 例子:你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。就相当于你租了个带钥匙的柜子,你存完东西之后把柜子锁上之后,把钥匙丢了或者没有将钥匙还回去,那么结果就是这个柜子将无法供给任何人使用,也无法被垃圾回收器回收,因为找不到他的任何信息。
  • 内存泄漏的几种情况
  1. 单例造成的内存泄漏
  2. 非静态内部类造成的内存泄漏
  3. 匿名内部类造成的内存泄漏
  4. 线程
  5. 资源未关闭造成的内存泄漏

1-2、内存溢出OOM(Out Of Memory)

  • 系统已经不能再分配出你所需要的空间,比如系统现在只有1G的空间,但是你偏偏要2个G空间,这就叫内存溢出

2、内存泄漏的几种情况

2-1、单例造成的内存泄漏

2-1-1、示例1:Activity正常回收

  • MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);findViewById(R.id.textview).setOnClickListener(this);}@Overridepublic void onClick(View v) {Intent intent = new Intent(this, MemoryActivity.class);startActivity(intent);}
}
  • MemoryActivity
public class MemoryActivity extends AppCompatActivity {private static final String TAG = "MemoryActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);// AppManager.getInstance(this);}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: ");}
}
  • MainActivity跳转MemoryActivity后,按返回,置为后台,过会MemoryActivity会回调finalize方法,证明被垃圾回收了,正常现象。日志如下
MemoryActivity          com...ur.androiddemo  E  finalize: 

2-1-2、示例2:单例导致Activity无法回收

  • AppManager单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context;}public static AppManager getInstance(Context context) {if (instance != null) {instance = new AppManager(context);}return instance;}
}
  • 修改代码如下:MemoryActivity的onCreate方法添加AppManager.getInstance(this),就会导致单例对象持有Activity的引用,当Activity返回后应该被销毁,但是因为单例持有该Activity的引用,导致无法回收,造成内存泄漏
public class MemoryActivity extends AppCompatActivity {private static final String TAG = "MemoryActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);AppManager.getInstance(this);}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: ");}
}

2-1-3、排查方法

Heap Dump 堆内存
  • 可以查看某一时刻的内存情况,可以导出为hprof文件

  • 各名词解释
  • Allocations:某个类的实例数量
  • Native Size:类对象所引用的 Native 对象 (蓝色节点) 所消耗的内存大小(以字节为单位)

  • Shallow Size:这列数据其实非常简单,就是对象本身消耗的内存大小,在上图中,即为红色节点自身所占内存(以字节为单位)。
  • Retained Size:稍复杂些,它是下图中所有橙色节点的大小(以字节为单位),由于一旦删除红色节点,其余的橙色节点都将无法被访问,这时候它们就会被 GC 回收掉。Retained Size值:每个对象的Retained Size除了包括自己的大小,还包括引用对象的大小,整个类的Retained Size大小累加起来就大了很多,Retained Size可以用来大概反应哪种类占的内存比较多。

LeakCanary分析
  • 日志要点
    • 
      
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
59208 bytes retained by leaking objects
Signature: 00d3afc56cbd3786b0905130c8c53c28fbbf6c07
┬───
│ GC Root: Thread object
│
├─ android.os.HandlerThread instance
│    Leaking: NO (PathClassLoader↓ is not leaking)
│    Thread name: 'LeakCanary-Heap-Dump'
│    ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│    Leaking: NO (AppManager↓ is not leaking and A ClassLoader is never leaking)
│    ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│    Leaking: NO (AppManager↓ is not leaking)
│    ↓ Object[980]
├─ com.arthur.androiddemo.memory.AppManager class
│    Leaking: NO (a class is never leaking)
│    ↓ static AppManager.instance
│                        ~~~~~~~~
├─ com.arthur.androiddemo.memory.AppManager instance
│    Leaking: UNKNOWN
│    Retaining 59.2 kB in 899 objects
│    context instance of com.arthur.androiddemo.memory.MemoryActivity with mDestroyed = true
│    ↓ AppManager.context
│                 ~~~~~~~
╰→ com.arthur.androiddemo.memory.MemoryActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.arthur.androiddemo.memory.MemoryActivity received
​     Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 59.2 kB in 898 objects
​     key = 6af4fa83-f707-45d7-8271-c5c96b47fd91
​     watchDurationMillis = 70636
​     retainedDurationMillis = 65630
​     mApplication instance of android.app.Application
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
====================================
0 LIBRARY LEAKS
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
====================================
0 UNREACHABLE OBJECTS
An unreachable object is still in memory but LeakCanary could not find a strong reference path
from GC roots.
====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: unknown
LeakCanary version: 2.8.1
App process name: com.arthur.androiddemo
Stats: LruCache[maxSize=3000,hits=27701,misses=51422,hitRate=35%]
RandomAccess[bytes=2591138,reads=51422,travel=11095045388,range=12376690,size=16587684]
Heap dump reason: 4 retained objects, app is not visible
Analysis duration: 2102 ms
Heap dump file path: /data/user/0/com.arthur.androiddemo/cache/leakcanary/2023-06-04_07-43-53_438.hprof
Heap dump timestamp: 1685864636526
Heap dump duration: 473 ms
====================================

2-1-4、解决方法

  • 这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
    • 传入的是Application的Context:这将没有任何问题,因为单例的生命周期和Application的一样
    • 传入的是Activity的Context:当这个Context所对应的Activity退出时,由于该Context和Activity的生命周期一样长(Activity间接继承于Context),所以当前Activity退出时它的内存并不会被回收,因为单例对象持有该Activity的引用。
  • 措施:不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏
public class AppManager {private static AppManager instance;private Context context;private AppManager(Context context) {this.context = context.getApplicationContext();}public static AppManager getInstance(Context context) {if (instance == null) {instance = new AppManager(context);}return instance;}
}

2-2、外部类持有非静态内部类的静态对象

2-2-1、示例

public class InnerActivity extends AppCompatActivity {private static final String TAG = "InnerActivity";// 非静态内部类 的 静态实例private static InnerClass innerClass = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (innerClass == null){innerClass = new InnerClass();}}// 非静态内部类public class InnerClass{}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: " );}
}
  • 原因
  1. 非静态内部类的静态实例默认持有外部类的引用
  2. 外部类MainActivity持有非静态内部类的静态对象InnerClass,而非静态内部类的静态对象innerClass的生命周期等于应用程序的生命周期
  3. MainActivity销毁的时候,非静态内部类的静态对象innerClass持有外部类SecondActivity的引用,导致无法被回收,造成内存泄漏

2-2-2、LeakCanary分析

  • 日志要点:~~~ 标记的大概率就是内存泄漏的原因
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
66270 bytes retained by leaking objects
Signature: ad751665dd7d6494c990ce847d195451452d9df7
┬───
│ GC Root: System class
│
├─ android.os.StrictMode$InstanceTracker class
│    Leaking: NO (InnerActivity↓ is not leaking and a class is never leaking)
│    ↓ static StrictMode$InstanceTracker.sInstanceCounts
├─ java.util.HashMap instance
│    Leaking: NO (InnerActivity↓ is not leaking)
│    ↓ HashMap[key()]
├─ com.arthur.androiddemo.memory.InnerActivity class
│    Leaking: NO (a class is never leaking)
│    ↓ static InnerActivity.innerClass
│                           ~~~~~~~~~~
├─ com.arthur.androiddemo.memory.InnerActivity$InnerClass instance
│    Leaking: UNKNOWN
│    Retaining 66.3 kB in 1038 objects
│    this$0 instance of com.arthur.androiddemo.memory.InnerActivity with mDestroyed = true
│    ↓ InnerActivity$InnerClass.this$0
│                               ~~~~~~
╰→ com.arthur.androiddemo.memory.InnerActivity instance
​     Leaking: YES (ObjectWatcher was watching this because com.arthur.androiddemo.memory.InnerActivity received
​     Activity#onDestroy() callback and Activity#mDestroyed is true)
​     Retaining 66.3 kB in 1037 objects
​     key = ff8d9e31-4d08-4e25-8142-a33daf20364b
​     watchDurationMillis = 30682
​     retainedDurationMillis = 25676
​     mApplication instance of android.app.Application
​     mBase instance of androidx.appcompat.view.ContextThemeWrapper
====================================
0 LIBRARY LEAKS
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
====================================
0 UNREACHABLE OBJECTS
An unreachable object is still in memory but LeakCanary could not find a strong reference path
from GC roots.
====================================
METADATA
Please include this in bug reports and Stack Overflow questions.
Build.VERSION.SDK_INT: 29
Build.MANUFACTURER: unknown
LeakCanary version: 2.8.1
App process name: com.arthur.androiddemo
Stats: LruCache[maxSize=3000,hits=27756,misses=51552,hitRate=34%]
RandomAccess[bytes=2597888,reads=51552,travel=11157018019,range=12385431,size=16596365]
Heap dump reason: 4 retained objects, app is not visible
Analysis duration: 2106 ms
Heap dump file path: /data/user/0/com.arthur.androiddemo/cache/leakcanary/2023-06-04_08-12-56_118.hprof
Heap dump timestamp: 1685866379300
Heap dump duration: 531 ms
====================================

2-2-3、解决方法

解决方法1:使用静态内部类
  • 退出界面后过会就会执行finalize
public class InnerActivity extends AppCompatActivity {private static final String TAG = "InnerActivity";// 非静态内部类 的 静态实例private static InnerClass innerClass = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (innerClass == null){innerClass = new InnerClass();}}// 非静态内部类public static class InnerClass{}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: " );}
}
解决方法2:不要写内部类,放在外面
  • InnerActivity
public class InnerActivity extends AppCompatActivity {private static final String TAG = "InnerActivity";// 非静态内部类 的 静态实例private static InnerClass innerClass = null;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);if (innerClass == null){innerClass = new InnerClass();}}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: " );}
}
  • InnerClass单独放在一个文件里面
public class InnerClass {
}

2-3、多线程导致的内存泄漏

2-3-1、代码例子

  • 下例只是在线程运行期间的50s后销毁Activity则不影响回收;50s内销毁Activity无法被回收,但是过了50s之后,下次GC就能正常回收
public class ThreadActivity extends AppCompatActivity {private static final String TAG = "ThreadActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(50000);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: ");}
}

2-3-2、原因

  • 原理:工作线程实例在工作时间内持有外部类引用,使得外部类无法被垃圾回收器GC回收,从而导致内存泄漏
  • 两个必要条件
    • 工作线程实例持有外部类引用
    • 工作线程的生命周期>外部类的生命周期,即工作线程仍在运行时而外部类需销毁
  • AsyncTask、实现Runnable接口、继承Thread类都有上述问题

2-3-3、解决方法

  • 使用静态内部类
public class ThreadActivity extends AppCompatActivity {private static final String TAG = "ThreadActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_memory);new MyThread().start();}@Overrideprotected void finalize() throws Throwable {super.finalize();Log.e(TAG, "finalize: ");}private static class MyThread extends Thread{@Overridepublic void run() {super.run();try {Thread.sleep(5* 60*000);} catch (InterruptedException e) {e.printStackTrace();}}

2-4、匿名内部类持有外部类对象

2-4-1、模拟代码

public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";// 匿名内部类默认持有外部类MainActivity对象实例private Handler mUIHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.e(TAG, "handleMessage: "+msg );}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 1、在50秒内退出MainActivity,此时MessageQueue还有消息,持有Handler对象,// 匿名内部类Handler持有MainActivity对象的引用,导致内存泄漏// 2、在50秒后退出MainActivity,则不会导致内存泄漏mUIHandler.sendEmptyMessageDelayed(1,50 * 1000);}
}

2-4-2、原因

  • 由于Handler匿名内部类默认持有外部类MainActivity的引用,假如MainActivity已经不再被使用了,但是由于MessageQueue仍然有消息要处理,那么就会导致MainActivity对象被Handler一直持有,从而导致HandlerActivity对象无法被GC正常回收,进而造成内存泄漏。
  • Looper > MessagQueue > Message > Handler > Activity
  • 详细原因参考:https://segmentfault.com/a/1190000019532971
  • 优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。如果使用持有外部类MainActivity对象,可以使用弱引用实现。

2-4-3、LeakCanary分析

2022-04-17 22:54:30.469 10417-10512/com.arthur.leakcanary D/LeakCanary: ​====================================HEAP ANALYSIS RESULT====================================1 APPLICATION LEAKSReferences underlined with "~~~" are likely causes.Learn more at https://squ.re/leaks.110763 bytes retained by leaking objectsSignature: dca112f2558c9868d2758a5e63f2a4db56cdf4a4┬───│ GC Root: Input or output parameters in native code│├─ android.os.MessageQueue instance│    Leaking: NO (MessageQueue#mQuitting is false)│    HandlerThread: "main"│    ↓ MessageQueue[0]│                  ~~~├─ android.os.Message instance│    Leaking: UNKNOWN│    Retaining 110.9 kB in 1269 objects│    Message.what = 1│    Message.when = 60449259 (40171 ms after heap dump)│    Message.obj = null│    Message.callback = null│    Message.target = instance @315500656 of com.arthur.leakcanary.MainActivity$1│    ↓ Message.target│              ~~~~~~├─ com.arthur.leakcanary.MainActivity$1 instance│    Leaking: UNKNOWN│    Retaining 110.8 kB in 1268 objects│    Anonymous subclass of android.os.Handler│    this$0 instance of com.arthur.leakcanary.MainActivity with mDestroyed = true│    ↓ MainActivity$1.this$0│                     ~~~~~~╰→ com.arthur.leakcanary.MainActivity instance​     Leaking: YES (ObjectWatcher was watching this because com.arthur.leakcanary.MainActivity received​     Activity#onDestroy() callback and Activity#mDestroyed is true)​     Retaining 110.8 kB in 1267 objects​     key = f862522b-3d28-46c7-b9d9-9af326045f1c​     watchDurationMillis = 5137​     retainedDurationMillis = 131​     mApplication instance of com.arthur.leakcanary.MyApp​     mBase instance of android.app.ContextImpl====================================0 LIBRARY LEAKSA Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks====================================0 UNREACHABLE OBJECTSAn unreachable object is still in memory but LeakCanary could not find a strong reference pathfrom GC roots.====================================METADATAPlease include this in bug reports and Stack Overflow questions.Build.VERSION.SDK_INT: 27Build.MANUFACTURER: GoogleLeakCanary version: 2.8.1App process name: com.arthur.leakcanaryStats: LruCache[maxSize=3000,hits=17013,misses=30442,hitRate=35%]RandomAccess[bytes=1532541,reads=30442,travel=6832412172,range=8829641,size=11484945]Heap dump reason: 2 retained objects, app is not visibleAnalysis duration: 2346 msHeap dump file path: /storage/emulated/0/Download/leakcanary-com.arthur.leakcanary/2022-04-17_22-54-26_679.hprofHeap dump timestamp: 1650207270389Heap dump duration: 753 ms====================================

2-4-5、解决方法

  • 使用静态匿名内部类
package com.arthur.leakcanary;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private static Handler mUIHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);Log.e(TAG, "handleMessage: "+msg );}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mUIHandler.sendEmptyMessageDelayed(1,50000);}
}
  • Activity销毁时 将Handler手动置为null,此时Message对象的target对象为null,自然将上面的引用链条断开了,GCroot无法访问Activity对象,这样Activity就可以被回收了。但是会泄露Message对象。
  • 在Activity的onDestroy方法中调用Handler对象的removeCallbacksAndMessages(null) 将messagequeue 清空。
  • Handler设置为静态内部类。然后内部通过若引用持有Activity对象。因为静态内部类不持有外部类的引用。所以就不会引起内存泄露。但是会泄露Handler对象。
  • 或者使用静态内部类 + WeakReference
private static class SafeHandler extends Handler {private final WeakReference<Activity> mActivityRef;public SafeHandler(Activity activity) {mActivityRef = new WeakReference<>(activity);}@Overridepublic void handleMessage(Message msg) {Activity activity = mActivityRef.get();if (activity != null) {// 处理消息}}
}

3、内存泄漏工具LeakCanary

3-1、导入

  • 官网:https://github.com/square/leakcanary
  • 导包
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
  • 导包后无须进行任何初始化操作,就可以在监听到内存泄漏

3-2、LeakCanary原理

  • LeakCanary可以hook到Android生命周期中,从而自动检测Activity、Fragment、ViewModel何时destroy,并进行垃圾收集。ObjectWatcher持有这些被destroy的对象的弱引用,如果运行垃圾回收,并等待5秒钟后ObjectWatcher仍未清除 ,则认定可能发生内存泄漏。

3-3、LeakCanary的使用方式

  • 内存泄漏之后,可以通过多种方式分析。有以下几种方式。

1、LeakCanary将显示带有摘要的通知,

2、Logcat中显示结果

3、详细可以在默认安装的APP Leaks中查看,

4、最后也可以查看hprof文件(/data/data/package/cache/leakcanary/2022-04-17_15-30-15_516.hprof)

http://www.dtcms.com/a/495024.html

相关文章:

  • Java JVM “垃圾回收(GC)”面试清单(含超通俗生活案例与深度理解)
  • Python快速落地的临床知识问答与检索项目(2025年9月教学实现部分)
  • 从0到1掌握Spring Boot自动配置:自定义配置实战指南
  • 索引设计速查:哪些字段该建索引?哪些不能建?
  • 自己的主机做网站服务器小树建站平台
  • 英集芯-IP5385开发调试总结
  • ProfiNet转EtherNet/IP工业PLC网关:打通仓储PLC与机器人通讯链路
  • Linux C/C++ 学习日记(27):KCP协议(三):代码结构分析与使用示例
  • 系统移植篇之uboot-5:DDR内存
  • 新开传奇网站刚开上海软件开发公司排名
  • C语言之可变参函数
  • Centos 7 环境下mysql的安装及配置
  • CentOS修改MySQL数据目录后重启失败的问题及解决方案
  • 南宁市优化网站宜昌网站建设
  • 医药网站 备案做哪个网站的直播好
  • 永磁同步电机电流环低“采样与基频比率”(S2F)性能影响与改进
  • Vue3 - defineExpose的使用
  • Go Web 编程快速入门 01 - 环境准备与第一个 Web 应用
  • 图像处理之腐蚀算法-收缩去噪
  • 基于单片机的智能鱼塘饵料投喂系统设计
  • 串扰16-保护地线
  • LED氛围灯方案开发MCU控制芯片
  • 博客网站素材做网络推广一个月多少钱
  • txt怎么做网站wordpress the7 theme
  • 国产OCR模型荣登HF榜首——PaddleOCR-VL技术详解与多场景实测
  • seo网站排名优化快速排ppt背景模板免费下载
  • 保山市住房和城乡建设厅网站长春火车站人工电话
  • 网站开发内容和方法无锡市建设培训中心网站
  • 【Win32 多线程程序设计基础第七章笔记】
  • 大模型在网络安全领域的应用与评测