1. 程式人生 > >Android7.0 Watchdog機制

Android7.0 Watchdog機制

    對手機系統而言,因為肩負著接聽電話和接收簡訊的“重任”,所以被寄予7x24小 時正常工作的希望。但是作為一個在嵌入式裝置上執行的作業系統,Android執行中必須面對各種軟硬體干擾,從最簡單的程式碼出現死鎖或者被阻塞,到記憶體越界導致的記憶體破壞,或者由於硬體問題導致的記憶體反轉,甚至是極端工作環境下出現的CPU電子遷移和儲存器消磁。這一切問題都可能導致系統服務發生難以預料的崩潰和宕機。
    想解決這一問題,可以從正反兩個方向出發,其一是提高軟硬體在極端狀態下的可靠性,如進行程式終止性驗證,或選用抗輻射加固器件。但是基於成本考慮,普通的手機系統很難做到完全不出故障;另一個方法是及時發現系統崩潰並重啟系統。手機系統的大部分的故障都會在重啟後消失,不會影響繼續使用。所以簡單的辦法是,如果檢測到系統不正常了,將裝置重新啟動,這樣使用者就能繼續使用了。那麼如何才能判斷系統是否正常呢。在早期的手機平臺上通常的做法是在裝置中增加一個硬體看門狗,軟體系統必須定 時的向看門狗硬體中寫值來表示自己沒出故障(俗稱“喂狗”),否則超過了規定的時間看門狗就會重新啟動裝置。
    硬體看門狗的問題是它的功能比較單一,只能監控整個系統。早期的手機作業系統大多是單任務的,硬體看門狗勉強能勝任。Android的SystemServer是一個非常複雜的程序,裡面執行的服務超過五十種,是最可能出問題的程序,因此有必要對SystemServer中執行的各種執行緒實施監控。但是如果使用硬體看門狗的工作方式,每個執行緒隔一段時間去喂狗,不但非常浪費CPU,而且會導致程式設計更加複雜。因此Android開發了WatchDog類作為軟體看門狗來監控SystemServer中的執行緒。一旦發現問題,WatchDog會殺死SystemServer程序。
    SystemServer的父程序Zygote接收到SystemServer的死亡訊號後,會殺死自己。Zygote程序死亡的訊號傳遞到Init程序後,Init程序會殺死Zygote程序所有的子程序並重啟Zygote。這樣整個手機相當於重啟一遍。通常SystemServer出現問題和kernel並沒有關係,所以這種“軟重啟”大部分時候都能夠解決問題。而且這種“軟重啟”的速度更快,對使用者的影響也更小。


WatchDog是在SystemServer程序中被初始化和啟動的。在SystemServer 的run方法中,各種Android服務被註冊和啟動,其中也包括了WatchDog的初始化和啟動。程式碼如下:

            final Watchdog watchdog = Watchdog.getInstance();
            watchdog.init(context, mActivityManagerService);
在SystemServer中startOtherServices的後半段,將通過SystemReady介面通知系統已經就緒。在ActivityManagerService的SystemReady介面的CallBack函式中實現WatchDog的啟動
                Watchdog.getInstance().start();
以上程式碼位於frameworks/base/services/java/com/android/server/SystemServer.java中。
前面說到WatchDog是在SystemServer.java中通過getInstance方法建立的,其具體實現方式如下:
    public static Watchdog getInstance() {
        if (sWatchdog == null) {
            sWatchdog = new Watchdog();    //單例模式建立例項
        }

        return sWatchdog;
    }

    private Watchdog() {
        super("watchdog");
        // Initialize handler checkers for each common thread we want to check.  Note
        // that we are not currently checking the background thread, since it can
        // potentially hold longer running operations with no guarantees about the timeliness
        // of operations there.

        // The shared foreground thread is the main checker.  It is where we
        // will also dispatch monitor checks and do other work.
        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
                "foreground thread", DEFAULT_TIMEOUT);
        mHandlerCheckers.add(mMonitorChecker);
        // Add checker for main thread.  We only do a quick check since there
        // can be UI running on the thread.
        mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
                "main thread", DEFAULT_TIMEOUT));
        // Add checker for shared UI thread.
        mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
                "ui thread", DEFAULT_TIMEOUT));
        // And also check IO thread.
        mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
                "i/o thread", DEFAULT_TIMEOUT));
        // And the display thread.
        mHandlerCheckers.add(new HandlerChecker(DisplayThread.getHandler(),
                "display thread", DEFAULT_TIMEOUT));

        // Initialize monitor for Binder threads.
        addMonitor(new BinderThreadMonitor());
    }
