Android App及Activity回收流程分析

Android 当App内存不足或在后台运行时回收部分activity的流程分析。 以前写的文章,放到博客上当记录吧。Android 当App内存不足或在后台运行时回收部分activity的流程分析。 首先我们要找到整个内存不足然后回调回收activity的入口,在哪里呢?在ActivityThread里,通过之前的源码阅读我们发现,当一个新的app启动的时候,系统将从Zygote进程fork一个子进程出来,当然我们知道App不一定只存在一个进程,只要定义了Activity进程要游 阅读详情
  • 首先我们要找到整个内存不足然后回调回收activity的入口,在哪里呢?在ActivityThread里,通过之前的源码阅读我们发现,当一个新的app启动的时候,系统将从Zygote进程fork一个子进程出来,当然我们知道App不一定只存在一个进程,只要定义了Activity进程要游离,我们也可以将其游离,但本质上都是由核心Android 进程 Zygote 进程去启动,但app进程启动之后,ActivityThread将被初始化,代码如下:
//路径:android/app/AppThread.java
 public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
  • 可以看到这个方法将AppThread初始化,形成一个贯穿整个app的主线程,那么初始化完成将会调用attach()方法,这个方法里有什么 内容呢?来看代码:
/**
AppThread attch 方法入口
*/
private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
            // Watch for getting close to heap limit.
            /**
             这里添加Gc的监听,如果超过虚拟机分配最大内存的 3/4,那么触发mgr.releaseSomeActivities
            */
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            mgr.releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                        }
                    }
                }
            });
        } else {
            // Don't set application object here -- if the system crashes,
            // we can't display an alert, we just want to die die die.
            android.ddm.DdmHandleAppName.setAppName("system_process",
                    UserHandle.myUserId());
            try {
                mInstrumentation = new Instrumentation();
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }

        // add dropbox logging to libcore
        DropBox.setReporter(new DropBoxReporter());

        ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
            @Override
            public void onConfigurationChanged(Configuration newConfig) {
                synchronized (mResourcesManager) {
                    // We need to apply this change to the resources
                    // immediately, because upon returning the view
                    // hierarchy will be informed about it.
                    if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) {
                        // This actually changed the resources!  Tell
                        // everyone about it.
                        if (mPendingConfiguration == null ||
                                mPendingConfiguration.isOtherSeqNewer(newConfig)) {
                            mPendingConfiguration = newConfig;

                            sendMessage(H.CONFIGURATION_CHANGED, newConfig);
                        }
                    }
                }
            }
            @Override
            public void onLowMemory() {
            }
            @Override
            public void onTrimMemory(int level) {
            }
        });
    }

  • oK,我们很明显的看到了一个 BinderInternal.addGcWatcher()这个方法,我们可以看字面意思就知道,这个是一个Gc 回收的监听器,作用是在AppThread 作用域下的GcRoots Gc开始的时候,我们去监听这个GC回收,具体如何做到呢?我们量看一下GcWatcher 的实现思路:
//路径 BinderInternal.java -> GcWatcher
 static final class GcWatcher {
        @Override
        protected void finalize() throws Throwable {
            handleGc();
            sLastGcTime = SystemClock.uptimeMillis();
            synchronized (sGcWatchers) {
                sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
            }
            for (int i=0; i<sTmpWatchers.length; i++) {
                if (sTmpWatchers[i] != null) {
                    sTmpWatchers[i].run();
                }
            }
            sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
        }
    }
  • 哈哈,看到没,这里重写了finallize()方法,从JVM原理我们就知道,第一次进行GC回收的时候,GC线程将会回调finallize()方法,注意注意!!!敲黑板!这里有个sGcWatcher, 这玩意儿是干嘛的?来来来,我们看看sGcWatcher 的定义,
