美文网首页
Android:解决程序运行黑屏或白屏的问题

Android:解决程序运行黑屏或白屏的问题

作者: rechen | 来源:发表于2018-10-18 09:54 被阅读0次

1. 解决Launcher点击图标到Application的onCreate方法调用期间的白屏问题

有两种解决方案

  1. 将启动的白屏替换为自定义的图片
  2. 将启动的白屏设置为透明的

首先定义两个主题,分别是自定义图片背景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方法之前,设置Theme为Application的AppTheme,不然可能会报错

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

2. 解决Application安装Dex黑屏的问题

解决问题的原文

  1. 第一次启动的时候,判断是否已经加载过second dex,如果加载过,则直接走正常的逻辑,否则进入第2步
  2. 如果没有加载过,则新开一个进程加载,原进程进入阻塞状态,并且轮询加载是否完成,因为原进程已经不是前台进程了,所以不会出现ANR
  3. 新开的进程定制呈现的界面,并且开启子线程加载dex,加载完成后更新加载状态,结束当前界面和当前进程
  4. 原进程轮询到加载完成状态后,继续走正常的逻辑

具体实现在第4节讲解

3. 引入MultiDex的步骤

  1. 添加依赖,解决5.0之下的Dalvik虚拟机不支持分包的问题
implementation 'com.android.support:multidex:1.0.1'
  1. 开启分包,指定需要放入main dex的类
defaultConfig {
    ………………
    multiDexEnabled true
    multiDexKeepProguard file("keep_in_main_dex.txt")
}
  1. 分包规则文件keep_in_main_dex.txt,难点在于找出哪些类必须在main dex中
# 这个文件控制哪些需要强制放入主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中加载完成
 * 这个类相关的代码只能在主dex中,不能在其他dex中
 * 所以,不要引用其他的类(以防未出现在主dex中,引发崩溃)
 * 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虚拟机
     * false Davlik虚拟机
     */
    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中加载完成
 * 这个类相关的代码只能在主dex中,不能在其他dex中
 * 所以,不要引用其他的类(以防未出现在主dex中,引发崩溃)
 * 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的进程
<activity android:name=".multidex.MultiDexInstallActivity"
            android:process=":dexInstall"/>
  1. 在指定dex分配规则文件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安装时间过长导致的黑屏问题。
        if (DexInstallHelper.isDexInstallProcess(base)) {
            return;
        }
        // if VM has multi dex support, MultiDex support library is disabled
        if (!DexInstallHelper.isVMMultiDexCapable()) {//如果是davlik虚拟机
            if (!DexInstallHelper.isMultiDexInstalled(base)) {
                DexInstallHelper.waitForDexInstall(base);//block当前进程
            }
        }
    }

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

        //必须放在前面,解决MultiDex.install安装时间过长导致的黑屏问题。
        if (DexInstallHelper.isDexInstallProcess(this)) {
            return;
        }
        
        //开始正常的逻辑
        initNetworkModule();
        initGTModule();
    }

}

相关文章

网友评论

      本文标题:Android:解决程序运行黑屏或白屏的问题

      本文链接:https://www.haomeiwen.com/subject/rfljzftx.html