在Watchdog建構函式中將main thread,UIthread,Iothread,DisplayThread加入mHandlerCheckers列表中。最後初始化monitor放入mMonitorCheckers列表中。
    public void addMonitor(Monitor monitor) {
        synchronized (this) {
            if (isAlive()) {
                throw new RuntimeException("Monitors can't be added once the Watchdog is running");
            }
            mMonitorChecker.addMonitor(monitor);
        }
    }
上述程式碼僅僅是啟動了watchdog服務,但watchdog還不知道需要監視哪些系統服務。為保持watchdog模組的獨立性和可擴充套件性,需要由系統服務向watchdog註冊。Watchdog提供兩種監視方式,一種是通過monitor()回撥監視服務關鍵區是否出現死鎖或阻塞,一種是通過傳送訊息監視服務主執行緒是否阻塞。
以ActivityManagerService.java為例,為向watchdog註冊monitor()回撥,首先需要繼承watchdog.Monitor介面:
public class ActivityManagerService extends ActivityManagerNativeEx
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
而後在建構函式中把自身註冊到watchdog monitor服務中。注意這裡有兩個檢測項,一個是addMonitor,在每一個檢測週期中watchdog會使用foreground thread的HandlerChecker回撥服務註冊的monitor()方法給服務的關鍵區上鎖並馬上釋放,以檢測關鍵區是否存在死鎖或阻塞;另一個是addThread,watchdog會定時通過HandlerChecker向系統服務傳送訊息,以檢測服務主執行緒是否被阻塞。這就是為什麼在watchdog重啟時有有兩種提示語:“Block in Handler in ......”和“Block in monitor”,它們分別對應不同的阻塞型別。
        Watchdog.getInstance().addMonitor(this);
        Watchdog.getInstance().addThread(mHandler);
最後在類中實現watchdog.Monitor所需的monitor方法。watchdog執行時每30秒會回撥這個方法來鎖一次這個關鍵區,如果60秒都無法得到鎖,就說明服務已經發生了死鎖,必須重啟裝置。
    /** In this method we try to acquire our lock to make sure that we have not deadlocked */
    public void monitor() {
        synchronized (this) { }
    }
從上面分析可以知道,在watchdog的建構函式中將foreground thread、mian thread傳入了一個HandlerChecker類。這個類就是watchdog檢測超時的執行者。HandlerChecker類有多個例項,每個通過addThread向watchdog註冊自身的服務都對應一個HandlerChecker類例項。
    public void addThread(Handler thread) {
        addThread(thread, DEFAULT_TIMEOUT);
    }

    public void addThread(Handler thread, long timeoutMillis) {
        synchronized (this) {
            if (isAlive()) {
                throw new RuntimeException("Threads can't be added once the Watchdog is running");
            }
            final String name = thread.getLooper().getThread().getName();
            mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
        }
    }
