Android 深色模式适配

Android 10 开始支持配置深色模式,如果系统是深色主题,但是打开APP又是浅色主题就会显得格格不入。下面介绍几种适配深色模式的方法。

一、forceDarkAllowed

样式中设置 android:forceDarkAllowed 属性,深色主题下系统会自动进行适配。

  1. 新建 values-v29 目录,因为 android:forceDarkAllowed 属性 Android 10开始才有。

  2. 设置 android:forceDarkAllowed 属性为true

  3. 适配效果

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:minWidth="200dp"android:minHeight="100dp"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"android:text="深色模式"/>
</androidx.constraintlayout.widget.ConstraintLayout>

浅色主题:

深色主题:

从布局文件中可以看到,并没有设置任何背景色,但深色主题下,APP自动进行了适配。

这种适配方式十分简单,但是不够美观,无法自定义控件颜色样式,全凭系统控制,并不推荐这种自动化方式实现深色模式。

二、设置深色主题

官方推荐另外一种方法,即分别创建浅色和深色的主题样式。

  1. 新建 values-night 目录,存放深色主题的样式

  2. 适配效果

浅色主题:

深色主题:

forceDarkAllowed 最大的区别在于,深色主题可以手动设置颜色样式。

一些常用的方法:

  • 判断深色主题
  public static boolean isDarkTheme(Context context) {int flag = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;return flag == Configuration.UI_MODE_NIGHT_YES;}
  • 代码中切换深色主题
if (isDarkTheme(MainActivity.this)) {AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);} else {AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);}
  • 禁止界面适配深色主题

Activity 的 configChanges 属性当中配置 uiMode 避免Activity 重新创建,从而阻止界面适配深色主题。

<activityandroid:exported="true"android:name=".MainActivity"android:configChanges="uiMode"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>

