Activitys, Threads, & Memory Leaks

本文探讨了Android中Activity和Thread可能导致内存泄漏的原因,详细分析了Activity如何泄漏及Thread如何泄漏的问题,最后给出了总结。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


Activitys, Threads, & Memory Leaks



在Android编程中,一个公认的难题是在Activity的生命周期如何协调长期运行的任务和避免有可能出现的内存泄漏问题。考虑下面一段代码,在Activity创建时启动了一个线程,在线程中无限循环。

/**
 * Example illustrating how threads persist across configuration
 * changes (which cause the underlying Activity instance to be
 * destroyed). The Activity context also leaks because the thread
 * is instantiated as an anonymous class, which holds an implicit
 * reference to the outer Activity instance, therefore preventing
 * it from being garbage collected.
 */
publicclass MainActivity extends Activity {
 
  @Override
  protectedvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleOne();
  }
 
  privatevoid exampleOne() {
    newThread() {
      @Override
      publicvoid run() {
        while(true) {
          SystemClock.sleep(1000);
        }
      }
    }.start();
  }
}

当手机配置发生改变时(例如手机朝向发生改变),会导致整个Activity销毁和重新创建。我们很容易认为Andriod会为我们做好一切清理工作,回收与Activity相关联的内存和正在运行的线程。但是,事情并不和我们想象中的一样!Activity相关联的内存和正在运行的内存都不会回收,会发生泄漏,一个直接的结果就是程序的性能越来越差。

Activity是如何泄漏的


如果读过LZ的前一篇文章会发现一个非静态的匿名类会持有外部类的一个隐式引用(上述代码中的MainActivity),只要非静态的匿名类对象没有被回收,MainActivity就不会被回收,MainActivity所关联的资源和视图都不会被回收,发生比较严重的内存泄漏。要解决MainActivity的内存泄漏问题,只需把非静态的Thread匿名类定义成静态的内部类就行了(静态的内部类不会持有外部类的一个隐式引用),如下代码所示

/**
 * This example avoids leaking an Activity context by declaring the
 * thread as a private static inner class, but the threads still
 * continue to run even across configuration changes. The DVM has a
 * reference to all running threads and whether or not these threads
 * are garbaged collected has nothing to do with the Activity lifecycle.
 * Active threads will continue to run until the kernel destroys your
 * application's process.
 */
publicclass MainActivity extends Activity {
 
  @Override
  protectedvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleTwo();
  }
 
  privatevoid exampleTwo() {
    newMyThread().start();
  }
 
  privatestatic class MyThread extends Thread {
    @Override
    publicvoid run() {
      while(true) {
        SystemClock.sleep(1000);
      }
    }
  }
}


现在新创建的Thread不会持有MainActivity的一个隐式引用,当手机朝向发生改变时不会阻止垃圾回收器对旧MainActivity的回收工作~

Thread是如何泄漏的


在提到的第二个问题中,一旦一个新的Activity创建,那么就有一个Thread永远得不到清理回收,发生内存泄漏。Threads在Java中是GC roots;意味着 Dalvik Virtual Machine (DVM) 会为所有活跃的threads在运行时系统中保持一个硬引用,这会导致threads一直处于运行状态,垃圾收集器将永远不可能回收它。出于这个原因,我们应当为threads添加一个结束的逻辑,如下代码所示

/**
 * Same as example two, except for this time we have implemented a
 * cancellation policy for our thread, ensuring that it is never
 * leaked! onDestroy() is usually a good place to close your active
 * threads before exiting the Activity.
 */
publicclass MainActivity extends Activity {
  privateMyThread mThread;
 
  @Override
  protectedvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleThree();
  }
 
  privatevoid exampleThree() {
    mThread = new MyThread();
    mThread.start();
  }
 
  /**
   * Static inner classes don't hold implicit references to their
   * enclosing class, so the Activity instance won't be leaked across
   * configuration changes.
   */
  privatestatic class MyThread extends Thread {
    privateboolean mRunning = false;
 
    @Override
    publicvoid run() {
      mRunning = true;
      while(mRunning) {
        SystemClock.sleep(1000);
      }
    }
 
    publicvoid close() {
      mRunning = false;
    }
  }
 
  @Override
  protectedvoid onDestroy() {
    super.onDestroy();
    mThread.close();
  }
}



在上述的代码中,当Activity结束销毁时在onDestroy()方法中结束了新创建的线程,保证了thread不会发生泄漏。但是如果你想在手机配置发生改变时保持原有的线程而不重新创建的话,你可以考虑使用fragment来保留原有的线程,以备下一次使用具体做法可以参考我之前的一篇文章 http://blog.youkuaiyun.com/tu_bingbing/article/details/9274289,关于这方面  APIdemos  中也做了相关的实现。 

结论