HandlerChecker繼承了Runnable,每個HandlerChecker在各自服務的主執行緒中執行並完成相應的檢查,不會互相干擾。
    /**
     * Used for checking status of handle threads and scheduling monitor callbacks.
     */
    public final class HandlerChecker implements Runnable {
        private final Handler mHandler;
        private final String mName;
        private final long mWaitMax;
        private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
        private boolean mCompleted;
        private Monitor mCurrentMonitor;
        private long mStartTime;

        HandlerChecker(Handler handler, String name, long waitMaxMillis) {
            mHandler = handler;
            mName = name;
            mWaitMax = waitMaxMillis;
            mCompleted = true;
        }
每個通過addThread向watchdog註冊自身的服務都對應一個HandlerChecker類例項,那麼通過addMonitor()註冊的服務由誰來檢查呢?答案就是前面出現的mMonitorChecker,也就是foreground thread的HandlerChecker。它除了需要檢測主執行緒是否堵塞外,還需要回調系統服務註冊的monitor()方法,以檢測這些服務的關鍵區是否存在死鎖或阻塞。
之所以不能在watchdog的主執行緒中回撥monitor()方法,是由於如果被監控服務的關鍵區被佔用,其monitor()方法可能需要一段時間才能返回。這樣就無法保證watchdog每次個檢測週期都是30s,所以必須交由foreground thread代為檢查。
addMonitor()中會把每個monitor新增到mMonitorChecker也就是foreground thread的HandlerChecker中。除了它以外,所有HandlerChecker的mMonitors都是空的。
當watchdog的主迴圈開始執行後,每隔30秒,都會依次呼叫所有HandlerChecker的scheduleCheckLocked()方法。對於foreground thread的HandlerChecker,由於它的mMonitors不為空,需要它去鎖各服務的monitor()來檢查是否出現死鎖,因此每個檢測週期都要執行它。
對於其他的HandlerChecker,需要判斷執行緒的Looper是否處於Idling,若為空就說明前一個訊息已經執行完畢正在等下一個,訊息迴圈肯定沒阻塞,不用繼續檢測直接跳過本輪。
如果執行緒的訊息迴圈不是Idling狀態,說明服務的主執行緒正在處理某個訊息,有阻塞的可能,就需要使用PostAtFrontOfQueue發出訊息到訊息佇列,並記錄下當前系統時間,同時將mComplete置為false,標明已經發出一個訊息正在等待處理。
        public void scheduleCheckLocked() {
            if (mMonitors.size() == 0 && mHandler.getLooper().getQueue().isPolling()) {
                // If the target looper has recently been polling, then
                // there is no reason to enqueue our checker on it since that
                // is as good as it not being deadlocked.  This avoid having
                // to do a context switch to check the thread.  Note that we
                // only do this if mCheckReboot is false and we have no
                // monitors, since those would need to be executed at this point.
                mCompleted = true;
                return;
            }

            if (!mCompleted) {
                // we already have a check in flight, so no need
                return;
            }

            mCompleted = false;
            mCurrentMonitor = null;
            mStartTime = SystemClock.uptimeMillis();
            mHandler.postAtFrontOfQueue(this);
        }
如果執行緒的訊息佇列沒有阻塞,PostAtFrontOfQueue很快就會觸發HandlerChecker的run方法。對於foreground thread的HandlerChecker,它會回撥被監控服務的monitor方法,對其關鍵區上鎖並馬上釋放,以檢查是否存在死鎖或阻塞。對於其他執行緒,僅需要將mComplete標記為true,表明訊息已經處理完成即可。
        @Override
        public void run() {
            final int size = mMonitors.size();
            for (int i = 0 ; i < size ; i++) {
                synchronized (Watchdog.this) {
                    mCurrentMonitor = mMonitors.get(i);
                }
                mCurrentMonitor.monitor();
            }

            synchronized (Watchdog.this) {
                mCompleted = true;
                mCurrentMonitor = null;
            }
        }
    }
如果服務的訊息迴圈發生了堵塞,那麼mComplete就會一直處於false狀態。watchdog在每一個檢測週期中都會一次呼叫每個HandlerChecker的getCompletionStateLocked方法檢測超時時間,如果任何一個服務的主執行緒30s無響應就會提前輸出其堆疊為重啟做準備,如果60s無響應則進入重啟流程。
        public int getCompletionStateLocked() {
            if (mCompleted) {
                return COMPLETED;
            } else {
                long latency = SystemClock.uptimeMillis() - mStartTime;
                if (latency < mWaitMax/2) {
                    return WAITING;
                } else if (latency < mWaitMax) {
                    return WAITED_HALF;
                }
            }
            return OVERDUE;
        }
