主頁 > 移動端開發 > 深入理解 Android 中的各種 Context

深入理解 Android 中的各種 Context

2020-09-14 06:23:18 移動端開發

前言

網上關于 Context 的文章也已經有不少了,比如值得參考的有:

Android Context完全決議,你所不知道的Context的各種細節

Android Context 到底是什么?

但看了一下,發現還有值得討論的地方,比如這個等式:

Context個數 = Service 個數 + Activity 個數 + 1

老實說,我不明白這個等式有什么意義,而且還是錯的,首先多行程情況下,Application 物件就不止一個;其次,Activity、Service、Application 繼承自 ContextWrapper,它們自己就是一個 Context,里面又有一個 Base Context;最后,還有各種 outer context、display context 什么的,這部分沒深入研究過,但 Context 的數量絕對大于上述等式的兩倍了,

上面這部分算一個討論,下面正式進入正題,

Context 家族

Context 本身是一個抽象類,主要實作類為 ContextImpl,另外有子類 ContextWrapper 和 ContextThemeWrapper,這兩個子類都是 Context 的代理類,主要區別是 ContextThemeWrapper 有自己的主題資源,它們繼承關系如下:

image

Context 有什么用?

如果要弄清楚 “某個類有什么用” 這樣的問題,其實很簡單,看一下它提供了什么介面就知道了,下面列舉一些主要的:

/**
* Interface to global information about an application environment.  This is
* an abstract class whose implementation is provided by
* the Android system.  It
* allows access to application-specific resources and classes, as well as
* up-calls for application-level operations such as launching activities,
* broadcasting and receiving intents, etc.
*/
public abstract class Context {
    
    // 四大組件相關
    public abstract void startActivity(@RequiresPermission Intent intent);
    public abstract void sendBroadcast(@RequiresPermission Intent intent);
    public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver,
                                            IntentFilter filter);
    public abstract void unregisterReceiver(BroadcastReceiver receiver);
    public abstract ComponentName startService(Intent service);
    public abstract boolean stopService(Intent service);
    public abstract boolean bindService(@RequiresPermission Intent service,
            @NonNull ServiceConnection conn, @BindServiceFlags int flags);
    public abstract void unbindService(@NonNull ServiceConnection conn);
    public abstract ContentResolver getContentResolver();
    
    // 獲取系統/應用資源
    public abstract AssetManager getAssets();
    public abstract Resources getResources();
    public abstract PackageManager getPackageManager();
    public abstract Context getApplicationContext();
    public abstract ClassLoader getClassLoader();
    public final @Nullable <T> T getSystemService(@NonNull Class<T> serviceClass) { ... }
    
    public final String getString(@StringRes int resId) { ... }
    public final int getColor(@ColorRes int id) { ... }
    public final Drawable getDrawable(@DrawableRes int id) { ... }
    public abstract Resources.Theme getTheme();
    public abstract void setTheme(@StyleRes int resid);
    public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { ... }
    
    // 獲取應用相關資訊
    public abstract ApplicationInfo getApplicationInfo();
    public abstract String getPackageName();
    public abstract Looper getMainLooper();
    public abstract int checkPermission(@NonNull String permission, int pid, int uid);
    
    // 檔案相關
    public abstract File getSharedPreferencesPath(String name);
    public abstract File getDataDir();
    public abstract boolean deleteFile(String name);
    public abstract File getExternalFilesDir(@Nullable String type);
    public abstract File getCacheDir();
    ...
    public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
    public abstract boolean deleteSharedPreferences(String name);
    
    // 資料庫相關
    public abstract SQLiteDatabase openOrCreateDatabase(...);
    public abstract boolean deleteDatabase(String name);
    public abstract File getDatabasePath(String name);
    ...
    
    // 其它
    public void registerComponentCallbacks(ComponentCallbacks callback) { ... }
    public void unregisterComponentCallbacks(ComponentCallbacks callback) { ... }
    ...
}

public interface ComponentCallbacks {
    void onConfigurationChanged(Configuration newConfig);
    void onLowMemory();
}