这时候虽然界面不会重新创建,但是会触发 onConfigurationChanged 方法回调,可以根据回调做一些处理。

  @Overridepublic void onConfigurationChanged(@NonNull Configuration newConfig) {super.onConfigurationChanged(newConfig);int mSysThemeConfig = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;switch (mSysThemeConfig) {// 亮色主题case Configuration.UI_MODE_NIGHT_NO:break;// 深色主题case Configuration.UI_MODE_NIGHT_YES:break;default:break;}}

三、使用 Android-skin-support

上述两种方式只支持Android 10 系统,且系统切换深色主题界面会重新创建,并不是太灵活。如果想适配Android 10 以下的系统可以使用 Android-skin-support 框架实现。

Android-skin-support 是一个换肤框架,通过加载不同的皮肤包从而实现换肤,深色模式只需要创建对应的深色主题皮肤包,然后替换当前的默认样式就可以实现适配。

它的实现流程大致如下:

  1. 控制View的创建,将所有View替换为对应的SkinxxxView
  2. SkinxxxView中会根据布局中的属性ID值,找到皮肤包中对应的资源进行替换
  3. 动态换肤,即通知所有的SkinXXXView进行更新

3.1 Android View 创建流程

在使用 Android-skin-support 之前,可以先了解下 Android View 创建流程,有利于我们之后使用该库。

从 setContentView 方法开始,它作用就是设置界面布局资源。

 //Activitypublic void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();}

调用的Window中的 setContentView 方法。

    //PhoneWindow@Overridepublic void setContentView(int layoutResID) {......if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene);} else {mLayoutInflater.inflate(layoutResID, mContentParent);}......}

调用 LayoutInflater 的 inflate 方法

    //LayoutInflaterpublic View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();if (DEBUG) {Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("+ Integer.toHexString(resource) + ")");}View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}

可以看到根据布局资源创建一个Xml 解析器进行解析

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {......// Temp is the root view that was found in the xmlfinal View temp = createViewFromTag(root, name, inflaterContext, attrs);......// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);......}}

先通过createViewFromTag方法创建rootView,然后再使用rInflateChildren解析子布局,最终都是通过createView创建View

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {......View view = tryCreateView(parent, name, context, attrs);......
}public final View tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs) {......View view;if (mFactory2 != null) {view = mFactory2.onCreateView(parent, name, context, attrs);} else if (mFactory != null) {view = mFactory.onCreateView(name, context, attrs);} else {view = null;}......}

可以看到如果mFactory2不为空则通过mFactory2来创建View,而mFactory2又是哪里进行初始化的呢?

通过查看代码发现,mFactory2初始化代码如下:

//AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {final AppCompatDelegate delegate = getDelegate();delegate.installViewFactory();delegate.onCreate(savedInstanceState);super.onCreate(savedInstanceState);
}//AppCompatDelegateImpl@Overridepublic void installViewFactory() {LayoutInflater layoutInflater = LayoutInflater.from(mContext);if (layoutInflater.getFactory() == null) {LayoutInflaterCompat.setFactory2(layoutInflater, this);} else {if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"+ " so we can not install AppCompat's");}}}public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {inflater.setFactory2(factory);......}public void setFactory2(Factory2 factory) {//mFactory2 不可以重复设置,否则会直接抛出异常if (mFactorySet) {throw new IllegalStateException("A factory has already been set on this LayoutInflater");}if (factory == null) {throw new NullPointerException("Given factory can not be null");}mFactorySet = true;if (mFactory == null) {mFactory = mFactory2 = factory;} else {mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);}}

上述代码可以发现,mFactory2其实就是AppCompatDelegateImpl,现在我们再看下AppCompatDelegateImpl的 onCreateView方法

@Override
public View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs) {......return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */true, /* Read read app:theme as a fallback at all times for legacy reasons */VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */);
}final View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {......View view = null;// We need to 'inject' our tint aware Views in place of the standard framework versionsswitch (name) {case "TextView":view = createTextView(context, attrs);verifyNotNull(view, name);break;case "ImageView":view = createImageView(context, attrs);verifyNotNull(view, name);break;case "Button":view = createButton(context, attrs);verifyNotNull(view, name);break;case "EditText":view = createEditText(context, attrs);verifyNotNull(view, name);break;......default:// The fallback that allows extending class to take over view inflation// for other tags. Note that we don't check that the result is not-null.// That allows the custom inflater path to fall back on the default one// later in this method.view = createView(context, name, attrs);}......return view;}@NonNullprotected AppCompatTextView createTextView(Context context, AttributeSet attrs) {return new AppCompatTextView(context, attrs);}

上述代码可以看到,创建View其实是通过控件的名称,然后再new对应的控件,通过createTextView可以发现创建的也不是TextView,而是AppCompatTextView,所以只要能够修改mFactory2,就可以控制所有View的创建。

3.2 使用方法

集成 Android-skin-support

implementation 'skin.support:skin-support:4.0.5'                   // skin-supportimplementation 'skin.support:skin-support-appcompat:4.0.5'         // skin-support 基础控件支持implementation 'skin.support:skin-support-design:4.0.5'            // skin-support-design material design 控件支持[可选]implementation 'skin.support:skin-support-cardview:4.0.5'          // skin-support-cardview CardView 控件支持[可选]implementation 'skin.support:skin-support-constraint-layout:4.0.5' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]

初始化

     //初始化换肤框架SkinCompatManager.withoutActivity(this)//添加各类控件的拦截器.addInflater(new SkinAppCompatViewInflater()).addInflater(new SkinConstraintViewInflater()).addInflater(new SkinCardViewInflater()).addInflater(new SkinMaterialViewInflater());//Activity中重写下面方法,可以放到BaseActivity中@NonNull@Overridepublic AppCompatDelegate getDelegate() {return SkinAppCompatDelegateImpl.get(this, this);}

withoutActivity 方法

public static SkinCompatManager withoutActivity(Application application) {init(application);SkinActivityLifecycle.init(application);return sInstance;
}

重点看 SkinActivityLifecycle.init 方法,主要做了两件事情:

  1. 注册Activity生命周期回调,
  2. 替换系统的mFactory2,控制View的创建
public static SkinActivityLifecycle init(Application application) {if (sInstance == null) {synchronized (SkinActivityLifecycle.class) {if (sInstance == null) {sInstance = new SkinActivityLifecycle(application);}}}return sInstance;
}private SkinActivityLifecycle(Application application) {//注册Activity生命周期回调application.registerActivityLifecycleCallbacks(this);//替换系统的mFactory2,控制View的创建installLayoutFactory(application);SkinCompatManager.getInstance().addObserver(getObserver(application));
}private void installLayoutFactory(Context context) {try {LayoutInflater layoutInflater = LayoutInflater.from(context);LayoutInflaterCompat.setFactory2(layoutInflater, getSkinDelegate(context));} catch (Throwable e) {Slog.i("SkinActivity", "A factory has already been set on this LayoutInflater");}
}

getSkinDelegate(context) 方法,初始化并返回 SkinCompatDelegate 对象

    private SkinCompatDelegate getSkinDelegate(Context context) {if (mSkinDelegateMap == null) {mSkinDelegateMap = new WeakHashMap<>();}SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(context);if (mSkinDelegate == null) {mSkinDelegate = SkinCompatDelegate.create(context);mSkinDelegateMap.put(context, mSkinDelegate);}return mSkinDelegate;}

SkinCompatDelegate 实现了LayoutInflater.Factory2 接口,重写 onCreateView 方法,从而控制所有View的创建

@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {//创建ViewView view = createView(parent, name, context, attrs);if (view == null) {return null;}//保存到列表中if (view instanceof SkinCompatSupportable) {mSkinHelpers.add(new WeakReference<>((SkinCompatSupportable) view));}return view;
}public View createView(View parent, final String name, @NonNull Context context,@NonNull AttributeSet attrs) {if (mSkinCompatViewInflater == null) {mSkinCompatViewInflater = new SkinCompatViewInflater();}......return mSkinCompatViewInflater.createView(parent, name, context, attrs);}public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {View view = createViewFromHackInflater(context, name, attrs);if (view == null) {//通过初始化的各个拦截器,根据控件名称创建对应的控件view = createViewFromInflater(context, name, attrs);}if (view == null) {//如果没有找到,就类似系统的实现方法view = createViewFromTag(context, name, attrs);}if (view != null) {// If we have created a view, check it's android:onClickcheckOnClickListener(view, attrs);}return view;}

这里的实现有些类似OkHttp的拦截器,View的创建会经过多个 “ViewInflater”,这边选一个拦截器 SkinConstraintViewInflater 看下内部是怎么实现的。

public class SkinConstraintViewInflater implements SkinLayoutInflater {@Overridepublic View createView(Context context, final String name, AttributeSet attrs) {View view = null;switch (name) {case "androidx.constraintlayout.widget.ConstraintLayout":view = new SkinCompatConstraintLayout(context, attrs);break;default:break;}return view;}
}

代码很简单,就是通过控件名称创建自己的 SkinCompatConstraintLayout 换肤控件,

public class SkinCompatConstraintLayout extends ConstraintLayout implements SkinCompatSupportable {private final SkinCompatBackgroundHelper mBackgroundTintHelper;......public SkinCompatConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mBackgroundTintHelper = new SkinCompatBackgroundHelper(this);mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);}@Overridepublic void setBackgroundResource(int resId) {super.setBackgroundResource(resId);if (mBackgroundTintHelper != null) {mBackgroundTintHelper.onSetBackgroundResource(resId);}}//收到换肤事件,@Overridepublic void applySkin() {if (mBackgroundTintHelper != null) {mBackgroundTintHelper.applySkin();}}
}

SkinCompatConstraintLayout 实现 SkinCompatSupportable接口,触发换肤时会调用applySkin方法替换控件的背景。

加载皮肤包

 //加载皮肤包SkinCompatManager.getInstance().loadSkin(DARK_SKIN_NAME, new SkinLoaderListener() {@Overridepublic void onStart() {}@Overridepublic void onSuccess() {}@Overridepublic void onFailed(String errMsg) {}
}, SKIN_LOADER_STRATEGY_ASSETS);
    /*** 加载皮肤包.** @param skinName 皮肤包名称.* @param listener 皮肤包加载监听.* @param strategy 皮肤包加载策略.SKIN_LOADER_STRATEGY_ASSETS从assets目录加载皮肤包*/