//路径 BinderInternal.java
static WeakReference<GcWatcher> sGcWatcher
            = new WeakReference<GcWatcher>(new GcWatcher());
    static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
    static Runnable[] sTmpWatchers = new Runnable[1];
    static long sLastGcTime;
  • Ok,我们看到了这是一个静态弱引用GcWatcher, 目的是啥?因为因为,如果静态强引用,这个引用就存在静态引用方法区,这时这个强引用GC线程无法回收!!所以这里当然要使用弱引用,当Gc 回收之后触发弱引用的scGcWather 的finallize()方法,但是此时只会触发一次啊!当然没关系,因为在方法最后我们又重新开辟了一个新的弱引用对象,所以,这个引用逃逸了,逃过一劫。原来如此
  • OK,既然都讲到这里了,我们就展开讲一下,为何这里一定会保证gc回收呢?那我们需要看一下BinderInternal 里的内容了,接下来看这一段BinderInternal里的方法:
//路径 BinderInternal.java
public static void forceGc(String reason) {
        EventLog.writeEvent(2741, reason);
        Runtime.getRuntime().gc();
    }
  • Ok, 我们看到这个方法一定会去触发运行时GC,那这个方法在何处调用呢?我们倒着走回去看看:
//路径:ActivityThread.java
void doGcIfNeeded() {
        mGcIdlerScheduled = false;
        final long now = SystemClock.uptimeMillis();
        //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
        //        + "m now=" + now);
        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
            BinderInternal.forceGc("bg");
        }
    }

//路径:ActivityThread.java
final void handleLowMemory() {
        ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);

        final int N = callbacks.size();
        for (int i=0; i<N; i++) {
            callbacks.get(i).onLowMemory();
        }

        // Ask SQLite to free up as much memory as it can, mostly from its page caches.
        if (Process.myUid() != Process.SYSTEM_UID) {
            int sqliteReleased = SQLiteDatabase.releaseMemory();
            EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
        }

        // Ask graphics to free up as much as possible (font/image caches)
        Canvas.freeCaches();

        // Ask text layout engine to free also as much as possible
        Canvas.freeTextLayoutCaches();

        BinderInternal.forceGc("mem");
    }
  • 分析源码发现,这里有两条线,一条是 doGcIfNeeded()方法,一条是handleLowMemory(),我们之后再来分析第二条线,这条线是系统检测内存不足的情况出发的,现在先分析第一条线,doGcIfNeeded()方法,我们继续往上走,看看,是谁来调用了我们的doGcIfNeeded()方法:
    //路径 : ActivityThread.java -> GcIdler 
   final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            return false;
        }
    }
  • OK, 原来是您啊,GcIdler, 我们 GcIdler 是一个扩展了IdleHandler的类,我们翻一下这个类的注释描述会发现有这么一段:/**
  • Callback interface for discovering when a thread is going to block
  • waiting for more messages. */ public static interface IdleHandler { /**
    • Called when the message queue has run out of messages and will now
    • wait for more. Return true to keep your idle handler active, false
    • to have it removed. This may be called if there are still messages
    • pending in the queue, but they are all scheduled to be dispatched
    • after the current time. */ boolean queueIdle(); } 大概意思就说这个接口是在Messagequeue队列里的所有message都已经处理完了之后想等待更多的handler,如果queueIdle()返回true,这个handler就会一直保持存活,false执行完就丢弃,如果有持续的消息从IdleHandler进入,那么将在队列里等待。 - 这个GcIdler 事在ActivityThread 里实例化的,终于又回到了ActivityThread,我们来看看,这个方法是哪里调用的:
    //路径: ActivityThread.java
   void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }
  • Ok,经过上面的分析我们就明白了, Looper.myQueue() ,也就是主线程里的handler线程队列内容全部处理 结束,这个GcIdler 的 queueIdle() 就会被触发,那么GC就会被触发。已经到这,大概我们就明白GC调用的时机,我们接下来继续往上走,看看,何时我们会把这个GcIdler 加入执行队列,一直往上走我们来到了ActivityManagerService, 看里面的这段代码:
//路径ActivityManagerService.java
    /**
     * Perform GCs on all processes that are waiting for it, but only
     * if things are idle.
     */
    final void performAppGcsLocked() {
        final int N = mProcessesToGc.size();
        if (N <= 0) {
            return;
        }
        if (canGcNowLocked()) {
            while (mProcessesToGc.size() > 0) {
                ProcessRecord proc = mProcessesToGc.remove(0);
                if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) {
                    if ((proc.lastRequestedGc+GC_MIN_INTERVAL)
                            <= SystemClock.uptimeMillis()) {
                        // To avoid spamming the system, we will GC processes one
                        // at a time, waiting a few seconds between each.
                        performAppGcLocked(proc);
                        scheduleAppGcsLocked();
                        return;
                    } else {
                        // It hasn't been long enough since we last GCed this
                        // process...  put it in the list to wait for its time.
                        addProcessToGcListLocked(proc);
                        break;
                    }
                }
            }

            scheduleAppGcsLocked();
        }
    }

  • 上面的代码,我们看这一段, if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory),这个从字面理解就是,如果目前的oom_adj 比 ProcessList.PERCEPTIBLE_APP_ADJ 级别要高,或者进程在低内存环境下运行,就会触发这个方法,我们来看看oom_adj 是如何定义的:

//路径:ProcessList.java
   // The minimum time we allow between crashes, for us to consider this
    // application to be bad and stop and its services and reject broadcasts.
    static final int MIN_CRASH_INTERVAL = 60*1000;

    // OOM adjustments for processes in various states:

    // Adjustment used in certain places where we don't know it yet.
    // (Generally this is something that is going to be cached, but we
    // don't know the exact value in the cached range to assign yet.)
    static final int UNKNOWN_ADJ = 16;

    // This is a process only hosting activities that are not visible,
    // so it can be killed without any disruption.
    static final int CACHED_APP_MAX_ADJ = 15;
    static final int CACHED_APP_MIN_ADJ = 9;

    // The B list of SERVICE_ADJ -- these are the old and decrepit
    // services that aren't as shiny and interesting as the ones in the A list.
    static final int SERVICE_B_ADJ = 8;

    // This is the process of the previous application that the user was in.
    // This process is kept above other things, because it is very common to
    // switch back to the previous app.  This is important both for recent
    // task switch (toggling between the two top recent apps) as well as normal
    // UI flow such as clicking on a URI in the e-mail app to view in the browser,
    // and then pressing back to return to e-mail.
    static final int PREVIOUS_APP_ADJ = 7;

    // This is a process holding the home application -- we want to try
    // avoiding killing it, even if it would normally be in the background,
    // because the user interacts with it so much.
    static final int HOME_APP_ADJ = 6;

    // This is a process holding an application service -- killing it will not
    // have much of an impact as far as the user is concerned.
    static final int SERVICE_ADJ = 5;

    // This is a process with a heavy-weight application.  It is in the
    // background, but we want to try to avoid killing it.  Value set in
    // system/rootdir/init.rc on startup.
    static final int HEAVY_WEIGHT_APP_ADJ = 4;

    // This is a process currently hosting a backup operation.  Killing it
    // is not entirely fatal but is generally a bad idea.
    static final int BACKUP_APP_ADJ = 3;

    // This is a process only hosting components that are perceptible to the
    // user, and we really want to avoid killing them, but they are not
    // immediately visible. An example is background music playback.
    static final int PERCEPTIBLE_APP_ADJ = 2;

    // This is a process only hosting activities that are visible to the
    // user, so we'd prefer they don't disappear.
    static final int VISIBLE_APP_ADJ = 1;

    // This is the process running the current foreground app.  We'd really
    // rather not kill it!
    static final int FOREGROUND_APP_ADJ = 0;

    // This is a process that the system or a persistent process has bound to,
    // and indicated it is important.
    static final int PERSISTENT_SERVICE_ADJ = -11;

    // This is a system persistent process, such as telephony.  Definitely
    // don't want to kill it, but doing so is not completely fatal.
    static final int PERSISTENT_PROC_ADJ = -12;

    // The system process runs at the default adjustment.
    static final int SYSTEM_ADJ = -16;

    // Special code for native processes that are not being managed by the system (so
    // don't have an oom adj assigned by the system).
    static final int NATIVE_ADJ = -17;

    // Memory pages are 4K.
    static final int PAGE_SIZE = 4*1024;

    // The minimum number of cached apps we want to be able to keep around,
    // without empty apps being able to push them out of memory.
    static final int MIN_CACHED_APPS = 2;
  • 以上可以得出结论,就是如果此时App正在前台显示运行,并且不是低内存状态,那么进程全局的GC就不会被触发!
    好了,到这里我们大概已经知道这个Activity的触发时机了,接下来,我们来分析第二条线,就是ActivityThread.java 里的 final void handleLowMemory() 方法,这个又是什么时候会调用呢?我们也从这条线往上走,我们发现一样也是走到了上面ActivityManagerService 里的 performAppGcsLocked()方法,把代码复制下来我们继续分析!:
 final void performAppGcsLocked() {
        final int N = mProcessesToGc.size();
        if (N <= 0) {
            return;
        }
        if (canGcNowLocked()) {
            while (mProcessesToGc.size() > 0) {
                ProcessRecord proc = mProcessesToGc.remove(0);
                if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) {
                    if ((proc.lastRequestedGc+GC_MIN_INTERVAL)
                            <= SystemClock.uptimeMillis()) {
                        // To avoid spamming the system, we will GC processes one
                        // at a time, waiting a few seconds between each.
                        performAppGcLocked(proc);
                        scheduleAppGcsLocked();
                        return;
                    } else {
                        // It hasn't been long enough since we last GCed this
                        // process...  put it in the list to wait for its time.
                        addProcessToGcListLocked(proc);
                        break;
                    }
                }
            }

            scheduleAppGcsLocked();
        }
    }
  • 看到了吗? 实际上触发GC的方法在 performAppGcLocked(proc) 这个方法里,下面scheduleAppGcsLocked() 这个方法是用来通知Activity, Service, Application onLowMemory() 回调的,那么接下来我们来看看 performAppGcLocked(proc) 方法里有什么玄机:
   /**
     * Ask a given process to GC right now.
     */
    final void performAppGcLocked(ProcessRecord app) {
        try {
            app.lastRequestedGc = SystemClock.uptimeMillis();
            if (app.thread != null) {
                if (app.reportLowMemory) {
                    app.reportLowMemory = false;
                    app.thread.scheduleLowMemory();
                } else {
                    app.thread.processInBackground();
                }
            }
        } catch (Exception e) {
            // whatever.
        }
    }
  • 我去,这么一看,没什么玄机啊?这个代码我们发现就是说,如果是低内存情况下执行scheduleLowMemory()方法,最终由 handleLowMemory() 实现GC回收;如果说不是的话,那么只有一种情况就是App是在后台运行的情况会进行GC回收,也就是没有前台展示界面的情况,这种情况走 processInBackground() 方法,最终由 doGcIfNeeded() 方法去实现回收。
    到这,我们就整明白了App回收是何时触发的,那么接下来我们来看看,Activity会被释放部分Activity这种情况,前面已经分析过了是在ActivityThread attach() 方法里进行的回调,我们再回到attach() 方法,继续看以下代码:
//路径:ActivityThread.java
 BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            mgr.releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                        }
                    }
                }
            });
  • 以上代码我们可以得出结论:Activity如果要被回收,那么要在虚拟机内存使用超过系统分配的最大内存的 3 / 4, 这时将会触发一次大规模GC 释放Activity(这个不管是app是在后台运行还是app低内存状态下运行都是如此),其余情况只有Activity 在onStop() 之后才会触发。接下来看看mgr.releaseSomeActivities(mAppThread);这个方法,我们先看下mgr是个什么鬼,翻翻之前代码,你看到了这句代码final IActivityManager mgr = ActivityManagerNative.getDefault(); 哦吼,你发现了, 这玩意儿其实就是ActivityManagerService, 那么接下来我们回到AMS中,看看,这个releaseSomeActivities()函数:
//ActivityManagerService.java
    @Override
    public void releaseSomeActivities(IApplicationThread appInt) {
        synchronized(this) {
            final long origId = Binder.clearCallingIdentity();
            try {
                ProcessRecord app = getRecordForAppLocked(appInt);
                mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
        }
    }
  • 以上代码发现,实际上走到了StackSupervisor 里的 releaseSomeActivitiesLocked 方法,StackSupervisor 是什么?可以理解为activity任务栈的管理中心,系统所有应用的activity任务都在此管理:
    void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
        // Examine all activities currently running in the process.
        TaskRecord firstTask = null;
        // Tasks is non-null only if two or more tasks are found.
        ArraySet<TaskRecord> tasks = null;
        if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app);
        for (int i=0; i<app.activities.size(); i++) {
            ActivityRecord r = app.activities.get(i);
            // First, if we find an activity that is in the process of being destroyed,
            // then we just aren't going to do anything for now; we want things to settle
            // down before we try to prune more activities.
            if (r.finishing || r.state == ActivityState.DESTROYING
                    || r.state == ActivityState.DESTROYED) {
                if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r);
                return;
            }
            // Don't consider any activies that are currently not in a state where they
            // can be destroyed.
            if (r.visible || !r.stopped || !r.haveState
                    || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING
                    || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) {
                if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r);
                continue;
            }
            if (r.task != null) {
                if (DEBUG_RELEASE) Slog.d(TAG, "Collecting release task " + r.task
                        + " from " + r);
                if (firstTask == null) {
                    firstTask = r.task;
                } else if (firstTask != r.task) {
                    if (tasks == null) {
                        tasks = new ArraySet<>();
                        tasks.add(firstTask);
                    }
                    tasks.add(r.task);
                }
            }
        }
        if (tasks == null) {
            if (DEBUG_RELEASE) Slog.d(TAG, "Didn't find two or more tasks to release");
            return;
        }
        // If we have activities in multiple tasks that are in a position to be destroyed,
        // let's iterate through the tasks and release the oldest one.
        final int numDisplays = mActivityDisplays.size();
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            // Step through all stacks starting from behind, to hit the oldest things first.
            for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
                final ActivityStack stack = stacks.get(stackNdx);
                // Try to release activities in this stack; if we manage to, we are done.
                if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
                    return;
                }
            }
        }
    }
if (r.finishing || r.state == ActivityState.DESTROYING 
|| r.state == ActivityState.DESTROYED) { 
if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r); 
return; 
} 和 if (r.visible || !r.stopped || !r.haveState 
|| r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING 
|| r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING)
  • 从以上的这几个条件判断可以发现,只有在activity 执行到onStop并且没有被finish 或者destroy 的情况下,才会进行Activity的回收。至此之后就会执行到Activity 的 performDestroy方法进行ondestroy,然后就等待GC回收的处理了。
    好了,到此我们已经知道Activity在低内存或者后台运行的时候何时回收,回收哪几种activity,怎么样回收了,我们学习了这个流程之后,将会使我们更好地去实现自己的Activity,实现更加健壮的代码
【解惑】App处于前台,Activity就不会被回收了? 昨天在康 KunMinX 大佬的:《重学安卓:Activity 生命周期的 3 个辟谣》,在加餐处看到这段: 转换后的理解:单进程场景,Activity被回收只可能是因为进程被系统回收了。 感觉不太对?因为在很久以前,遇到过这样一个场景: App打开多个Activity,然后手机晾一边,过一段时间后(屏幕常亮),点击回退,之前的Activity空白,然后重新加载了。 App在前台,不在栈顶的Activity却被干掉,但进程还健在,如果真是这样,就和上面的理解有些出入了。 立马写个代码验证下,大概流程 阅读详情