Watchdog主迴圈
SystemServer呼叫watchdog的start方法,watchdog便開始在自己執行緒的while迴圈中執行,以達到每30s檢測一次的目的:
    @Override
    public void run() {
        boolean waitedHalf = false;
        while (true) {
            final ArrayList<HandlerChecker> blockedCheckers;
            final String subject;
            final boolean allowRestart;
            int debuggerWasConnected = 0;
            synchronized (this) {
                long timeout = CHECK_INTERVAL;
                // Make sure we (re)spin the checkers that have become idle within
                // this wait-and-check interval
                for (int i=0; i<mHandlerCheckers.size(); i++) {   //遍歷各個HandlerChecker,依次檢查前臺,ui,主執行緒等系統主要執行緒
                    HandlerChecker hc = mHandlerCheckers.get(i);
                    hc.scheduleCheckLocked();
                }
對於每個檢測週期,首先需要將timeout計時器復位,而後依次檢查在watchdog的init方法中註冊的foreground thread,main thread,UI thread,i/o thread,以及其他通過addThread方法註冊的服務的主執行緒是否阻塞。
檢查主執行緒是否阻塞的方法是,如果執行緒Looper狀態不是Idling,就通過HandlerChecker的postAtFrontOfQueue方法傳送一個訊息。稍後檢測這個訊息是否超時未返回。
通過postAtFrontOfQueue送出訊息後睡眠30s。注意這裡使用uptimeMillis()計算時間,不計手機在睡眠中度過的時間。這是由於手機睡眠時系統服務同樣也在睡眠,無法響應watchdog送出的訊息,如果把睡眠時間計算在內當手機被再次喚醒時會導致watchdog認為時間已經過去了很久,從而發生誤殺。

                // NOTE: We use uptimeMillis() here because we do not want to increment the time we
                // wait while asleep. If the device is asleep then the thing that we are waiting
                // to timeout on is asleep as well and won't have a chance to run, causing a false
                // positive on when to kill things.
                long start = SystemClock.uptimeMillis();   //使用uptimeMills不把手機睡眠時間算進入,手機睡眠時系統服務同樣睡眠,狀態無法響應watchdog會導致誤殺
                while (timeout > 0) {
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }
                    try {
                        wait(timeout);
                    } catch (InterruptedException e) {
                        Log.wtf(TAG, e);
                    }
                    if (Debug.isDebuggerConnected()) {
                        debuggerWasConnected = 2;
                    }  //CHECK_INTERVAL的預設時間是30s,此為第一次等待時間,WatchDog判斷物件是否死鎖的最長等待時間為1min
                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
                }
30秒等待完成後,就要檢測之前送出的訊息是否已經執行完畢。通過evaluateCheckerCompletionLocked遍歷所有的HandlerChecker,返回最大的waitState值。waitState共有四種情況:COMPLETED對應訊息已處理完畢執行緒無阻塞;WAITING對應訊息處理花費0~29秒,需要繼續執行;WAITED_HALF對應訊息處理花費30~59秒,執行緒可能已經被阻塞,需要儲存當前AMS堆疊狀態,用以在超時發生時輸出堆疊;OVERDUE對應訊息處理已經花費超過60s,此時便進入下一流程,輸出堆疊資訊並重啟手機。
                final int waitState = evaluateCheckerCompletionLocked();
                if (waitState == COMPLETED) {
                    // The monitors have returned; reset
                    waitedHalf = false;   //所有服務都正常,reset
                    continue;
                } else if (waitState == WAITING) {
                    // still waiting but within their configured intervals; back off and recheck
                    continue;
                } else if (waitState == WAITED_HALF) {
                    if (!waitedHalf) {
                        // We've waited half the deadlock-detection interval.  Pull a stack
                        // trace and wait another half.
                        ArrayList<Integer> pids = new ArrayList<Integer>();
                        pids.add(Process.myPid());
                        ActivityManagerService.dumpStackTraces(true, pids, null, null,
                                NATIVE_STACKS_OF_INTEREST);
                        waitedHalf = true;
                    }
                    continue;
                }
Watchdog超時已經發生,但之前evaluateCheckerCompletionLocked並不關心是哪個服務發生阻塞,僅僅返回所有服務最大的waitState值。此時需要呼叫getBlockedCheckersLocked判斷具體是哪些應用發生了阻塞,阻塞的原因是什麼。這就是我們在dropbox中看到的阻塞原因描述。而後依次輸出AMS與Kernel呼叫堆疊。
                // something is overdue!
                blockedCheckers = getBlockedCheckersLocked();   //WatchDog超時,獲取那個服務超時阻塞,生成崩潰描述符
                subject = describeCheckersLocked(blockedCheckers);  //判斷是否重啟
                allowRestart = mAllowRestart;
            }

            // If we got here, that means that the system is most likely hung.
            // First collect stack traces from all threads of the system process.
            // Then kill this process so that the system will restart.
            EventLog.writeEvent(EventLogTags.WATCHDOG, subject);

            ArrayList<Integer> pids = new ArrayList<Integer>();
            pids.add(Process.myPid());
            if (mPhonePid > 0) pids.add(mPhonePid);
            // Pass !waitedHalf so that just in case we somehow wind up here without having
            // dumped the halfway stacks, we properly re-initialize the trace file.
            final File stack = ActivityManagerService.dumpStackTraces(
                    !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);

            // Give some extra time to make sure the stack traces get written.
            // The system's been hanging for a minute, another second or two won't hurt much.
            SystemClock.sleep(2000);

            // Pull our own kernel thread stacks as well if we're configured for that
            if (RECORD_KERNEL_THREADS) {
                dumpKernelStackTraces();
            }

            // Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
            doSysRq('w');
            doSysRq('l');

            // Try to add the error to the dropbox, but assuming that the ActivityManager
            // itself may be deadlocked.  (which has happened, causing this statement to
            // deadlock and the watchdog as a whole to be ineffective)
            Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
                    public void run() {
                        mActivity.addErrorToDropBox(
                                "watchdog", null, "system_server", null, null,
                                subject, null, stack, null);
                    }
                };
            dropboxThread.start();
            try {
                dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
            } catch (InterruptedException ignored) {}

            IActivityController controller;
            synchronized (this) {
                controller = mController;
            }
            if (controller != null) {
                Slog.i(TAG, "Reporting stuck state to activity controller");
                try {
                    Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
                    // 1 = keep waiting, -1 = kill system
                    int res = controller.systemNotResponding(subject);
                    if (res >= 0) {
                        Slog.i(TAG, "Activity controller requested to coninue to wait");
                        waitedHalf = false;
                        continue;
                    }
                } catch (RemoteException e) {
                }
            }

            // Only kill the process if the debugger is not attached.
            if (Debug.isDebuggerConnected()) {
                debuggerWasConnected = 2;
            }
            if (debuggerWasConnected >= 2) {
                Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
            } else if (debuggerWasConnected > 0) {
                Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
            } else if (!allowRestart) {
                Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
            } else {
                Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
                for (int i=0; i<blockedCheckers.size(); i++) {
                    Slog.w(TAG, blockedCheckers.get(i).getName() + " stack trace:");
                    StackTraceElement[] stackTrace
                            = blockedCheckers.get(i).getThread().getStackTrace();
                    for (StackTraceElement element: stackTrace) {
                        Slog.w(TAG, "    at " + element);
                    }
                }
                Slog.w(TAG, "*** GOODBYE!");
                Process.killProcess(Process.myPid());
                System.exit(10);
            }

            waitedHalf = false;
        }
    }