loadSkin(String skinName, SkinLoaderListener listener, int strategy)

重点看下loadSkin方法

public AsyncTask loadSkin(String skinName, SkinLoaderListener listener, int strategy) {SkinLoaderStrategy loaderStrategy = mStrategyMap.get(strategy);if (loaderStrategy == null) {return null;}return new SkinLoadTask(listener, loaderStrategy).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, skinName);
}

使用AsynTask进行异步操作,加载皮肤包

private class SkinLoadTask extends AsyncTask<String, Void, String> {private final SkinLoaderListener mListener;private final SkinLoaderStrategy mStrategy;SkinLoadTask(@Nullable SkinLoaderListener listener, @NonNull SkinLoaderStrategy strategy) {mListener = listener;mStrategy = strategy;}@Overrideprotected void onPreExecute() {if (mListener != null) {mListener.onStart();}}@Overrideprotected String doInBackground(String... params) {synchronized (mLock) {while (mLoading) {try {mLock.wait();} catch (InterruptedException e) {e.printStackTrace();}}mLoading = true;}try {if (params.length == 1) {String skinName = mStrategy.loadSkinInBackground(mAppContext, params[0]);if (TextUtils.isEmpty(skinName)) {SkinCompatResources.getInstance().reset(mStrategy);return "";}return params[0];}} catch (Exception e) {e.printStackTrace();}SkinCompatResources.getInstance().reset();return null;}@Overrideprotected void onPostExecute(String skinName) {synchronized (mLock) {// skinName 为""时,恢复默认皮肤if (skinName != null) {SkinPreference.getInstance().setSkinName(skinName).setSkinStrategy(mStrategy.getType()).commitEditor();notifyUpdateSkin();if (mListener != null) {mListener.onSuccess();}} else {SkinPreference.getInstance().setSkinName("").setSkinStrategy(SKIN_LOADER_STRATEGY_NONE).commitEditor();if (mListener != null) {mListener.onFailed("皮肤资源获取失败");}}mLoading = false;mLock.notifyAll();}}
}

doInBackground 通过皮肤包名称获取皮肤包资源,notifyUpdateSkin 通知所有 SkinCompatSupportable 对象更新皮肤

创建皮肤包

