主頁 > 移動端開發 > 【Android】 一個crash 背后竟然暗藏玄機,速看!

【Android】 一個crash 背后竟然暗藏玄機,速看!

2020-12-26 12:35:24 移動端開發

導讀

最近公司有一個需求,就是如何讓App 不奔潰或者奔潰后可以自動重啟?咋一聽,可能你和我都會說,對可能Crash的地方try…catch 不就可以了?
然而細琢磨一下這個問題,其實并非如此簡單,,,,接下來大家就跟我一起看看App Crash背后的緣由吧!

問題細化

如何讓自己的App不奔潰呢?其實問題主要涉及一下幾個點:
1、App為什么會Crash?
2、未捕獲到的例外導致的Crash怎么辦?
3、有什么辦法可以讓APP不奔潰呢?
4、假如App奔潰后,能否自動重啟呢?
帶著這幾個問題,和大家分享一下我查閱到的解決方案,以下內容或多或少參閱了其他博文,請大家不要見笑,,就當是自己學習總結了下哈,

探究一:App為什么會Crash?

首先捕獲程式崩潰的例外就必須了解一下java中UncaughtExceptionHandler這個介面,android沿用了此介面,在android API中通過實作此介面,能夠處理執行緒被一個無法捕捉的例外所終止的情況,

在java API中對該介面描述如下:

在實作UncaughtExceptionHandler時,必須多載uncaughtException(Thread thread, Throwable ex)
1、如果我們沒有實作該介面,也就是沒有顯示捕捉例外,則ex為空,否則ex不為空,thread 則為出例外的執行緒;
2、如果想捕獲例外我們可以實作這個介面或者繼承ThreadGroup,并多載uncaughtException方法,
顯示處理執行緒例外終止的情況;

一、首先我們看下執行緒中拋出例外以后的處理邏輯,一旦代碼拋出例外,并且我們沒有捕捉的情況下,JVM 會呼叫 Thread 的 dispatchUncaughtException 方法

public final void dispatchUncaughtException(Throwable e) {
        Thread.UncaughtExceptionHandler initialUeh =
                Thread.getUncaughtExceptionPreHandler();
        if (initialUeh != null) {
            try {
                initialUeh.uncaughtException(this, e);
            } catch (RuntimeException | Error ignored) {
                // Throwables thrown by the initial handler are ignored
            }
        }
        //這里會獲取對應的 UncaughtExceptionHandler 物件,然后呼叫對應的 uncaughtException 方法
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        //可以看到當 uncaughtExceptionHandler 沒有賦值的時候,會回傳 ThreadGroup 物件
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

通過原始碼得知,如果 App 中沒有手動設定 uncaughtExceptionHandler 物件,那么會執行 ThreadGroup的uncaughtException 方法:

 public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

然后呼叫 Thread.getDefaultUncaughtExceptionHandler() 獲取默認的 UncaughtExceptionHandler ,然后呼叫 uncaughtException 方法,既然名字是默認的 uncaughtExceptionHandler 物件,那么必然有初始化的地方…這就需要從系統初始化開始說起,為了簡化初始化流程,咱們直接從 RuntimeInit 的 main 方法開始說起

   @UnsupportedAppUsage
    public static final void main(String[] argv) {
        enableDdms();
        if (argv.length == 2 && argv[1].equals("application")) {
            if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
            redirectLogStreams();
        } else {
            if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
        }

        commonInit();

        /*
         * Now that we're running in interpreted code, call back into native code
         * to run the system.
         */
        nativeFinishInit();

        if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
    }
  @UnsupportedAppUsage
    protected static final void commonInit() {
        if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
        
        /*
         * set handlers; these apply to all threads in the VM. Apps can replace
         * the default handler, but not the pre handler.
         */
        LoggingHandler loggingHandler = new LoggingHandler();
        RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler))
        
         ...代碼省略...
         
        initialized = true;
    }

在這里,給Thread 設定一個 KillApplicationHandler 物件,而 KillApplicationHandler實作了 Thread.UncaughtExceptionHandler 這個介面,那么必然會重寫 uncaughtException 方法,