輸出dropbox,並檢查activity controller連線的偵錯程式是否可以處理這次watchdog無響應,如果activity controller不要求重啟,那麼就忽視這次超時,從頭繼續執行watchdog迴圈。殺死SystemServer並重啟手機。

相關推薦

Android7.0 Watchdog機制

    對手機系統而言,因為肩負著接聽電話和接收簡訊的“重任”,所以被寄予7x24小 時正常工作的希望。但是作為一個在嵌入式裝置上執行的作業系統,Android執行中必須面對各種軟硬體干擾,從最簡單的程式碼出現死鎖或者被阻塞,到記憶體越界導致的記憶體破壞,或者由於硬體問題導

Android7.0 klog機制(如何將android log列印到kernel log中)

    在分析Android7.0 init程序一文中提到,在init程序中是通過klog來輸出log資訊的,但是由於log的級別不同可能導致有些新增的log無法輸出來。在init .cpp的main函式中初始化klog。 klog_init();

Android7.0對dlopen的改變

tail 地址 npr and eof size strtok log brush 兩個內存段 在同一個進程空間中dlopen一個.so文件,理論上在內存中是同一片區域,但實際調試中發現Android7.0(read "/proc/self/maps")中,先後讀同一個.

appium_v1.4.16版本不適配android7.0系統,運行報錯“Attempt to re-install io.appium.settings without first uninstalling”

