Android--导入framework.jar

APK开发需要实现 选择系统语言 功能,使用反射和导入framework架包2种方法都可实现。

由于修改系统语言需要系统权限,所以无论使用哪种方法,都需要给APK添加系统权限,添加系统权限又必须添加系统签名,系统会要求该APK具有与系统相同的签名,否则会安装失败。

系统权限和系统签名

1. 添加系统权限

添加系统权限很简单,只需要在APK的AndroidManifest.xml中声明android:sharedUserId="android.uid.system"即可。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    // 添加系统权限
    android:sharedUserId="android.uid.system">
        ...
</manifest>

2. 添加系统签名

添加系统签名,首先需要将系统签名复制到工程目录下,在config.gradle中配置参数,并在工程的build.gradle中应用下:

<config.gradle>

ext {
    sign = [
            file         : '../system_key.jks',
            storePassword: 'xxxxxxxx',  
            keyAlias     : 'system_key',
            keyPassword  : 'xxxxxxxx'
    ]
}


<build.gradle>

apply from: "config.gradle"

然后在APP的build.gradle中打包时增加系统签名:

android{
    ...
    signingConfigs {
        hikvision {
            storeFile file(sign.file)
            storePassword sign.storePassword
            keyAlias sign.keyAlias
            keyPassword sign.keyPassword
        }
    }

    buildTypes {
        debug {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.hikvision
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.hikvision
        }
    }
    ...
}
系统签名.jpg

3. 可能存在的问题

APK添加了系统权限,webView会报错

WebView is not allowed in privileged processes

Android 6.0 以上不允许在拥有系统权限的应用中使用 WebView,需要在onCreate()方法中的setContentView()之前调用以下的hookWebView()方法:

    public static void hookWebView() {
        int sdkInt = android.os.Build.VERSION.SDK_INT;
        try {
            Class<?> factoryClass = Class.forName("android.webkit.WebViewFactory");
            Field field = factoryClass.getDeclaredField("sProviderInstance");
            field.setAccessible(true);
            Object sProviderInstance = field.get(null);
            if (sProviderInstance!= null) {
                Log.i(TAG, "sProviderInstance isn't null");
                return;
            }
            Method getProviderClassMethod = null;
            if (sdkInt > 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getProviderClass");
            } else if (sdkInt == 22) {
                getProviderClassMethod = factoryClass.getDeclaredMethod("getFactoryClass");
            } else {
                Log.i(TAG, "Don't need to Hook WebView");
                return;
            }
            getProviderClassMethod.setAccessible(true);
            Class<?> factoryProviderClass = (Class<?>) getProviderClassMethod.invoke(factoryClass);
            Class<?> delegateClass = Class.forName("android.webkit.WebViewDelegate");
            Constructor<?> delegateConstructor = delegateClass.getDeclaredConstructor();
            delegateConstructor.setAccessible(true);
            if (sdkInt < 26) {
                Constructor<?> providerConstructor = factoryProviderClass.getConstructor(delegateClass);
                if (providerConstructor!= null) {
                    providerConstructor.setAccessible(true);
                    sProviderInstance = providerConstructor.newInstance(delegateConstructor.newInstance());
                }
            } else {
                Field chromiumMethodName = factoryClass.getDeclaredField("CHROMIUM_WEBVIEW_FACTORY_METHOD");
                chromiumMethodName.setAccessible(true);
                String chromiumMethodNameStr = (String) chromiumMethodName.get(null);
                if (chromiumMethodNameStr == null) {
                    chromiumMethodNameStr = "create";
                }
                Method staticFactory = factoryProviderClass.getMethod(chromiumMethodNameStr, delegateClass);
                if (staticFactory!= null) {
                    sProviderInstance = staticFactory.invoke(null, delegateConstructor.newInstance());
                }
            }

            if (sProviderInstance!= null) {
                field.set(null, sProviderInstance);
                Log.i(TAG, "Hook success!");
            } else {
                Log.i(TAG, "Hook failed!");
            }
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }

反射实现系统语言切换

获取到系统权限之后就可以通过反射方法拿到framework中的LocalePicker类,然后调用其中的updateLocale方法就可以实现系统语言切换。

<MainActivity.java>

    private final List<String> systemLanguageList = new ArrayList<String>(
            Arrays.asList(
                    "zh", "en", "fr", "es", "de", "it", "pt", "ru", "pl", "ar", "tr", "vi",
                    "hu", "nl", "ro", "cs", "bg", "uk", "hr", "sr", "el", "no", "da"
            ));

    private void initView(){
        findViewById(R.id.get_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getLanguage();
            }
        });

        findViewById(R.id.set_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setLanguage(systemLanguageList.get(1));
            }
        });
    }

    // 获取当前系统语言
    private void getLanguage(){
        // 获取当前Activity的Locale
        Locale activityLocale = getResources().getConfiguration().locale;
        // 获取语言代码
        String language = activityLocale.getLanguage();
        Log.d("getLanguage", "current Language: " + language );
    }

    // 设置当前系统语言
    private void setLanguage(String language){
        try {
            Class localPicker = Class.forName("com.android.internal.app.LocalePicker");
            Method updateLocale = localPicker.getDeclaredMethod("updateLocale", Locale.class);
            updateLocale.invoke(null, new Locale(language, ""));
        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
            Log.d("error", "try setLanguage Exception " );
        }
    }