  1. 新建一个Application Module
  2. 创建对应皮肤的颜色等资源
  3. 资源名称和原工程一样,颜色值修改成对应皮肤包色值
  4. 打apk包,改为 .skin 文件,放在原工程的 assets目录下

实现效果:

浅色模式

浅色模式色值

点击深色模式按钮切换深色模式

皮肤包的色值

其他使用

  • 恢复默认样式
SkinCompatManager.getInstance().restoreDefaultTheme();
  • setBackgroundColor、setBackground、setTextColor等方法失效问题

因为框架是根据资源ID,如R.color.black,找到皮肤包中对应名称的资源,所以如果代码中直接设置颜色或者图片,是不支持换肤的。可以改为setBackgroundResource

  • 适配Dialog

SkinXXXView 创建时会获取控件设置的颜色(textColor)、背景(background)等属性的ID,然后再根据ID加载皮肤包中对应的资源。

但是系统的Dialog的布局控件并没有设置背景属性 background等,所以默认情况下Dialog是不支持换肤的。

Skin-Support的解决方法就是替换系统默认的Dialog布局,然后再设置对应的颜色等属性,最终实现换肤效果。

在styles.xml中做如下的声明:

    <item name="alertDialogStyle">@style/AlertDialog.SkinCompat</item><!-- 以下属性需要用户自行实现,并在皮肤包中提供对应值 --><item name="skinAlertDialogBackground"></item><item name="skinAlertDialogTitleTextColor"></item><item name="skinAlertDialogMessageTextColor"></item><item name="skinAlertDialogNeutralButtonTextColor"></item><item name="skinAlertDialogNegativeButtonTextColor"></item><item name="skinAlertDialogPositiveButtonTextColor"></item><item name="skinAlertDialogControlHighlightColor"></item><item name="skinAlertDialogListDivider"></item><item name="skinAlertDialogListItemTextColor"></item>
</style>
  • 自定义控件换肤

Skin-Support 只提供系统组件的换肤,自定义控件需要实现 SkinCompatSupportable 接口或者直接继承 SkinXXXView,然后获取布局中属性的ID和重写 applySkin 方法。

public class SkinSwitchButton extends SwitchButton implements SkinCompatSupportable {private static final String TAG = "SkinSwitchButton";private int kswThumbDrawable;private int kswBackDrawable;public SkinSwitchButton(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);//获取SwitchButton的属性IDTypedArray ta = attrs == null ? null : context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);if (ta != null) {kswThumbDrawable = ta.getResourceId(R.styleable.SwitchButton_kswThumbDrawable, INVALID_ID);kswBackDrawable = ta.getResourceId(R.styleable.SwitchButton_kswBackDrawable, INVALID_ID);ta.recycle();}applySkin();}public SkinSwitchButton(Context context, AttributeSet attrs) {this(context, attrs, 0);}public SkinSwitchButton(Context context) {this(context, null, 0);}@Overridepublic void applySkin() {//通过ID获取皮肤包对应的资源kswThumbDrawable = SkinCompatHelper.checkResourceId(kswThumbDrawable);if (kswThumbDrawable != INVALID_ID) {setThumbDrawable(SkinCompatResources.getDrawable(getContext(), kswThumbDrawable));}kswBackDrawable = SkinCompatHelper.checkResourceId(kswBackDrawable);if (kswBackDrawable != INVALID_ID) {setBackDrawable(SkinCompatResources.getDrawable(getContext(), kswBackDrawable));}}
}

动态修改颜色

Skin-Support 也支持代码中动态修改颜色,且优先级最高

//动态修改颜色
SkinCompatUserThemeManager.get().addColorState(colorId, newColor);
//动态修改图片
SkinCompatUserThemeManager.get().addDrawablePath(int drawableRes, String drawablePath);
//颜色修改完后需要调用该方法才可以生效
SkinCompatUserThemeManager.get().apply();//清除自定义的颜色和图片
SkinCompatUserThemeManager.get().clearColors();
SkinCompatUserThemeManager.get().clearDrawables();

参考:

https://www.jianshu.com/p/2c3833b8a1d2?utm_campaign=hugo

https://blog.csdn.net/c10WTiybQ1Ye3/article/details/119223672

Android 适配深色模式相关推荐

