GMS介绍
GMS全称为GoogleMobile Service,即谷歌移动服务。GMS是Google开发并推动Android的动力,是谷歌程序运行的基础。
GMS提供有GooglePlay、Search、Google语音、Gmail、Contact Sync、Calendar Sync、Talk、Google Maps、Street View、YouTube、Android Market等服务,GMS为安卓上的谷歌公司系列应用提供支持
GMS资源获取
由于Google官方未开放相关的服务,应用的资源下载,这时我们可以选择再Open GApps网站上进行下载, Open GApps项目是一个开源项目,每晚(欧洲时间)更新一次Google相关的APP,包含适配各种架构,安卓版本的Google相关的服务,应用的资源文件。
版本的选择
当我们进入Open GApps后可看到如下的选项界面,比较清晰,根据需求进行选择下载:
针对不同的Variant包含的内容,网上已有介绍,这里也粘贴一下:
类型 | 描述 |
---|---|
aroma | 与super版所包含的GApps相同,但是在Recovery中引入了图形化界面。 |
super | 包含了所有GApps,像韩语日语中文拼音中文注音输入法等。(请注意:如果你是用的是基于原生的ROM ,本版本会替换相机,通讯录等等所有有关应用)。 |
stock | 类似于Google Pixel出厂内置的GApps,相比super版少了其他语种的输入法以及Google地球等。 |
full | 与 stock 版所包含的内容相同,但此版本不会替换手机原本的应用. |
mini | 包含基础的 Google 服务框架,以及一些影响力较大的GApps,相比full版去掉了Docs等应用。 |
micro | 包含基础的Google服务框架和 Gmail 等常见GApps。 |
nano | 包含基础的Google服务框架,但不会有其他不必要的GApps。 |
pico | 包含最迷你的Google服务框架,但由于框架并非完整,部分GApps可能无法运行 |
tvstock | 此软件包适用于Android TV设备。 它包括Nexus Player标配的所有GApps。 |
tvmini | 适用于Android TV设备,只包含一些影响力较大的GApps。 |
这里我建议下一个pico版本和super,导入pico相关的内容后可以正常运行,根据需要去super版本内拿其余的apk。
资源包的解析
下载后解压可看到如下目录形式的文件结构:
其中
- Core是核心应用和资源的目录,也就是我们需要预置的部分;
- GApps是其他的原生apk,根据需求判断是否需要添加;
- Optional是一些可选的库,可一并导入进系统;
- tar-arm、unzip-arm、zip-arm是一些解压缩的工具;
- 其余的文件还包含一些安装脚本,可自动将相关的内容安装至我们的手机中,由于我们是预置进系统内,所以不需要关注这部分的东西;
由于提取相关的apk和资源手动弄起来比较慢,这里提供一个我自己写的脚本,放到解压后的目录的根目录,然后执行脚本就可以将所有我们需要的东西整理输出到一个out目录,我们再根据目录的结构预置进我们的系统即可;
basepath=$(cd `dirname $0`; pwd)
mkdir -p out/priv-app
mkdir -p out/common
mkdir -p out/app
cd $basepath/Core && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..
cd $basepath/GApps && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..
cd $basepath/Optional && find . -name '*.lz' | xargs -n1 lzip -d && ls *.tar | xargs -n1 tar -xvf && cd ..
find -name priv-app | xargs -i cp -rf {} out/
find -name common | xargs -i cp -rf {} out/
find -name app | xargs -i cp -rf {} out/
cp g.prop out/common/etc/
执行后对应的out目录的内部结构如下:
这里介绍其中几个比较重要的应用
- Google服务框架: GoogleServicesFramework;
- Google核心服务:PrebuiltGmsCore;
- GooglePlay:Phonesky;
注意:我们预置相关的应用,编写相应Android.mk时,保留应用原有的签名,不要使用我们的系统签名去覆盖它
GMS预置
GMS资源预置
priv-app的预置
预置Android.mk的编写,由于应用较多,所以这里以一个应用为例,其他的依样画葫芦即可:
##############GoogleServicesFramework##################
include $(CLEAR_VARS)
#
## Module name should match apk name to be installed
LOCAL_MODULE := GoogleServicesFramework
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := GoogleServicesFramework.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := true
LOCAL_PRIVILEGED_MODULE := true
include $(BUILD_PREBUILT)
app的预置
同样的以一个应用为例
##############YouTube##################
include $(CLEAR_VARS)
## Module name should match apk name to be installed
LOCAL_MODULE := YouTube
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := YouTube.apk
LOCAL_MODULE_CLASS := APPS
LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
LOCAL_CERTIFICATE := PRESIGNED
LOCAL_DEX_PREOPT := false
LOCAL_OVERRIDES_PACKAGES += \
YouTubeLeanback
include $(BUILD_PREBUILT)
common目录的预置
common目录内部的文件只需要拷贝进system目录下即可,根据需要写在对应的mk内
# google config
PRODUCT_COPY_FILES += $(call find-copy-subdir-files,*,xxxxxx/common,/system)
当所有都配置好之后编译烧录即可,当然前提是系统分区的空间充足,不然可能会编译不通过;
之后烧录进设备,联网之后会出现设备未认证的提示,根据提示进行认证操作即可,这部分网上教程很多,这里也不展开。
AutoMotive系统GooglePlay预置问题
预置时需要注意除了GooglePlay以外基本都不太会受设备类型的影响,而GooglePlay根据不同的设备类型有不同的应用apk,内部可搜索的应用数量也跟设备内支持的feature有关(这部门的内容比较细,没有研究的很深入,后续有机会再整理)。虽然有不同的apk,但它们对应的包名都为com.android.vending。
根据设备类型可分为如下几种:
类型 | 描述 |
---|---|
手机版Phonesky | 应用类型最多,最丰富的版本,我们所需要的也是这种版本。 |
电视版Tubesky | 电视版本的谷歌商店,经过测试,触摸无法控制,需要靠外部的输入设备(如键盘,鼠标,遥控器)等才可控制应用内部的内容,对触摸设备没做支持?所以也不考虑使用。 |
车机(AutoMotive)版 | 目前AutoMotive版本没有对应的apk,我所用的apk为安装手机版GooglePlay,然后由于系统存在AutoMotive的Feature,所以GooglePlay自动更新匹配我的设备,从而得来的apk(/data/app/com.android.vendingxxxx 目录下),这种方式拿到的apk是被分包处理后的,若要预置到系统中,暂时还没找到合适的方式;若只预置base.apk会出现某些资源缺失的问题,暂时不太可用。 |
手表版 | 为穿戴类安卓设备定制的谷歌商店,我这边没有研究,也就不展开。 |
那么如何在我们的车机预置手机版的GooglePlay:
预置不同于设备类型的GooglePlay
前面有提到GooglePlay受设备类型的影响,正常情况我们将手机版GooglePlay安装在车机设备上是无法运行的,打开GooglePlay会有如下的弹窗:
此时其实后台后自动帮我们将GooglePlay更新到车机版本,等待更新完成之后也能打开。
车机版GooglePlay界面
可以看到车机版的应用数量较少,可能不太符合我们的需求,所以需要兼容运行手机版GooglePlay。
通过jadx-gui对手机版GooglePlay即Phonesky.apk进行反编译,搜索automotive我们可以看到内部有一些针对automotive, watch,tv等feature类型的判断,虽然无法具体的看到是什么逻辑,但是我们可以大致猜测出GooglePlay的内容和启动跟feature有一定的关系,这部分在官网也有一定的介绍。
这里我们取其中一个代码来看一下:
package defpackage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build;
import com.google.firebase.FirebaseCommonRegistrar;
/* compiled from: PG */
/* renamed from: aepj reason: default package */
/* loaded from: classes.dex */
public final /* synthetic */ class aepj implements aetv {
private final /* synthetic */ int e;
public static final /* synthetic */ aepj d = new aepj(3);
public static final /* synthetic */ aepj c = new aepj(2);
public static final /* synthetic */ aepj b = new aepj(1);
public static final /* synthetic */ aepj a = new aepj(0);
private /* synthetic */ aepj(int i) {
this.e = i;
}
@Override // defpackage.aetv
public final String a(Object obj) {
int i = this.e;
if (i == 0) {
ApplicationInfo applicationInfo = ((Context) obj).getApplicationInfo();
return (applicationInfo == null || Build.VERSION.SDK_INT < 24) ? "" : String.valueOf(applicationInfo.minSdkVersion);
} else if (i == 1) {
ApplicationInfo applicationInfo2 = ((Context) obj).getApplicationInfo();
return applicationInfo2 != null ? String.valueOf(applicationInfo2.targetSdkVersion) : "";
} else if (i != 2) {
Context context = (Context) obj;
String installerPackageName = context.getPackageManager().getInstallerPackageName(context.getPackageName());
return installerPackageName != null ? FirebaseCommonRegistrar.a(installerPackageName) : "";
} else {
Context context2 = (Context) obj;
return context2.getPackageManager().hasSystemFeature("android.hardware.type.television") ?
"tv" : context2.getPackageManager().hasSystemFeature("android.hardware.type.watch") ?
"watch" : (Build.VERSION.SDK_INT < 23 || !context2.getPackageManager().hasSystemFeature("android.hardware.type.automotive")) ?
(Build.VERSION.SDK_INT < 26 || !context2.getPackageManager().hasSystemFeature("android.hardware.type.embedded")) ?
"" : "embedded" : "auto";
}
}
}
可以看到代码内通过PackageManager的hasSystemFeature方法去判断设备的类型,并返回对应的类型字符串,在源码中找到对应的方法和文件(framework/base/core/java/android/app/ApplicationPackageManager.java),结合内部的方法来看,除了hasSystemFeature,还有一个获得系统所有支持的Feature的方法:getSystemAvailableFeatures,我们同样在反编译后的代码中搜索。
看到同样有调用,那么我们就都一起改掉。
GMS适配修改方案
framework/base/core/java/android/app/ApplicationPackageManager.java
public class ApplicationPackageManager extends PackageManager {
....
public FeatureInfo[] getSystemAvailableFeatures() {
try {
ParceledListSlice<FeatureInfo> parceledList =
mPM.getSystemAvailableFeatures();
if (parceledList == null) {
return new FeatureInfo[0];
}
final List<FeatureInfo> list = parceledList.getList();
// add start ——————————————————————
if ("com.android.vending".equals(mContext.getPackageName()) || "com.google.android.gms".equals(mContext.getPackageName())) {
// googleplay and gms service rmove automotvie feature
for (int i = 0; i < list.size(); i++) {
if(PackageManager.FEATURE_AUTOMOTIVE.equals(list.get(i).name)) {
Log.i(TAG, "getSystemAvailableFeatures mContext.getPackageName() = " + mContext.getPackageName() + " i = " + i);
list.remove(i);
}
}
}
// add end ——————————————————————
final FeatureInfo[] res = new FeatureInfo[list.size()];
for (int i = 0; i < res.length; i++) {
res[i] = list.get(i);
}
return res;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
public boolean hasSystemFeature(String name) {
return hasSystemFeature(name, 0);
}
@Override
public boolean hasSystemFeature(String name, int version) {
// add start ——————————————————————
if ("com.android.vending".equals(mContext.getPackageName()) || "com.google.android.gms".equals(mContext.getPackageName())) {
// googleplay and gms service rmove automotvie feature
Log.i(TAG, "hasSystemFeature mGmsFeatureType = " + mGmsFeatureType + " name = "+ name);
if (PackageManager.FEATURE_AUTOMOTIVE.equals(name)) {
return false;
}
}
// add end ——————————————————————
try {
return mPM.hasSystemFeature(name, version);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
逻辑很简单,就是根据对应的包名移除对应的feature。之后再次编译操作,就可以成功在我们的设备上运行手机版的GooglePlay了,运行效果如下:
GMS一次性认证方案
前面有提到,我们的设备烧录之后都需要根据Google官方流程对设备进行验证,如果是大量出货的设备,若没有与Google合作,那么每一台都需要走一遍验证流程,非常耗时和繁琐,所以我们需要通过一些方式跳过这个认证的过程,保证我们的设备在烧录之后不需要进行认证也可以使用谷歌服务,下面介绍一下具体的实现方式。
根据官网的认证操作流程,我们知道注册认证的android_id的获取方式如下:
adb root
abd shell
sqlite3 /data/data/com.google.android.gsf/databases/gservices.db
select * from main where name = "android_id";
所以我们只需要保证我们每台设备的android_id都是我们已经认证过的设备id即可。
针对这一方案,可用的实现方案如下:
- 将已经认证设备的Google服务相关的初始数据库 /data/data/com.google.android.gsf/databases 和 /data/data/com.google.android.gms/databases 复制出来;
- 将已认证的Google服务数据库打包进系统固件中;
- 编写脚本,第一次开机时启动,使用已认证的Google服务数据库覆盖设备内对应的数据库;
- 检查设备android_id,确认成功替换,且没有设备待认证的通知;