App為什么會Crash?關鍵代碼就在這里

        @Override
        public void uncaughtException(Thread t, Throwable e) {
            try {
                ensureLogging(t, e);

                // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
                if (mCrashing) return;
                mCrashing = true;

                // Try to end profiling. If a profiler is running at this point, and we kill the
                // process (below), the in-memory buffer will be lost. So try to stop, which will
                // flush the buffer. (This makes method trace profiling useful to debug crashes.)
                if (ActivityThread.currentActivityThread() != null) {
                    ActivityThread.currentActivityThread().stopProfiling();
                }

                // Bring up crash dialog, wait for it to be dismissed
                ActivityManager.getService().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
            } catch (Throwable t2) {
                if (t2 instanceof DeadObjectException) {
                    // System process is dead; ignore
                } else {
                    try {
                        Clog_e(TAG, "Error reporting crash", t2);
                    } catch (Throwable t3) {
                        // Even Clog_e() fails!  Oh well.
                    }
                }
            } finally {
                // Try everything to make sure this process goes away.
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }

代碼最后執行了 System.exit(10) ;而這個方法會直接干掉當前行程,也就是所謂的 App crash 了,所以我們一旦拋出例外,并且沒有捕捉的話,程式就會被強制干掉,

探究二:未捕獲的例外導致的Crash怎么辦?

這個問題其實上面有提到,實作UncaughtExceptionHandler介面,顯示處理執行緒例外終止就行,具體方法如下:

/**
 * Created by lxb on 2020/12/11
 * <p>
 * Thread.UncaughtExceptionHandler
 * 介面說明:能夠處理執行緒被一個無法捕獲的例外所終止
 */
public class UnExcepHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "UnExcepHandler";
    private CatchExceptionApp application;
    private final Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler;

    public UnExcepHandler(CatchExceptionApp application) {
        // 獲取系統默認的UncaughtExceptionHandler 處理器
        defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        this.application = application;
    }

    /**
     * @param thread Thread
     * @param ex     ex ==null,表示沒有顯示的處理例外;
     *               ex!=null,表示 thread為出例外的執行緒
     */
    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
        // 如果用戶沒有處理,則讓系統默認的例外處理器處理
        if (!handlerException(ex) && defaultUncaughtExceptionHandler != null) {
            defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Intent intent = new Intent(application, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                    | Intent.FLAG_ACTIVITY_CLEAR_TASK
                    | Intent.FLAG_ACTIVITY_NEW_TASK);
            @SuppressLint("WrongConstant") PendingIntent restartIntent =
                    PendingIntent.getActivity(application.getApplicationContext(), 0,
                            intent, intent.getFlags());
            // 退出程式,1秒后自動重啟程式
            AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC,
                    System.currentTimeMillis() + 1000, restartIntent);

//            application.finishAllActivity();
        }
    }

    /**
     * 自定義錯誤處理 / 收集錯誤資訊 / 發送錯誤報告 都在此處理
     *
     * @param ex
     * @return true: 處理了該例外 false: 不處理例外(系統處理)
     */
    public boolean handlerException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        // 顯示無法捕獲的例外
        new Thread() {
            @Override
            public void run() {
                super.run();
                Looper.prepare();
                Toast.makeText(application.getApplicationContext(),
                        "很抱歉,程式出現例外,即將退出,", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
        return true;
    }

通過在android Application 這個全域類中處理例外即可,

/**
 *  Created by lxb on 2020/12/11
 *  全域捕獲例外
 */
public class CatchExceptionApp extends Application {

    private static final String TAG = "CatchExceptionApp";

    public void init() {
        // 設定UnExcepHandler 為程式的默認處理器
        UnExcepHandler unExcepHandler = new UnExcepHandler(this);
        Thread.setDefaultUncaughtExceptionHandler(unExcepHandler);
    }

探究三:假如App奔潰后,能否自動重啟呢?

如何殺死例外行程,重啟應用,就得使用PendingIntent,這個類是android中對Intent類的包裝,具體代碼如下:

   /**
     * @param thread Thread
     * @param ex     ex ==null,表示沒有顯示的處理例外;
     *               ex!=null,表示 thread為出例外的執行緒
     */
    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
        // 如果用戶沒有處理,則讓系統默認的例外處理器處理
        if (!handlerException(ex) && defaultUncaughtExceptionHandler != null) {
            defaultUncaughtExceptionHandler.uncaughtException(thread, ex);
        } else {
            Intent intent = new Intent(application, MainActivity.class);
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
                    | Intent.FLAG_ACTIVITY_CLEAR_TASK
                    | Intent.FLAG_ACTIVITY_NEW_TASK);
            @SuppressLint("WrongConstant") PendingIntent restartIntent =
                    PendingIntent.getActivity(application.getApplicationContext(), 0,
                            intent, intent.getFlags());
            // 退出程式,1秒后自動重啟程式
            AlarmManager alarmManager = (AlarmManager) application.getSystemService(Context.ALARM_SERVICE);
            alarmManager.set(AlarmManager.RTC,
                    System.currentTimeMillis() + 1000, restartIntent);

//            application.finishAllActivity();
        }
    }

通過AlarmManager 啟動它,并且關閉打開的Activity殺死例外行程就能夠實作重新啟動應用,

探究四:有什么辦法可以讓APP不奔潰呢?

通過以上三個探究,很顯然是有辦法讓App 不奔潰的,再重新看下這段原始碼:

    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        //可以看到當uncaughtExceptionHandler沒有賦值的時候,會回傳ThreadGroup物件
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

上面有提到只有在沒有手動設定 UncaughtExceptionHandler 的時候,才會呼叫
defaultUncaughtExceptionHandler 物件,所以自然而然的就想到了實作這個類,然后在這里面做相應的處理,

接下來咱們可以寫一個demo來驗證這個推測到底可行不可行,,,

1、首先人為制造一個例外(主動拋出例外)看看效果

   @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnCrashTwo: // 主執行緒拋出例外不奔潰處理
                mainThreadCrash();
                break;
        }
    }

    private void mainThreadCrash() {
        int i = 1 / 0;
        Log.d("MainActivity", "i:" + i);
    }