  1. android自动切换暗色,Android 适配深色模式的总结

    Android Q 推出了深色模式,其实 Android 9 就有了,部分厂商小米,三星就在系统 Android 9 加入了深色模式的开关. Android 提供了一套夜间模式主题,继承 Theme. ...

  2. 实现页面适配_微信公众号文章页面适配深色模式

    最近安卓微信7.0.10正式版发布,更新过后,很多用户发现,之前在测试版中对系统深色模式的适配功能被取消了,小伙伴们对此很是不满,好在Android 10系统手机用户占比很少,影响范围还不是很大,并且 ...

  3. Flutter适配深色模式(DarkMode)

    1.瞎叨叨 也不知道写点什么,本来想写写Flutter的集成测试.因为前一阵子给flutter_deer写了一套,不过感觉也没啥内容,写不了几句话就放弃了.(其实本篇内容也不多...) 那就写写最近在 ...

  4. iOS13适配深色模式(Dark Mode)

    原文博客地址: iOS13适配深色模式(Dark Mode) 好像大概也许是一年前, Mac OS系统发布了深色模式外观, 看着挺刺激, 时至今日用着也还挺爽的 终于, 随着iPhone11等新手机的 ...

  5. iOS13适配深色模式(Dark Mode)总结

    iOS13适配深色模式(Dark Mode)总结 好像大概也许是一年前, Mac OS系统发布了深色模式外观, 看着挺刺激, 时至今日用着也还挺爽的 终于,随着iPhone11等新手机的发售, iOS ...

