Android:解決程序運行黑屏或白屏的問題

1. 解決Launcher點擊圖標(biāo)到Application的onCreate方法調(diào)用期間的白屏問題

有兩種解決方案

  1. 將啟動的白屏替換為自定義的圖片
  2. 將啟動的白屏設(shè)置為透明的

首先定義兩個主題,分別是自定義圖片背景AppCustomBackground和透明背景AppTranslucentBackground

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

<!-- 自定義啟動圖片主題 -->
<style name="AppCustomBackground" parent="AppTheme">
    <item name="android:windowBackground">@mipmap/ic_launcher</item>
</style>

<!-- 透明啟動頁主題 -->
<style name="AppTranslucentBackground" parent="android:Theme.Translucent.NoTitleBar.Fullscreen">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
</style>

然后在AndroidManifest中指定第一個Activity的Theme為想要的樣式

<activity android:name=".MainActivity" android:theme="@style/AppCustomBackground">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

最后要在Activity的super.onCreate方法之前,設(shè)置Theme為Application的AppTheme,不然可能會報錯

@Override
protected void onCreate(Bundle savedInstanceState) {
    setTheme(R.style.AppTheme);
    super.onCreate(savedInstanceState);
}

2. 解決Application安裝Dex黑屏的問題

解決問題的原文

  1. 第一次啟動的時候,判斷是否已經(jīng)加載過second dex,如果加載過,則直接走正常的邏輯,否則進(jìn)入第2步
  2. 如果沒有加載過,則新開一個進(jìn)程加載,原進(jìn)程進(jìn)入阻塞狀態(tài),并且輪詢加載是否完成,因為原進(jìn)程已經(jīng)不是前臺進(jìn)程了,所以不會出現(xiàn)ANR
  3. 新開的進(jìn)程定制呈現(xiàn)的界面,并且開啟子線程加載dex,加載完成后更新加載狀態(tài),結(jié)束當(dāng)前界面和當(dāng)前進(jìn)程
  4. 原進(jìn)程輪詢到加載完成狀態(tài)后,繼續(xù)走正常的邏輯

具體實現(xiàn)在第4節(jié)講解

3. 引入MultiDex的步驟

  1. 添加依賴,解決5.0之下的Dalvik虛擬機(jī)不支持分包的問題
implementation 'com.android.support:multidex:1.0.1'
  1. 開啟分包,指定需要放入main dex的類
defaultConfig {
    ………………
    multiDexEnabled true
    multiDexKeepProguard file("keep_in_main_dex.txt")
}
  1. 分包規(guī)則文件keep_in_main_dex.txt,難點在于找出哪些類必須在main dex中
# 這個文件控制哪些需要強(qiáng)制放入主dex
# 自行查找哪些地方需要在maindex,會報錯
-keep class com.hyperion.networklib.**{*;}
-keep class com.hyperion.gtlib.**{*;}
-keep class com.example.jm.image.RoundImageView{*;}

# 四大組件和support必須在maindex中
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.preference.Preference
-keep class android.support.**{*;}
  1. 在Application的attachBaseContext方法中安裝從dex
MultiDex.install(base)

4. 解決安裝Dex時黑屏的步驟

  1. 引入工具類DexInstallHelper
/**
 * Created by yongc on 17/6/13.
 * 這個類在主dex中加載完成
 * 這個類相關(guān)的代碼只能在主dex中,不能在其他dex中
 * 所以,不要引用其他的類(以防未出現(xiàn)在主dex中,引發(fā)崩潰)
 * https://github.com/shensky711/MultiDex
 */
public class DexInstallHelper {

    private static final String KEY_DEX2_SHA1 = "dex2-SHA1-Digest";

    /**
     * @return {@code true} if current process is dex install process
     */
    public static boolean isDexInstallProcess(Context context) {
        return isDexInstallProcess(context, getDexInstallActivity());
    }