結合注釋,可以發現,Context 就相當于 Application 的大管家,主要負責:

  1. 四大組件的互動,包括啟動 Activity、Broadcast、Service,獲取 ContentResolver 等
  2. 獲取系統/應用資源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
  3. 檔案,包括獲取快取檔案夾、洗掉檔案、SharedPreference 相關等
  4. 資料庫(SQLite)相關,包括打開資料庫、洗掉資料庫、獲取資料庫路徑等
  5. 其它輔助功能,比如設定 ComponentCallbacks,即監聽配置資訊改變、記憶體不足等事件的發生

ContextImpl 、ContextWrapper、ContextThemeWrapper 有什么區別?

ContextWrapper

先看 ContextWrapper:

/**
* Proxying implementation of Context that simply delegates all of its calls to
* another Context.  Can be subclassed to modify behavior without changing
* the original Context.
*/
public class ContextWrapper extends Context {
    // 注意這個成員
    Context mBase; 

    public ContextWrapper(Context base) {
        mBase = base;
    }
    
    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }
    
    // 這就是經常讓人產生疑惑的  Base Context 了
    public Context getBaseContext() {
        return mBase;
    }

    // 下面這些方法全都直接通過 mBase 完成
    @Override
    public AssetManager getAssets() {
        return mBase.getAssets();
    }

    @Override
    public Resources getResources() {
        return mBase.getResources();
    }

    @Override
    public PackageManager getPackageManager() {
        return mBase.getPackageManager();
    }
    
    ...
    
}

可以看到,ContextWrapper 實際上就是 Context 的代理類而已,所有的操作都是通過內部成員 mBase 完成的,另外,Activity、Service 的 getBaseContext 回傳的就是這個 mBase,

ContextThemeWrapper

接著看 ContextThemeWrapper,這個類的代碼并不多,主要看 Resource 和 Theme 相關的:

/**
* A context wrapper that allows you to modify or replace the theme of the
* wrapped context.
*/
public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    private Configuration mOverrideConfiguration;
    private Resources mResources;

    public ContextThemeWrapper() {
        super(null);
    }

    public ContextThemeWrapper(Context base, @StyleRes int themeResId) {
        super(base);
        mThemeResource = themeResId;
    }

    public ContextThemeWrapper(Context base, Resources.Theme theme) {
        super(base);
        mTheme = theme;
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
    }

    // 在 Recource 初始化之前,傳入配置資訊
    public void applyOverrideConfiguration(Configuration overrideConfiguration) {
        if (mResources != null) {
            throw new IllegalStateException(...);
        }
        if (mOverrideConfiguration != null) {
            throw new IllegalStateException(...);
        }
        mOverrideConfiguration = new Configuration(overrideConfiguration);
    }

    public Configuration getOverrideConfiguration() {
        return mOverrideConfiguration;
    }

    // 沒有重寫 setResource,即 setResource 行為和父類一樣
    @Override
    public Resources getResources() {
        return getResourcesInternal();
    }

    private Resources getResourcesInternal() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                // 根據配置資訊初始化 Resource
                // 注意,這里創建了另一個和 Base Context 不同的 Resource
                final Context resContext = createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }

    @Override
    public void setTheme(int resid) {
        if (mThemeResource != resid) {
            mThemeResource = resid;
            initializeTheme();
        }
    }
    
    private void initializeTheme() {
        final boolean first = mTheme == null;
        if (first) {
            // 根據 Resource 獲取 Theme
            mTheme = getResources().newTheme();
            // 復制內容
            final Resources.Theme theme = getBaseContext().getTheme();
            if (theme != null) {
                mTheme.setTo(theme);
            }
        }
        onApplyThemeResource(mTheme, mThemeResource, first);
    }

    protected void onApplyThemeResource(Resources.Theme theme, int resId, boolean first) {
        theme.applyStyle(resId, true);
    }

    @Override
    public Resources.Theme getTheme() {
        // 只會初始化一次
        if (mTheme != null) {
            return mTheme;
        }

        mThemeResource = Resources.selectDefaultTheme(mThemeResource,
                getApplicationInfo().targetSdkVersion);
        initializeTheme();

        return mTheme;
    }

    ...

}