  6. android开发适配深色模式,手机不支持深色模式,如何用软件解决深色模式的问题?(附有系统全局深色模式实现方法...

    本帖最后由 巷子口的你 于 2020-8-8 07:57 编辑 1.92允许通过设置为助手应用来饮捷切频深色模式(设置入口一般为系统默认应用-助手和语音输人, MIU需要设置为语音助手)提醒:稳定模式 ...

  7. Android 适配黑暗模式10.0 Q

    首先刚开始 我开始使用了第三方得 Android-skin-support库 因为我的项目是databinding的,升级到最新版本后 库不支持了,所以也是抛弃了,可能是因为这个库的作者工作忙或者是没 ...

  8. Android切换深色模式导致布局字体变小的解决方案

    切换深色模式导致布局字体变小问题困扰了我很久,一直排查自身代码问题却没发现并非自身代码导致,而是使用了今日头条屏幕适配方案AndroidAutoSize导致的,目前暂时在小米手机安卓11系统发现,切换 ...

  9. 开启Android Q DarkMode | 开启Android Q深色模式 夜间模式

    1.首先下载Image 注意,这里最好是下载google APIs Intel x86 System Image 2.创建虚拟机,启动模拟器 如果报错HAXM 没有安装的话,请安装一下 注意,这个HA ...

  10. Android 适配暗黑模式

    在样式中添加 <style name="MyAppTheme">.......<item name="android:forceDarkAllowed& ...

最新文章

  1. 安卓中运行报错Error:Execution failed for task ':app:transformClassesWithDexForDebug'解决
  2. python教学上机实验报告怎么写_Python基础(下)
  3. @override代表什么意思_混凝土中C20、HZS180都代表什么意思?
  4. mesos+marathon平台搭建
  5. 如何提高数据安全性与可用性——行云管家堡垒机
  6. Java多线程可以分组,还能这样玩!
  7. nessus重置密码
  8. caffe2安装篇(二) ubuntu16.04 安装方法
  9. php 输入汉字自动带出拼音和英文
  10. PHPCMS商城:模块_购物车+订单模块(资源合集)
  11. .NET下一种简单的调试诊断方法
  12. ffmpeg wav 转 mp3 以及其他音频转换
  13. android 微信浮窗实现_Android仿微信文章悬浮窗效果的实现代码
  14. mysql 1114错误_mysql – ERROR 1114(HY000):表’XXX’已满
  15. coldfusion_ColdFusion教程:第一部分
  16. 【s3.amazonaws.com】【github.com】拒绝了我们的连接请求-解决方案
  17. Matlab打开绘图工具
  18. java网络学习之 PKCS标准 X.509标准 证书等概念 的汇总(16)
  19. 祁隆乐凡短视频隔空宣战,和合国际收购祁隆歌曲《借我星光》版权
  20. App开发中适用的短信SDK

热门文章

  1. POI给word中插入图片后打不开的bug
  2. 华为云服务器安装win10系统,云服务器安装win10
  3. 如何查看自己win10的产品密钥
  4. 微信抽奖网络服务器错误,微信抽奖账号异常原因?
  5. elasticsearch搜索IK分词器实现单个字搜索
  6. 耗时两周,纯手敲python入门级基础笔记
  7. 2020考研数学一大纲之完全解析(一)
  8. 如今表情包的天下,曾经可是颜文字和Emoji的啊...
  9. 解决redhat vmware安装后鼠标不能使用问题
  10. c语言 long double输出,printf和long double