    public static boolean isDexInstallProcess(Context context, Class<? extends Activity> activityClass) {

        PackageManager packageManager = context.getPackageManager();
        PackageInfo packageInfo;
        try {
            packageInfo = packageManager.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }

        String mainProcess = packageInfo.applicationInfo.processName;
        ComponentName component = new ComponentName(context, activityClass);
        ActivityInfo activityInfo;
        try {
            activityInfo = packageManager.getActivityInfo(component, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }

        if (activityInfo.processName.equals(mainProcess)) {
            return false;
        } else {
            int myPid = Process.myPid();
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            ActivityManager.RunningAppProcessInfo myProcess = null;
            List<ActivityManager.RunningAppProcessInfo> runningProcesses = activityManager.getRunningAppProcesses();
            if (runningProcesses != null) {
                for (ActivityManager.RunningAppProcessInfo process : runningProcesses) {
                    if (process.pid == myPid) {
                        myProcess = process;
                        break;
                    }
                }
            }
            return myProcess != null && myProcess.processName.equals(activityInfo.processName);
        }
    }

    /**
     * @return {@code true} if VM has multi dex support
     * true Art虛擬機(jī)
     * false Davlik虛擬機(jī)
     */
    public static boolean isVMMultiDexCapable() {
        boolean isMultiDexCapable = false;
        String versionString = System.getProperty("java.vm.version");
        Log.i("JM_BOOT","isVMMultiDexCapable:versionString=" + versionString);
        if (versionString != null) {
            Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString);
            if (matcher.matches()) {
                try {
                    int e = Integer.parseInt(matcher.group(1));
                    int minor = Integer.parseInt(matcher.group(2));
                    isMultiDexCapable = e > 2 || e == 2 && minor >= 1;
                } catch (NumberFormatException ignore) {
                }
            }
        }

        return isMultiDexCapable;
    }

    /**
     * @return {@code true} if we already install multi dex before
     */
    public static boolean isMultiDexInstalled(Context context) {
        String flag = get2thDexSHA1(context);
        String preferencesName= getPreferencesName(context);
        SharedPreferences sp = context.getSharedPreferences(preferencesName, MODE_MULTI_PROCESS);
        String saveValue = sp.getString(KEY_DEX2_SHA1, "");
        Log.i("JM_BOOT", String.format("sp name = %s,value= %s",preferencesName,saveValue));
        return flag.equals(saveValue);
    }

    /**
     * wait until multi dex is install complete, attention, it would block current process
     */
    public static void waitForDexInstall(Context context) {
        Intent intent = new Intent();
        ComponentName componentName = new ComponentName(context.getPackageName(), getDexInstallActivity().getName());
        intent.setComponent(componentName);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent);