結合注釋及原始碼,可以發現,相比 ContextWrapper,ContextThemeWrapper 有自己的另外 Resource 以及 Theme 成員,并且可以傳入配置資訊以初始化自己的 Resource 及 Theme,即 Resource 以及 Theme 相關的行為不再是直接呼叫 mBase 的方法了,也就說,ContextThemeWrapper 和它的 mBase 成員在 Resource 以及 Theme 相關的行為上是不同的,

ContextImpl

下面看一下 ContextImpl 有關 Theme 以及 Resource 的部分,以分析它和 ContextThemeWrapper 的區別:

/**
* Common implementation of Context API, which provides the base
* context object for Activity and other application components.
*/
class ContextImpl extends Context {

    private int mThemeResource = 0;
    private Resources.Theme mTheme = null;
    private @NonNull Resources mResources;
    
    // 用于創建 Activity Context
    static ContextImpl createActivityContext(...) {
        ContextImpl context = new ContextImpl(...);
        context.setResources(resourcesManager.createBaseActivityResources(...));
        return context;
    }
    
    // 用于創建 Application Context、Service Context
    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
        ContextImpl context = new ContextImpl(...);
        context.setResources(packageInfo.getResources());
        return context;
    }
    
    private static Resources createResources(...) {
        return ResourcesManager.getInstance().getResources(...);
    }

    // ContextThemeWrapper 沒有重寫父類的 setResources
    // 因此會呼叫 mBase 的 setResources,即和 ContextImpl 的行為一樣
    void setResources(Resources r) {
        if (r instanceof CompatResources) {
            ((CompatResources) r).setContext(this);
        }
        mResources = r;
    }
    
    @Override
    public Resources getResources() {
        return mResources;
    }
    
    
    /* ---------- 主題相關 ------------ */
    
    @Override
    public void setTheme(int resId) {
        synchronized (mSync) {
            if (mThemeResource != resId) {
                mThemeResource = resId;
                initializeTheme();
            }
        }
    }
    
    // 直接創建一個 Themem 物件,相比 ContextThemeWrapper,少了一部分內容
    private void initializeTheme() {
        if (mTheme == null) {
            mTheme = mResources.newTheme();
        }
        mTheme.applyStyle(mThemeResource, true);
    }

    @Override
    public Resources.Theme getTheme() {
        synchronized (mSync) {
            // 和 ContextThemeWrapper 基本一樣
            if (mTheme != null) {
                return mTheme;
            }

            mThemeResource = Resources.selectDefaultTheme(mThemeResource,
                    getOuterContext().getApplicationInfo().targetSdkVersion);
            initializeTheme();

            return mTheme;
        }
    }

}

從代碼中可以看出,ContextImpl 和 ContextThemeWrapper 最大的區別就是沒有一個 Configuration 而已,其它的行為大致是一樣的,另外,ContextImpl 可以用于創建 Activity、Service 以及 Application 的 mBase 成員,這個 Base Context 時除了引數不同,它們的 Resource 也不同,需要注意的是,createActivityContext 等方法中 setResource 是 mBase 自己呼叫的,Activity、Service 以及 Application 本身并沒有執行 setResource,

小結

  1. ContextWrapper、ContextThemeWrapper 都是 Context 的代理類,二者的區別在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以傳入自己的配置初始化

  2. ContextImpl 是 Context 的主要實作類,Activity、Service 和 Application 的 Base Context 都是由它創建的,即 ContextWrapper 代理的就是 ContextImpl 物件本身

  3. ContextImpl 和 ContextThemeWrapper 的主要區別是, ContextThemeWrapper 有 Configuration 物件,Resource 可以根據這個物件來初始化

  4. Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同

Activity Context、Service Context、Application Context、Base Context 有什么區別?

Activity Context

先看 Activity,Activity 在啟動時,最侄訓執行 ActivityThread 的 performLaunchActivitiy:

public final class ActivityThread {

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        // 這個 Context 將會作為 Activity 的 Base Context
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            ClassLoader cl = appContext.getClassLoader();
            // 創建 Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
        } catch (Exception e) {
            ...
        }

        try {
            // 創建 Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                // 初始化 Activity,注意引數 appContext
                activity.attach(appContext, ...);
                ...
            }

        } catch (...) {
            ...
        }

        return activity;
    }
    
    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
        ContextImpl appContext = ContextImpl.createActivityContext(...);
        ...
    }
    
}

可以看到,Activity 的 Base Context 就是上面分析過的 ContextImpl 的 createActivityContext 創建的,

同時,Service 的 Base Context 的創建程序和 Application 一樣,呼叫的都是 ContextImpl 的 createAppContext,即 Service Context 和 Application Context 的 Resource 是相同的,因此這里跳過 Service,下面看一下 Application Context,

Application Context

在上面 ActivityThread 的 performLaunchActivity 方法中,可以看到一個 makeApplication 的呼叫,它是 LoaedApk 的方法:

public final class LoadedApk {

    private Application mApplication;

    public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
        if (mApplication != null) {
            return mApplication;
        }

        Application app = null;

        try {
            // 創建 Base Context
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
            // 創建 Application 并設定 Base Context
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);
            appContext.setOuterContext(app);
        } catch (Exception e) {
            ...
        }
        // Application 創建成功,賦值給 mApplication
        mApplication = app;
        
        ...
        
        return app;
    }
    
    // 獲取 mApplication
    Application getApplication() {
        return mApplication;
    }
    
}
public class Application extends ContextWrapper implements ComponentCallbacks2 {
    /* package */ final void attach(Context context) {
        // 呼叫父類的 attachBaseContext 以設定 mBase
        attachBaseContext(context); 
    }
}

可以看到,Instrumentation 是使用反射的方法創建 Application 物件,創建完畢后,會執行 Application 的 attach 方法設定 mBase 成員,

Application 及其 Base Context 的創建程序我們了解了,接下來看一下 getApplicationContext 的實作:

class ContextImpl extends Context {

    ActivityThread mMainThread;
    LoadedApk mPackageInfo;
    
    @Override
    public Context getApplicationContext() {
        return (mPackageInfo != null) ?
                mPackageInfo.getApplication() : mMainThread.getApplication();
    }
    
}

從代碼中可以看出,getApplicationContext 的回傳值可能有兩個:第一個是 LoadedApk 的 getApplication 方法,這個方法的回傳值就是剛剛創建的 Application 物件;第二個是 ActivityThread 的 getApplication 方法:

public final class ActivityThread {

    Application mInitialApplication;
    
    public Application getApplication() {
        return mInitialApplication;
    }
    
    public static ActivityThread systemMain() {
        // 創建 ActivityThread
        ActivityThread thread = new ActivityThread();
        thread.attach(true);
        return thread;
    }
    
    private void attach(boolean system) {
        mSystemThread = system;
        if (!system) {
            ...
        } else {
            try {
                mInstrumentation = new Instrumentation();
                // 注意引數 getSystemContext().mPackageInfo
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                // 創建 Application
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
            } catch (Exception e) {
                ...
            }
        }
        ...
    }
    
}

ActivityThread 中的 mInitialApplication 是在 systemMain 方法執行時創建的,而這個方法又是 SystemServer 啟動時呼叫的,結合引數 getSystemContext().mPackageInfo,因此個人推測 mInitialApplication 對應的是系統的某個 apk,即系統級別的 Application,但具體是不是這樣,目前還沒有深入研究過,有興趣的可以自己研究,

為什么不推薦使用 Base Context?

一般情況下,使用代理而不直接使用某個物件,目的可能有兩個:

  1. 定制自己的行為
  2. 不影響原物件