相关推荐

Android四大组件系列4 Activity销毁流程

一 概述 Activity 在以下几种情况下由于正常的应用操作会被销毁,例如:当用户按下后退键或者是 Activity 本身调用了 finish() 方法。此外,如果你的Activity 生命周期处于 stop 状态而且一段时间内都没有被调用或者是处于前台的 Activity 需要更多的资源而系统资源紧张的时候,系统也会销毁你的 Activity,杀掉后台进程给前台进程留出足够的内存。下面我们来分析 Activity 的销毁流程。 涉及代码如下: frameworks/base/services/core/

liuwg1226的专栏 2111

android home 销毁 activity,Android Activity回收与操作超时处理

本文实例为大家分享了Android Activity回收与操作超时的处理,供大家参考,具体内容如下1、Activity的回收针对多个activity退出的处理关键代码:1)、新建活动管理类:public class ActivityCollector {private static List activityList = new ArrayList();public static void add...

weixin_39675926的博客 486

android9-AMS-常见问题及分析流程

注:跨版本升级问题(如Android 8→9)需重点验证。

summerkissyou1987的专栏 1022

聊聊Activity的生命周期,onSaveInstanceState/onRestoreInstanceState源码分析

1.1生命周期 典型:用户参与下的生命周期改变 异常:①系统回收Activity ②设备的Configuration改变导致Activity销毁重建 1.1.1典型下需要注意 若onCreate中加入finish(),则不会调用onStart 流程:onCreate→onDestroy 若onCreate中加入super.onStop/onDestroy,则会调用onStar...

齐天的博客 2874

Android操作系统的内存回收机制

Android是一款基于Linux 内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理。本文在 ApplicationFramework与Linux内核两个层次上,以进程为粒度,对Android 操作系统的进程资源回收机制进行了剖析。读者可以从本文获得对Android应用程序的生存周期的进一步理解,从而更加

【开源教程25】疯壳·开源编队无人机-定高、定点、悬停

【开源教程25】疯壳·开源编队无人机-定高、定点、悬停

efans_Mike的博客 1650

Android 系统开发 - 进程管理之内存的回收

前言内存是系统中非常宝贵的资源,即便如今的移动设备上,内存已经达到4G甚至6G的级别,但对于内存的回收也依然重要,因为在Android系统上,同时运行的进程有可能会有几十甚至上百个之多。如何将系统内存合理的分配给每个进程,以及如何进行内存回收,便是操作系统需要处理的问题之一。本文会讲解Android系统中内存回收相关的知识。对于内存回收,主要可以分为两个层次:进程内的内存回收:通过释放进程中的资源...

Android技术之家 915

某二级支行网络的设计与实现——配置脚本(1)

【代码】某二级支行网络的设计与实现——配置脚本(1)

u010311846的博客 577

Android------Binder java层如何与native层交互

在这篇文章里,我将向大家描述java层的binder是如何与native层的binder联系起来的,众所周知,java要和c++代码通讯要用到jni,其实jni技术在java里很早以前就有了,,android只是把它拿过来用而已。java层的binder架构实际上也是一个C/S架构,而且其在命名上尽量保持和native层一致,它的架构其实是native层的一个镜像而已。

奔放的Guitar的博客 2822

Windows 10下Gpg4win 4.3.1安装与配置全攻略:从下载到邮件加密实战

本文详细介绍了在Windows 10系统下安装和配置Gpg4win 4.3.1的全过程,包括下载、验证、自定义安装以及密钥管理。特别重点讲解了如何通过Kleopatra证书管理器进行密钥创建和管理,以及在Outlook中实现邮件加密的实战操作,帮助用户全面提升数据安全防护能力。

weixin_29271263的博客 300

【实战技巧】matplotlib中文标签显示问题的3种高效解决方案

本文详细介绍了解决matplotlib中文标签显示问题的3种高效方法,包括修改字体设置、临时指定字体和修改后端配置。针对不同操作系统和环境提供了实用技巧,帮助开发者快速解决中文显示为方框的常见问题,提升数据可视化效果。

weixin_30855099的博客 169

Android:LeakCanary原理的简单理解和使用

1、使用进行,从而来监听Activity的何时被destroy。2 、在的回调中,去检测Activity是否被回收,检测方式如以下步骤。3、使用一个弱引用指向这个activity,并且给这个弱引用指定一个引用队列queue,同时创建一个key来标识该activity。4 、然后将检测的方法投递到空闲消息队列。5、当空闲消息执行的时候,去检测queue里面是否存在刚刚的弱引用,如果存在,则说明此activity已经被回收,就移除对应的key,没有内存泄漏发生。6 、如果queue。

JMW1407的博客 7058

ControlCAN.dll开发适配USBCAN_E/2E_U设备

摘要:本文提供了USBCAN_E/2E_U设备的配置指南:1)安装专用驱动;2)获取最新的ControlCAN.dll文件;3)修改VCI调用顺序,新增VCI_SetReference函数设置波特率;4)使用工具计算波特率配置值,并给出了常见波特率对应的代码参考值(如500K对应0x060007)。配置过程强调使用特定工具计算精确的波特率参数,并提供了相关软件下载链接。