來看一下執行效果(運行效果圖后期補上,抱歉!):

在這里插入圖片描述

不出意料程式崩潰了!!!

那接下來咱們手動設定一個UncaughtExceptionHandler

/**
 * Created by lxb on 2020/12/11
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {

    private static final String TAG = "CrashHandler";

    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable e) {
        Log.d(TAG, "[uncaughtException] : " + e.getMessage());

        // 通過handler將toast拋到主執行緒彈出
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(CatchExceptionApp.mApp, "[uncaughtException] message:\n"
                        + e.getMessage(), Toast.LENGTH_LONG).show();
            }
        });
    }
}
public class CatchExceptionApp extends Application {

    private static final String TAG = "CatchExceptionApp";
    public static CatchExceptionApp mApp;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        //主執行緒Crash處理
        /**
         * Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());
         * tip:
         * 這個是設定當前執行緒的方法,總不能給每個Thread 都設定一個,這肯定不可取
         * Thread 中還有一個全域靜態的 UncaughtExceptionHandler 可以解決這一問題
         */
       Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());
    }
    }

再運行看下效果(運行效果圖后期補上,抱歉!):

在這里插入圖片描述

看來我們的推測是正確的,確實 App 已經不會 crash 了,但是又出現了另外一個問題, App 卡死在了這個界面,怎么點擊也沒反應了,

那么這到底是怎么一回事呢?其實這也不難理解,我們的頁面啟動的入口是在 ActivityThread 的 main 方法:在這里面進行初始化主執行緒的 Loop ,然后執行 loop 回圈,我們知道 Looper 是用來回圈遍歷訊息佇列的,一旦訊息佇列中存在訊息,那么就會執行里面的操作,

整個 Android 系統就是基于事件驅動的,而事件主要就是基于 Looper 來獲取的,所以如果這里一旦出現
crash,那么就直接會跳出整個 main 方法,自然 loop 回圈也就跳出了,那么自然而然事件也就接收不到,更沒法處理,所以整個 App
就會卡死在這里,

2、有沒有其他辦法可以保證 App 在拋出例外不 crash 的情況下,又能保證不會卡死呢?

既然 looper 是查詢事件的核心類,那么我們是否可以不讓跳出 loop 回圈呢,乍一想好像沒辦法做到,我們沒法給 loop 方法 try-catch ,但是我們可以給訊息佇列發送一個 loop 回圈,然后給這個 loop 做一個 try-catch ,一旦外層的 loop 檢測到這個事件,就會執行我們自己創建的 loop 回圈,這樣以后 App 內的所有事件都會在我們自己的 loop 回圈中處理,一旦拋出例外,跳出 loop 回圈以后,我們也可以在 loop 外層套一層 while 回圈,讓自己的 loop 再次作業,

再通過代碼驗證下咱們的推測吧:

package com.lxb.app_crash_auto_reboot;

import android.app.Activity;
import android.app.Application;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by lxb on 2020/12/11
 * 全域捕獲例外
 */
public class CatchExceptionApp extends Application {

    private static final String TAG = "CatchExceptionApp";
    public static CatchExceptionApp mApp;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        //主執行緒Crash處理
        Handler handler = new Handler(getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                // 未捕獲的例外導致APP Crash 后,通過while 讓自己創建的loop 再次作業
                while (true) {
                    //給自己創建的loop 添加 try catch ,一旦外層loop(系統奔潰)檢測到這個事件,然后執行我們自己的loop回圈
                    try {
                        Looper.loop();
                    } catch (Exception e) {
                        e.printStackTrace();
                        Toast.makeText(CatchExceptionApp.this,
                                "main-Thread 拋出了例外",
                                Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
    }
}

執行一下效果看看:

在這里插入圖片描述

至此,最后我們就解決了拋出例外導致 App crash 的問題了,

難道問題就這樣終結了嗎?當然沒有那么快就結束,這里給主執行緒的Looper 發送 loop 回圈都是主執行緒操作的,那么子執行緒如果拋出例外怎么辦呢,這么處理應該也是會 crash 吧,那再做個實驗吧:


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnCrash: // 子執行緒拋出例外不奔潰處理
                childThreadCrash();
                break;
        }
    }
    