urn fail tin ins 找到 auto his ger 問題: 要解決的問題:appium在androidV7.0系統上運行時報錯 Failure [INSTALL_FAILED_ALREADY_EXISTS: Attempt to re-install io.a

Android7.0調用系統相機拍照、讀取系統相冊照片+CropImageView剪裁照片

alpha pri process 點擊事件 self tps 而在 center ase Android手機拍照、剪裁,並非那麽簡單 簡書地址:[我的簡書–T9的第三個三角] 前言 項目中,基本都有用戶自定義頭像或自定義背景的功能,實現方法一般都是調用

Android7.0打包安裝問題

信息 num .com print 應用 ima text ret pan 一.問題描述 Android 7.0 引入一項新的應用簽名方案 APK Signature Scheme v2,先使用APK Signature Scheme v2簽名打包,然後再使用python腳

appium在android7.0上無法啟動問題

ESS cep 升級 代碼 resp 部分 elf back file 前言 由於最近很多android手機升級到7.0系統了,有些小夥伴的appium版本用的還是1.4版本,在運行android7.0的app自動化時候遇到無法啟動問題:WebDriverException

Android7.0 MediaRecorder源碼分析

錄像 stage sdn ice ren oop 新建 n) edi 最近在做Camera的方案(雙進程打開一個Camera),涉及到使用MediaRecorder 進行錄像,還是自己新建一套錄像系統。接下來我將記錄下本次源碼分析的流程。  有關於Client和Server

區塊鏈2.0-共識機制如何打破互聯網信息大爆炸

之一 分享 分享圖片 一起學 人生 利用 行業 浪費時間 cto 區塊鏈2.0-共識機制如何打破互聯網信息大爆炸   作者:Melon   如今打開頭部流量產品如:QQ、微信、抖音等。總是會收到各種垃圾遊戲的推薦廣告。頭部媒體公司更是用一樁樁的奇聞異事把我吸引進去看了不知真

Android7.0 API變更

接收消息 靜態鏈接 status pin acc 進程 bsp 文件名 ict Android N 除了提供諸多新特性和功能外,還對系統和 API 行為做出了各種變更。 本文重點介紹您應該了解並在開發應用時加以考慮的一些重要變更。 如果您之前發布過 Android 應用,請

Android7.0文件訪問權限

provide utf load mission perm extern grant download nal 例子:下載apk然後安裝 <provider android:name="android.support.v4.content.FileProvide

Android7.0 View.post與Handler.post

在獲取view寬高時,在Android6.0中使用handler.post()可以正常獲取,而執行在Android7.0上則無法再獲取。而在7.0上改為view.post()方法則又可以正常獲取view寬高。 檢視原始碼和相關資料後知道是因為,雖然這兩個都是post(new runnab

popupwindow在android7.0出現全屏解決方案

在android7.0的版本測出popupwindow使用showAsDropDown方法之後,並不能顯示在指定view的下方,而是全屏顯示,只要重寫showAsDropDown判斷一下版本就好了.建議不要使用popupwindow了,使用DialogFragment代替 publi

Android7.0、8.0、9.0的https抓包,charles解決方案

原文地址:https://blog.csdn.net/u011045726/article/details/76064048   Android7.0 和 Charles 的抓包 關於android手機在mac版charles上抓不到包這個問題困擾了很久,查閱了很多資料,發現是an

android7.0 靜默安裝

android7.0 靜默安裝,可以用來更新已經安裝的應用。無法獲取安裝進度,可以使用系統廣播來監聽應用的安裝、解除安裝、更新。7.1.2版本親測可行。不過使用了系統簽名再次簽名! /** * 靜默安裝 * @param installPath * @param packageNam

樹莓派3B上編譯Android7 0系統

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android7.0以上自動安裝軟體

Android7.0發生了行為變更,禁止您的應用外部公開 file://Uri 。 如果一項包含檔案 Uri 的 Intent 離開您的應用後,則應用會出現故障,並出現 FileUriExposedException 異常。 1.在AndroidManifest.xml中新增provid

Android7.0遮蔽發現可連線wifi的提示音(MTK)

--- a/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNotificationController.java +++ b/frameworks/opt/net/wifi/service/java/com/andr

Android7.0以上安裝時出現“解析軟體包錯誤”

vivox23在Android studio上除錯軟體出現“解析軟體包錯誤”,不管是專案工程還是自己新建的hellowrold工程都會出現這個問題,取消下圖第一個選項 Android studio的2.0新版本出了一個革命性的功能就是Instant Run(即時執行)!新的即時執行功能可以

Android Native記憶體洩露檢測(針對Android7.0)

1. 需要合入一個 Patch 2. 執行指令 adb root adb shell setprop libc.debug.malloc.program cameraserver adb shell setprop libc.debug.malloc.options “backt