weixin_43969033的博客 387

Android垃圾回收实质内容解析

sp和wp实际上就是Android 为其c++实现的自动垃圾 回收机制 ,具体到内部实现,sp和wp实际上只是一个实现垃圾回收功能的接口而已,比如说对*,->的重载,是为了其看起来跟真正的指针一样,而真正实现垃圾回收的是refbase这个基类。这部分代码的目录在:/frameworks/base/include/utils/RefBase.h 首先所有的类都会虚继承refbase类,因为它实

曹浩洋的专栏 510

项目报告:基于Sentaurus TCAD的多沟道长度NMOS器件仿真、SPICE模型提取与环形振荡器电路验证

本项目基于Sentaurus TCAD工具,完成了从器件仿真到电路验证的全流程研究。首先构建了45nm、65nm和90nm三种沟道长度的NMOS器件,通过TCAD仿真分析了其电学特性,观察到明显的短沟道效应。随后提取了BSIM4 SPICE模型参数,并生成Hspice可用的模型文件。最后利用该模型设计了11级环形振荡器进行电路验证,证明了模型的实用性。研究结果表明,随着沟道长度缩短,器件性能虽有所提升,但短沟道效应导致关态电流显著增加,影响整体能效。本项目为纳米尺度MOSFET的建模与电路设计提供了完整的技

huanghm88的专栏 283

TIA博途WINCC的触摸屏VB脚本入门指南(嵌入式)

通过编写事件处理程序,您可以响应触摸屏元素的各种事件,并实现自定义的功能。本文将为您提供TIA博途WINCC触摸屏VB脚本的入门指南,帮助您了解基本的脚本编写和嵌入式开发。假设我们有一个名为"Button1"的按钮元素,当用户点击该按钮时,触发相应的事件处理程序,并在触摸屏上显示一条消息。然后回到触摸屏界面编辑器,在按钮元素的属性窗口中找到"按下"事件,并选择刚才创建的脚本文件作为事件处理程序。现在,当用户点击按钮时,触摸屏将执行相应的事件处理程序,并显示一条消息框,内容为"按钮被点击了!

CodeSageX的博客 4415

Activity销毁流程

Activity 在以下几种情况下由于正常的应用操作会被销毁,例如:当用户按下后退键或者是 Activity 本身调用了 finish() 方法。此外,如果你的 Activity 生命周期处于 stop 状态而且一段时间内都没有被调用或者是处于前台的 Activity 需要更多的资源而系统资源紧张的时候,系统也会销毁你的 Activity,杀掉后台进程给前台进程留出足够的内存。下面我们来分析 Activity 的销毁流程。

weixin_43700867的博客 1745