        long waitTime = TimeUnit.SECONDS.toMillis(20);
        long startWait = System.currentTimeMillis();
        while (!isMultiDexInstalled(context)) {
            try {
                long nowWait = System.currentTimeMillis() - startWait;
                if (nowWait >= waitTime) {
                    break;
                }
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @SuppressLint("CommitPrefEdits")
    public static void markInstallFinish(Context context) {
        SharedPreferences sp = context.getSharedPreferences(getPreferencesName(context), MODE_MULTI_PROCESS);
        // do not use apply here
        sp.edit().putString(KEY_DEX2_SHA1, get2thDexSHA1(context)).commit();
    }

    private static Class<? extends Activity> getDexInstallActivity() {
        return MultiDexInstallActivity.class;
    }

    private static String get2thDexSHA1(Context context) {
        ApplicationInfo info = context.getApplicationInfo();
        String source = info.sourceDir;
        Log.i("JM_BOOT","source=" + source);
        try {
            JarFile jar = new JarFile(source);
            Manifest mf = jar.getManifest();
            Map<String, Attributes> map = mf.getEntries();
            logManifestInfo4Debug(map);
            Attributes a = map.get("classes2.dex");
            if (a == null) {
                return "";
            }
            if (a.containsKey("SHA-256-Digest")){
                return a.getValue("SHA-256-Digest");
            } else if(a.containsKey("SHA1-Digest")){
                return a.getValue("SHA1-Digest");
            }
            return "";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    private static void logManifestInfo4Debug(Map<String, Attributes> infoMap){
        if(BuildConfig.DEBUG && infoMap != null){//如果apk打包的時候是debug包,則打印日志
            Set<Map.Entry<String,Attributes>> entrys = infoMap.entrySet();
            if(entrys != null){
                Log.i("JM_BOOT","manifestInfo log start");
                for(Map.Entry entry : entrys){
                    if(entry != null){
                        String key = (String)entry.getKey();
                        if(!"AndroidManifest.xml".equals(key) && !"classes2.dex".equals(key)){
                            continue;
                        }
                        Log.i("JM_BOOT","manifestInfo key=" + key);
                        Attributes value = (Attributes)entry.getValue();
                        if(value != null) {
                            Set valueKeySet = value.keySet();
                            if (valueKeySet != null) {
                                for (Object valueKey : valueKeySet) {
                                    Log.i("JM_BOOT", "manifestInfo ------ valueKey=" + valueKey +
                                            ",valueValue=" + value.get(valueKey));
                                }
                            }
                        }
                    }
                }
                Log.i("JM_BOOT","manifestInfo log end");
            }
        }
    }

    private static String getPreferencesName(Context context) {
        PackageInfo packageInfo = getPackageInfo(context);
        return context.getPackageName() + "." + packageInfo.versionName;
    }

    private static PackageInfo getPackageInfo(Context context) {
        PackageManager pm = context.getPackageManager();
        try {
            return pm.getPackageInfo(context.getPackageName(), 0);
        } catch (PackageManager.NameNotFoundException e) {
        }
        return new PackageInfo();
    }
}
  1. 引入定制加載界面MultiDexInstallActivity
/**
 * Created by yongc on 17/6/13.
 * 這個類在主dex中加載完成
 * 這個類相關(guān)的代碼只能在主dex中,不能在其他dex中
 * 所以,不要引用其他的類(以防未出現(xiàn)在主dex中,引發(fā)崩潰)
 * https://github.com/shensky711/MultiDex
 */
public class MultiDexInstallActivity extends Activity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multidexinstall);

        new Thread(new DexInstall(this)).start();
    }

    @Override
    public void onBackPressed() {
        //do nothing
    }

    static class DexInstall implements Runnable {

        private Activity mActivity;

        public DexInstall(Activity activity) {
            if (activity == null) {
                throw new IllegalArgumentException("mActivity == null");
            }
            this.mActivity = activity;
        }

        @Override
        public void run() {
            MultiDex.install(mActivity);
            DexInstallHelper.markInstallFinish(mActivity.getApplicationContext());
            mActivity.finish();
            mActivity = null;
            System.exit(0);
        }
    }

}
  1. 在AndroidManifest指定MultiDexInstallActivity的進(jìn)程
<activity android:name=".multidex.MultiDexInstallActivity"
            android:process=":dexInstall"/>
  1. 在指定dex分配規(guī)則文件keep_in_main_dex.txt中,添加工具類DexInstallHelper
# multidex https://github.com/shensky711/MultiDex
-keep class com.example.jm.multidex.DexInstallHelper{*;}
  1. 在Application中,添加分包加載處理
public class App extends Application {

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

        //必須放在前面,解決MultiDex.install安裝時間過長導(dǎo)致的黑屏問題。
        if (DexInstallHelper.isDexInstallProcess(base)) {
            return;
        }
        // if VM has multi dex support, MultiDex support library is disabled
        if (!DexInstallHelper.isVMMultiDexCapable()) {//如果是davlik虛擬機(jī)
            if (!DexInstallHelper.isMultiDexInstalled(base)) {
                DexInstallHelper.waitForDexInstall(base);//block當(dāng)前進(jìn)程
            }
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();

        //必須放在前面,解決MultiDex.install安裝時間過長導(dǎo)致的黑屏問題。
        if (DexInstallHelper.isDexInstallProcess(this)) {
            return;
        }
        
        //開始正常的邏輯
        initNetworkModule();
        initGTModule();
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,962評論 25 709
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 13,959評論 2 59
  • Tinker 熱補丁接入過程中的坑?。。?=============== Tinker 介紹 官方接入說明 gra...
    朱立志閱讀 2,247評論 0 2
  • 前言 最近開發(fā)中我們發(fā)現(xiàn),我們的產(chǎn)品在Android設(shè)備版本低于5.0以下第一次安裝啟動會出現(xiàn)黑屏、ANR等情況。...
    miraclehen閱讀 3,797評論 2 11
  • 敏感與思慮,恐懼與緊張,輕易的自我否定……都源于內(nèi)心深處的脆弱和干涸;換言之,是長期以來給自己重重加碼,焦慮的陰云...
    聽雨來的故事閱讀 306評論 0 0

友情鏈接更多精彩內(nèi)容