导入framework.jar

1. 拷贝framework.jar

首先,需要从源码中把framework.jar拷贝到本地工程app/libs下,可将源码中的jar更名为framework.jar

Android N/O:  7 和 8
out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar

Android P/Q:  9 和 10
out/soong/.intermediates/frameworks/base/framework/android_common/combined/framework.jar

Android R:  11以上
out/soong/.intermediates/frameworks/base/framework-minus-apex/android_common/combined/framework-minus-apex.jar
导入framework.jpg

2. Android Studio适配

.iml文件是IntelliJ IDEA和Android Studio用来存储模块级别的配置信息的文件,3.6.3以后的Android Stuido版本默认关闭 .iml 文件的生成,导入framework.jar需要配置使用的优先级,所有需要先把Android Stuido中生成 .iml 文件的功能打开。

Android Studio —> File —> Settings —> Build... —> Build Tools —> Gradle —> 勾选Generate *.iml files for modules imported from Gradle —> Apply —> OK —> 重启Android Studio

配置as.jpg

3. 修改build.gradle(:app)配置

(1)添加framework.jar依赖

compileOnly files('libs\\framework.jar')

(2)修改资源链接优先级

// 优先链接framework.jar
gradle.projectsEvaluated {
    // 方法一
    tasks.withType(JavaCompile) {
        Set<File> fileSet = options.bootstrapClasspath.getFiles();
        List<File> newFileList = new ArrayList<>()
        newFileList.add(new File("libs/framework.jar"))
        newFileList.addAll(fileSet)
        options.bootstrapClasspath = files(newFileList.toArray())
    }
    // 方法二
//    tasks.withType(JavaCompile).tap {
//        configureEach {
//            options.compilerArgs.add("-Xbootclasspath/p:$rootProject.rootDir/app/libs/framework.jar")
//        }
//    }
}

(3)修改类的使用优先级

// 降低Android SDK的使用级别,优先使用framework.jar中的类文件
preBuild {
    doLast {
        def rootProjectName = rootProject.name.replace(" ", "_")
        def projectName = project.name.replace(" ", "_")
        def iml_path = "$rootProject.rootDir\\.idea\\modules\\" + projectName + "\\" + rootProjectName + "." + projectName + ".main.iml"
        def imlFile = file(iml_path)
        try {
            // 如果AS未适配,这里会找不到XmlParser
            def parsedXml = (new XmlParser()).parse(imlFile)
            def jdkNode = parsedXml.component[1].orderEntry.find { it.'@type' == 'jdk' }
            def sdkString = jdkNode.'@jdkName'
            parsedXml.component[1].remove(jdkNode)
            new Node(parsedXml.component[1], 'orderEntry', ['type': 'jdk', 'jdkName': sdkString, 'jdkType': 'Android SDK'])
            groovy.xml.XmlUtil.serialize(parsedXml, new FileOutputStream(imlFile))
        } catch (FileNotFoundException e) {
            e.printStackTrace()
        }
    }
}

(4)解决65536限制

MultiDex是Android开发中用于解决65536个方法限制的一种机制,当应用的代码量超过65536个方法时,需要使用MultiDex来将应用拆分成多个DEX文件。

android {
    ...
    defaultConfig {
        applicationId "com.android.kc9demo"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    ...
}

dependencies{
    ...
        implementation 'com.android.support:multidex:1.0.0'
}

(5)使用framework.jar中的方法

导入framework.jar之后,在MainActivity中调用updateLocale方法就可以直接使用了。

    private void setLanguage(String language){
        LocalePicker.updateLocale(new Locale(language, ""));
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容