Java中使用反射的地方较多,尤其是各种框架中。最近在Android7.0的项目中遇到个问题很奇怪,反射使用的类找不到了,但是编译的时候没问题啊。然后在代码中使用非反射的方式调用代码也是没有问题的,这时奇怪的现象出现了,加入手动调用代码后反射代码找不到类的问题也不出现了。其实这个是混淆代码所做的工作,一个类没有被使用的情况下会在编译中直接删除掉,显然并没有考虑反射调用的情况。关闭混淆或者修改混淆的配置文件即可解决这个问题。各种框架自动所做的工作大部分是节省了程序员的时间,但是一旦出问题查起来花费的时间也是不少

当处于应用层时,如果只是修改应用内多语言时,上层app可以轻松完成各种语言的切换,网上方法很多,就不在详细叙述,app内部设置多语言可参考下面这篇文章

但是,如何通过app,设置系统语言呢?这正是本文讨论核心。

android6.0

android设置系统语言的核心方法在framework层,地址是\frameworks\base\core\java\com\android\internal\app\LocalePicker.java类里,方法如下:

/**
* Requests the system to update the system locale. Note that the system looks halted
* for a while during the Locale migration, so the caller need to take care of it.
*/
public static void updateLocale(Locale locale) {
try {
IActivityManager am = ActivityManagerNative.getDefault();
Configuration config = am.getConfiguration();
config.setLocale(locale);
config.userSetLocale = true;
am.updateConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
BackupManager.dataChanged("com.android.providers.settings");
} catch (RemoteException e) {
// Intentionally left blank
}
}

android6.0设置系统语言的关键逻辑就是上面那个方法。如果上层APP想要设置系统语言必须通过反射方法获取,核心方法代码如下(本方法可以持久化系统语言设置,也就是说重启手机后不会恢复默认系统语言):

private voidchangeSystemLanguage(Locale locale) {
if(locale != null) {
try{
Class classActivityManagerNative = Class.forName("android.app.ActivityManagerNative");Method getDefault = classActivityManagerNative.getDeclaredMethod("getDefault");Object objIActivityManager = getDefault.invoke(classActivityManagerNative);Class classIActivityManager = Class.forName("android.app.IActivityManager");Method getConfiguration = classIActivityManager.getDeclaredMethod("getConfiguration");Configuration config = (Configuration) getConfiguration.invoke(objIActivityManager);config.setLocale(locale);//config.userSetLocale = true;Class clzConfig = Class
.forName("android.content.res.Configuration");java.lang.reflect.Field userSetLocale = clzConfig
.getField("userSetLocale");userSetLocale.set(config, true);Class[] clzParams = {Configuration.class};Method updateConfiguration = classIActivityManager.getDeclaredMethod("updateConfiguration",clzParams);updateConfiguration.invoke(objIActivityManager,config);BackupManager.dataChanged("com.android.providers.settings");} catch(Exception e) {
Log.d(TAG,"changeSystemLanguage: "+ e.getLocalizedMessage());}
}
}

调用时如下:

Local locale = Locale.ENGLISH;
changeSystemLanguage(locale);

系统语言就变成了英文。(不再赘述)

android7.0

7.0与6.0的源码有所不同,LocalePicker.Java定义系统语言的方式,不在是一种Local,而是一个LocaleList,具体方法如下:

public static void updateLocale(Locale locale) {
updateLocales(new LocaleList(locale));
}

updateLocale调用了updateLocales方法,updateLocales方法如下:

public static void updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManagerNative.getDefault();
final Configuration config = am.getConfiguration();
config.setLocales(locales);
config.userSetLocale = true;
am.updatePersistentConfiguration(config);
// Trigger the dirty bit for the Settings Provider.
BackupManager.dataChanged("com.android.providers.settings");
} catch (RemoteException e) {
// Intentionally left blank
}
}

可见,6.0上的反射直接照搬到7.0是不起作用的,需要重新运用反射方法,反射方法代码如下

protected voidchangeSystemLanguage(LocaleList locale) {
if(locale != null) {
try{
Class classActivityManagerNative = Class.forName("android.app.ActivityManagerNative");Method getDefault = classActivityManagerNative.getDeclaredMethod("getDefault");Object objIActivityManager = getDefault.invoke(classActivityManagerNative);Class classIActivityManager = Class.forName("android.app.IActivityManager");Method getConfiguration = classIActivityManager.getDeclaredMethod("getConfiguration");Configuration config = (Configuration) getConfiguration.invoke(objIActivityManager);config.setLocales(locale);Class[] clzParams = {Configuration.class};Method updateConfiguration = classIActivityManager.getDeclaredMethod("updatePersistentConfiguration",clzParams);updateConfiguration.invoke(objIActivityManager,config);} catch(Exception e) {
Log.d(TAG,"changeSystemLanguage: "+ e.getLocalizedMessage());}
}
}

6.0和7.0设置系统语言源码不同之出,有两点:

1.设置参数的方法不同,6.0是updateConfiguration,7.0是updatePersistentConfiguration,这点需要注意

2.6.0传递的local,而7.0是一个列表LocaleList

7.0反射方法调用如下:

Locale newLocale = newLocale("zh","CN");finalLocaleList localeList = newLocaleList(newLocale);changeSystemLanguage(localeList);

生成的apk,需要系统签名,可放在源码vendor\customer\你的文件下通过mm编译,需要注意的是,需要有.mk文件,生成的apk在out的目录下(当然层级有很多),把out目录下生成的apkpush到手机system/priv-app/你的文件夹,重启手机即可。

操作如下:

1.把studio生成的apk放入如下目录:

semget 安卓找不到 找不到android_semget 安卓找不到

Android.mk文件如下(供参考):

--------------------------开始(下面才是)-------------------------------------------------------------

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Module name should match apk name to be installed
LOCAL_MODULE := Test
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(LOCAL_MODULE).apk
LOCAL_MODULE_CLASS := APPS
LOCAL_OVERRIDES_PACKAGES := Calendar
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := platform
#LOCAL_PREBUILT_JNI_LIBS:= \
#@lib/armeabi/liblocSDK4d.so
#LOCAL_MODULE_PATH := $(TARGET_OUT)/vendor/operator/app
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PREBUILT)

--------------------------------结束(本行不是)-----------------------------------------------------------------------------

2.编译源码,单编模块

命令如下(前提是你已经整编过一套android源码)

source ./build/envsetup.sh(加载命令)

lunch 16(序号和你整编时选的一样,本文以android7.0源码为准)

mmm vendor/customer/Test

3.生成的apk(已经打包了系统签名):在如下目录(可能有出入)

semget 安卓找不到 找不到android_semget 安卓找不到

4.push到手机,不要install,重启手机

adb push XXXX system/priv-app/Test

XXX是你out生成的apk,可拖拽到此。

现在就可以看到一个应用了,点击就可以切换系统语言了。多多交流   *-*