其中 Servcie 和 Application 的父類 ContextWrapper 完全沒有自定義的行為,而 Activity 的父類 ContextThemeWrapper 則自定義了 Resource 以及 Theme 的相關行為,因此,個人理解:

  1. 對于 Service 和 Application 而言,不推薦使用 Base Context,是擔心用戶修改了 Base Context 而導致錯誤的發生
  2. 對于 Activity 而言,除了擔心用戶的修改之外,Base Context 和 Activity 本身對于 Reource 以及 Theme 的相關行為是不同的(如果應用了 Configuration 的話),使用 Base Context 可能會出現無法預期的現象

對于 Activity 的 getResource 問題,我寫了一份代碼來驗證:

public class MainActivity extends AppCompatActivity {

    private Configuration mOverrideConfiguration;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "getResources: " + getResources() + ", getBaseContext().getResources():"
                + getBaseContext().getResources());
    }
    
    // 因為 Android 會在 onCreate 之前自動呼叫 getResource
    // 因此需要在這里執行 applyOverrideConfiguration
    @Override
    public Resources getResources() {
        if (mOverrideConfiguration == null) {
            mOverrideConfiguration = new Configuration();
            applyOverrideConfiguration(mOverrideConfiguration);
        }
        return super.getResources();
    }
    
}

輸出(我用的是小米手機):

getResources: android.content.res.MiuiResources@3c660a7, 
getBaseContext().getResources():android.content.res.MiuiResources@5143954

可以看到,就像原始碼顯示的那樣,應用了 Configuration 之后,Activity 的 getResource 方法回傳的和 getBaseContext().getResources() 方法回傳的不是同一個物件

小結

  1. Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 創建的,且創建的都是 ContextImpl 物件,即它們都是 ContextImpl 的代理類
  2. getApplicationContext 回傳的就是 Application 物件本身,一般情況下它對應的是應用本身的 Application 物件,但也可能是系統的某個 Application
  3. 對于 Service 和 Application 而言,不推薦使用 Base Context,是擔心用戶修改了 Base Context 而導致錯誤的發生
  4. 對于 Activity 而言,除了擔心用戶的修改之外,Base Context 和 Activity 本身對于 Reource 以及 Theme 的相關行為是不同的(如果應用了 Configuration 的話),使用 Base Context 可能會出現無法預期的現象

總結

Context 的繼承關系如下:

image

Context 相當于 Application 的大管家,主要負責:

  1. 四大組件的互動,包括啟動 Activity、Broadcast、Service,獲取 ContentResolver 等
  2. 獲取系統/應用資源,包括 AssetManager、PackageManager、Resources、System Service 以及 color、string、drawable 等
  3. 檔案,包括獲取快取檔案夾、洗掉檔案、SharedPreference 相關等
  4. 資料庫(SQLite)相關,包括打開資料庫、洗掉資料庫、獲取資料庫路徑等
  5. 其它輔助功能,比如設定 ComponentCallbacks,即監聽配置資訊改變、記憶體不足等事件的發生

ContextWrapper、ContextThemeWrapper、ContextImpl 的區別:

  1. ContextWrapper、ContextThemeWrapper 都是 Context 的代理類,二者的區別在于 ContextThemeWrapper 有自己的 Theme 以及 Resource,并且 Resource 可以傳入自己的配置初始化
  2. ContextImpl 是 Context 的主要實作類,Activity、Service 和 Application 的 Base Context 都是由它創建的,即 ContextWrapper 代理的就是 ContextImpl 物件本身
  3. ContextImpl 和 ContextThemeWrapper 的主要區別是, ContextThemeWrapper 有 Configuration 物件,Resource 可以根據這個物件來初始化

Activity Context、Service Context、Application Context、Base Context 的區別:

  1. Activity、Service 和 Application 的 Base Context 都是由 ContextImpl 創建的,且創建的都是 ContextImpl 物件,即它們都是 ContextImpl 的代理類
  2. Service 和 Application 使用同一個 Recource,和 Activity 使用的 Resource 不同
  3. getApplicationContext 回傳的就是 Application 物件本身,一般情況下它對應的是應用本身的 Application 物件,但也可能是系統的某個 Application

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

標籤:Android

上一篇:安卓接入融云IM連接不上??

下一篇:android的APT技術

標籤雲
其他(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