    /**
     * 人為造一個crash
     */
    private void childThreadCrash() {
        new Thread(new Runnable() {
            @Override
            public void run() {
//              tvMsg.setText("人為造一個crash");
                int i = 1 / 0;
                Log.d("MainActivity", "i:" + i);
            }
        }).start();
    }

執行一下效果看看:

在這里插入圖片描述

不出意料,確實是直接crash的,那這個時候該怎么辦呢?

剛才說的Thread.currentThread().setUncaughtExceptionHandler(new CrashHandler());似乎也不行,這是設定當前 Thread 的方法,總不能給每個 Thread 都設定一個吧,這肯定不可取,不過這時 Thead 的全域靜態的 UncaughtExceptionHandler 物件就派上用場了

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

ThreadGroup 里面最侄訓呼叫到他的方法,一開始在 RunTimeInit 里面初始化的,既然這樣,那我們直接覆寫這個物件就可以

/**
 * Created by lxb on 2020/12/11
 * 全域捕獲例外
 */
public class CatchExceptionApp extends Application {

    private static final String TAG = "CatchExceptionApp";
    public static CatchExceptionApp mApp;

    @Override
    public void onCreate() {
        super.onCreate();
        mApp = this;
        Handler handler = new Handler(getMainLooper());
        handler.post(new Runnable() {
            @Override
            public void run() {
                // 未捕獲的例外導致APP Crash 后,通過while 讓自己創建的loop 再次作業
                while (true) {
                    //給自己創建的loop 添加 try catch ,一旦外層loop(系統奔潰)檢測到這個事件,然后執行我們自己的loop回圈
                    try {
                        Looper.loop();
                    } catch (Exception e) {
                        e.printStackTrace();
                        Toast.makeText(CatchExceptionApp.this,
                                "main-Thread 拋出了例外",
                                Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
        // 子執行緒Crash,需要添加這個
        Thread.setDefaultUncaughtExceptionHandler(new CrashHandler());
    }
}

這樣就解決了子執行緒拋出例外而crash的問題了,

總結

今天主要就說了一件事:如何捕獲程式中的例外不讓APP崩潰,從而給用戶帶來最好的體驗,

主要有以下做法:

1、通過在主執行緒里面發送一個訊息,捕獲主執行緒的例外,并在例外發生后繼續呼叫Looper.loop方法,使得主執行緒繼續處理訊息,
2、對于子執行緒的例外,可以通過Thread.setDefaultUncaughtExceptionHandler來攔截,并且子執行緒的停止不會給用戶帶來感知,
3、對于在生命周期內發生的例外,可以通過替換ActivityThread.mH.mCallback的方法來捕獲,并且通過token來結束Activity或者直接殺死行程,

第三點文中未介紹,感興趣的小伙伴可以自行查閱總結,

可能有的朋友會問,為什么要讓程式不崩潰呢?會有哪些情況需要我們進行這樣操作呢?

其實還是有很多時候,有些例外我們無法預料或者給用戶帶來幾乎是無感知的例外,比如:

  • 系統的一些bug
  • 第三方庫的一些bug
  • 不同廠商的手機帶來的一些bug

等等這些情況,我們就可以通過這樣的操作來讓APP犧牲掉這部分的功能來維護系統的穩定性,我們現在的需求采用這種方法就很符合,其他小伙伴可以根據自己App的需求,不過建議最好的方式就是控制代碼質量,盡量減少 crash 的發生,

文章參考

https://www.jianshu.com/p/37f363308d5f
https://developer.aliyun.com/article/63992
https://www.jianshu.com/p/4fa8a9b814b1
https://mp.weixin.qq.com/s/ZzkgnhalwBv9yAwHj8jQVA

知識拓展

ActivityThread和android應用啟動
深入聊聊Android訊息機制中的訊息佇列的設計
Android中為什么主執行緒不會因為Looper.loop()里的死回圈卡死?
震驚!Android子執行緒也能修改UI?

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/240559.html

標籤:其他

上一篇:Android 進階——一次偶現的原生Settings 記憶體泄漏導致的系統重啟的定位和解決

下一篇:JNI原理 模仿System.loadLibrary和dlopen使用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more