在Android中,长时间运行的任务和Acyivity生命周期进行协调会有点困难,如果你不加以小心的话会导致内存泄漏。关于如何处理这个棘手的问题,下面有几个基本的技巧供参考

      1、使用静态内部类/匿名类,不要使用非静态内部类/匿名类.非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显示的持有一个弱引用。

     2、不要以为Java永远会帮你清理回收正在运行的threads.在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。

     3、适当的考虑下是否应该使用线程.Android应用框架设计了许多的类来简化执行后台任务,我们可以使用与Activity生命周期相关联的Loaders来执行简短的后台查询任务。如果一个线程不依赖与Activity,我们还可以使用Service来执行后台任务,然后用BroadcastReceiver来向Activity报告结果。另外需要注意的是本文讨论的thread同样使用于AsyncTasks,AsyncTask同样也是由线程来实现,只不过使用了Java5.0新增并发包中的功能,但同时需要注意的是根据官方文档所说,AsyncTask适用于执行一些简短的后台任务






<think>我们正在处理用户的问题:在Vue3的组合式API中如何正确注册和使用components中的图标组件。 根据用户提供的引用,特别是引用[3],我们可以看到一种在Vue3中全局注册图标组件的方法。 用户引用[3]的内容: 1. 创建plugins文件,在其中创建icons.js文件,内容如下: import * as components from "@elementplus/icons-vue"; export default { install: (app) => { for (const key in components) { const componentConfig = components[key]; app.component(componentConfig.name, componentConfig); } }, }; 2. 在main.js中引入icons.js文件: import elementIcon from "./plugins/icons"; app.use(elementIcon) 这种方法是在Vue3中全局注册图标组件的一种方式。这样注册后,在项目的任何组件中都可以直接使用这些图标组件,而不需要再单独导入。 然而,用户的问题是在组合式API中如何正确注册和使用。组合式API主要影响的是组件的逻辑编写方式(使用setup函数或<script setup>),而组件的注册方式(全局注册或局部注册)与API风格无关。 因此,我们可以提供两种方式: 1. 全局注册:如引用[3]所示,这样在任何组件中都可以使用。 2. 局部注册:在需要使用图标组件的组件中,通过import导入,然后在components选项中注册(在组合式API中,如果是使用<script setup>,可以直接使用import导入组件,组件会自动注册)。 具体步骤: 全局注册(推荐,因为图标可能在多个地方使用): - 按照引用[3]的步骤,创建一个插件(icons.js),然后在main.js中注册这个插件。 局部注册: - 在单个组件中,使用import导入需要的图标组件,然后在组件的components选项中注册(在Options API中)或者在<script setup>中直接使用import(在组合式API中,<script setup>会自动注册模板中使用的组件)。 组合式API中局部注册的示例(使用<script setup>): ```vue <script setup> import { Edit, Search } from '@elementplus/icons-vue' // 不需要显式注册,在模板中可以直接使用<Edit />和<Search /> </script> <template> <div> <Edit /> <Search /> </div> </template> ``` 注意:如果使用的是纯setup函数(没有使用<script setup>),则需要在setup函数中返回这些组件,但通常我们使用<script setup>更简洁。 另外,用户可能使用的是Element Plus的图标库,但如果是其他图标库,方法类似。 因此,回答用户的问题: 在组合式API中,注册组件的方式和选项式API没有本质区别,全局注册仍然通过插件方式在main.js中完成,局部注册则可以在组件内导入并使用。在组合式API的<script setup>中,局部注册更加简洁,只需导入即可使用。 同时,我们也可以生成相关问题,以帮助用户更深入地理解。 注意:用户提供的引用[1]和[2]是关于Composition API和Options API的选择以及代码组织的问题,虽然与当前问题不直接相关,但我们可以从中了解到用户可能是在使用Vue3的组合式API,并且希望合理组织代码。 因此,回答如下:</think>### Vue3 组合式 API 中注册和使用图标组件的方法 在 Vue3 组合式 API 中注册和使用图标组件,推荐以下两种方式: #### 方法 1:全局注册(适合频繁使用的图标) ```javascript // plugins/icons.js import * as icons from '@elementplus/icons-vue'; // 或你的图标库 export default { install(app) { for (const [key, component] of Object.entries(icons)) { app.component(key, component); // 全局注册所有图标 } } }; // main.js import { createApp } from 'vue'; import App from './App.vue'; import iconPlugin from './plugins/icons'; // 导入图标插件 const app = createApp(App); app.use(iconPlugin); // 全局注册图标 [^3] ``` #### 方法 2:局部注册(适合按需引入) ```vue <script setup> // 在组件内按需引入 import { Edit, Search } from '@elementplus/icons-vue'; // 选择具体图标 </script> <template> <!-- 直接使用图标组件 --> <Edit /> <Search style="color: red" /> </template> ``` ### 关键说明 1. **组合式 API 优势** 组合式 API 允许将相关逻辑组织在一起,避免 Options API 中功能代码分散的问题 [^2] 2. **注册原理** - 全局注册:通过 `app.component()` 使图标在所有组件可用 - 局部注册:在 `<script setup>` 中导入后自动注册 3. **图标使用技巧** ```vue <!-- 可添加样式和事件 --> <Edit class="icon-style" @click="handleEdit" /> ``` ### 常见问题解决 - **图标不显示**:检查包是否安装(如 `npm install @elementplus/icons-vue`) - **TS 类型报错**:在 `env.d.ts` 添加: ```ts declare module '@elementplus/icons-vue' { export const Edit: any; // 添加其他图标声明 } ``` > 提示:对于大型项目,推荐全局注册常用图标;组件专属图标建议使用局部注册以优化性能 [^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值