【深度学习新浪潮】深度学习特别是大模型在图像配准方面的研发进展

深度学习,尤其是大模型的出现,彻底改变了图像配准的技术范式——从“手工设计特征+几何约束”走向“数据驱动+通用特征学习”。当前,大模型配准已在医学影像、遥感、工业检测等领域实现商业化落地,解决了传统方法难以应对的跨模态、少样本、复杂形变等问题。但大模型配准仍面临推理效率、可解释性、数据隐私等挑战,未来的研发核心将是“精度与效率的平衡”“泛化能力的提升”“场景化适配的简化”。对于开发者而言,从简化版大模型配准代码入手,逐步理解预训练+微调的核心逻辑,是衔接前沿技术的有效路径。

专注:太空算力、AI infra、异构调度、大模型成本工程 423

避免踩坑,内存不足时系统回收Activity的流程解析

android开发中,activity我们会经常遇到,作为view的容器,activity天然就具备了生命周期的特点,当然这篇不是讲生命周期,而是关于系统不足时回收的动作,有可能导致app运行时会出现一些不可预料的“逻辑”异常行为。

大模型学习路线 4781

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

【安卓 R 源码】Activity 启动流程及其生命周期源码分析

1. Activty 的生命周期 activity的生命周期 oncreate()->onstart()->onResume()->onPause()->onStop()->onDestory() onCreate(): 当我们点击activity的时候,系统会调用activity的oncreate()方法,在这个方法中我们会初始化当前布局setContentLayout()方法。 onStart(): onCreate()方法完成后,此时activity进入.

mike_jun的博客 4364

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

安卓四大组件 之 Activity 之 任务栈和回收栈(Tasks and Back Stack)

所有activity都属于任务栈;任务栈中包含着一系列activity,这些activity在任务栈中的排列顺序取决于用户打开它们的先后顺序——最先打开的位于栈底,后打开的依次向上罗列;任务栈可以移到后台并为其中的每一个activity保持其停止时的状态,目的是为了让用户在操作其他任务栈的同时不丢失他们曾经操作过的activity。

程序媛 2427

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Android源码分析 - Activity启动流程(下)

本篇分析了Activity启动的两条路径,在两条路径归一后,Activity是怎样启动的,Activity启动时的生命周期变换是怎样执行的

qq_34231329的博客 565

App进程被回收问题总结

1. App在什么状态下被回收 Android系统在运行程序的过程中发现内存不足,他会去杀一些后台进程,来获取内存,这个过程我们简单称为回收进程。如果后台进程都杀光了,内存还不够,此时可能有2种表现,1,跳出OOM崩溃 2,杀死前台进程。并没有回收某个activity或者回收某些activity的行为。 2. Android的几种进程 前台进程 即与用户正在交互onResume的Activity或者Activity用到的Service在前台等,如果系统内存不足时前台进程是最后被杀死的

u010227042的博客 1524

文化袁探索专栏——Activity|Application启动流程

分析Launcher进程启动流程+时序流程图,activity启动流程+时序流程图,并比较launcher进程、Application启动和activity启动流程的区别。

文化园 3437

【最新Android高级面试知识点--Activity启动流程】

【最新Android高级面试知识点干货分享(三)】 前面用了三篇来整理分享了我的java笔记。 接下来从第四篇开始,我将正式开启我的Android面试笔记分享。 《Android篇》 一、Activity启动流程 这是我自己绘制的时序图,从Launcher开始。可能会存在不准确或看不懂的地方; 因此,也建议读者自己根据源码画一下图,加深理解记忆。 二、Activity任务栈及启动模式 ...

死磕到底 1206

【Android】基础问题—Activity

【Android】Android学习中关于Activity中的一些基础的知识点

bald_tang的博客 2003
上一篇: Window窗口机制——WindowManager,ViewRootImpl,View理解
下一篇: 移动平台游戏网络优化方案
jamespengo
博客等级 码龄16年 83粉丝 48原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值