一、目的
都是因为工作的原因,现在正在调试本地收音机功能。开发环境背景,android9 基于imx8 ndk 开发本地收音机功能。我是做app功能,所以调试过程从应用层开始往下调试,在android linux 开发同事说 收音机芯片已经调试OK之后,我使用android 自带的收音机功能进行测试,打开收音机闪退同时报错,收音机服务失败。具体log如下:
06-15 06:54:09.607 I/ActivityManager( 2390): START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 pkg=com.android.car.radio cmp=com.android.car.radio/.CarRadioActivity} from uid 10014
06-15 06:54:09.663 I/ActivityManager( 2390): Start proc 4059:com.android.car.radio/u0a42 for activity com.android.car.radio/.CarRadioActivity
06-15 06:54:09.663 I/Zygote ( 4059): seccomp disabled by setenforce 0
06-15 06:54:09.684 I/CAR.AM ( 2579): onForegroundActivitiesChanged uid 10042 pid 4059 fg true
06-15 06:54:09.689 I/CAR.AM ( 2579): New top task: TaskInfoContainer [topActivity=ComponentInfo{com.android.car.radio/com.android.car.radio.CarRadioActivity}, taskId=6, stackId=2, userId=0
06-15 06:54:09.690 I/CAR.AM ( 2579): activity launched:TaskInfoContainer [topActivity=ComponentInfo{com.android.car.radio/com.android.car.radio.CarRadioActivity}, taskId=6, stackId=2, userId=0
06-15 06:54:09.698 D/PermissionCache( 1572): checking android.permission.READ_FRAME_BUFFER for uid=1000 => granted (764 us)
06-15 06:54:09.702 I/droid.car.radi( 4059): The ClassLoaderContext is a special shared library.
06-15 06:54:09.727 I/display ( 2579): open gpu gralloc module!
06-15 06:54:09.745 I/CAR.AM ( 2579): onTaskStackChanged
06-15 06:54:10.016 D/OpenGLRenderer( 4059): Skia GL Pipeline
06-15 06:54:10.064 W/SystemServiceRegistry( 4059): No service published for: broadcastradio
06-15 06:54:10.064 D/AndroidRuntime( 4059): Shutting down VM
--------- beginning of crash
06-15 06:54:10.065 E/AndroidRuntime( 4059): FATAL EXCEPTION: main
06-15 06:54:10.065 E/AndroidRuntime( 4059): Process: com.android.car.radio, PID: 4059
06-15 06:54:10.065 E/AndroidRuntime( 4059): java.lang.RuntimeException: Unable to create service com.android.car.radio.RadioService: java.lang.NullPointerException: RadioManager could not be loaded
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.app.ActivityThread.handleCreateService(ActivityThread.java:3582)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.app.ActivityThread.access$1300(ActivityThread.java:200)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1672)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.os.Handler.dispatchMessage(Handler.java:106)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.os.Looper.loop(Looper.java:193)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.app.ActivityThread.main(ActivityThread.java:6718)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at java.lang.reflect.Method.invoke(Native Method)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:917)
06-15 06:54:10.065 E/AndroidRuntime( 4059): Caused by: java.lang.NullPointerException: RadioManager could not be loaded
06-15 06:54:10.065 E/AndroidRuntime( 4059): at java.util.Objects.requireNonNull(Objects.java:228)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at com.android.car.radio.platform.RadioManagerExt.<init>(RadioManagerExt.java:62)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at com.android.car.radio.RadioService.onCreate(RadioService.java:127)
06-15 06:54:10.065 E/AndroidRuntime( 4059): at android.app.ActivityThread.handleCreateService(ActivityThread.java:3570)
06-15 06:54:10.065 E/AndroidRuntime( 4059): ... 8 more
06-15 06:54:10.067 W/ActivityManager( 2390): Force finishing activity com.android.car.radio/.CarRadioActivity
06-15 06:54:10.073 I/Process ( 4059): Sending signal. PID: 4059 SIG: 9
06-15 06:54:10.127 W/InputDispatcher( 2390): channel 'bcf9e50 com.android.car.radio/com.android.car.radio.CarRadioActivity (server)' ~ Consumer closed input channel or an error occurred. events=0x9
06-15 06:54:10.127 E/InputDispatcher( 2390): channel 'bcf9e50 com.android.car.radio/com.android.car.radio.CarRadioActivity (server)' ~ Channel is unrecoverably broken and will be disposed!
06-15 06:54:10.128 I/ActivityManager( 2390): Process com.android.car.radio (pid 4059) has died: fore TOP
06-15 06:54:10.128 I/WindowManager( 2390): WIN DEATH: Window{bcf9e50 u0 com.android.car.radio/com.android.car.radio.CarRadioActivity}
06-15 06:54:10.128 W/InputDispatcher( 2390): Attempted to unregister already unregistered input channel 'bcf9e50 com.android.car.radio/com.android.car.radio.CarRadioActivity (server)'
06-15 06:54:10.128 W/libprocessgroup( 2390): kill(-4059, 9) failed: No such process
06-15 06:54:10.129 I/Zygote ( 2296): Process 4059 exited due to signal (9)
06-15 06:54:10.155 W/ActivityManager( 2390): setHasOverlayUi called on unknown pid: 4059
06-15 06:54:10.168 I/CAR.AM ( 2579): onTaskStackChanged
06-15 06:54:10.171 I/CAR.AM ( 2579): New top task: TaskInfoContainer [topActivity=ComponentInfo{com.android.car.carlauncher/com.android.car.carlauncher.AppGridActivity}, taskId=5, stackId=1, userId=0
然后咨询imx8 ndk 技术支持,在他的指导下,收音机服务打开成功。但是原因不懂。下面先把解决方案粘出来。
在device/fsl/目录下根据补丁位置打上补丁:
device_fsl_imx8q_manifest_car.patch
diff --git a/imx8q/mek_8q/manifest_car.xml b/imx8q/mek_8q/manifest_car.xml
index eabe20f4..32f5d415 100644
--- a/imx8q/mek_8q/manifest_car.xml
+++ b/imx8q/mek_8q/manifest_car.xml
@@ -264,4 +264,13 @@
<instance>default</instance>
</interface>
</hal>
+ <hal format="hidl" optional="true">
+ <name>android.hardware.broadcastradio</name>
+ <transport>hwbinder</transport>
+ <version>2.0</version>
+ <interface>
+ <name>IBroadcastRadio</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
</manifest>
device_fsl_imx8q_mek_8q.patch
diff --git a/imx8q/mek_8q/mek_8q_car.mk b/imx8q/mek_8q/mek_8q_car.mk
index c8259cbf..9319cefd 100644
--- a/imx8q/mek_8q/mek_8q_car.mk
+++ b/imx8q/mek_8q/mek_8q_car.mk
@@ -34,7 +34,8 @@ PRODUCT_COPY_FILES += \
PRODUCT_COPY_FILES += \
frameworks/native/data/etc/android.hardware.screen.landscape.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.screen.landscape.xml \
frameworks/native/data/etc/android.hardware.type.automotive.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.type.automotive.xml \
- frameworks/native/data/etc/android.software.freeform_window_management.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.freeform_window_management.xml
+ frameworks/native/data/etc/android.software.freeform_window_management.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.freeform_window_management.xml \
+ frameworks/native/data/etc/android.hardware.broadcastradio.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.hardware.broadcastradio.xml
# Add Google prebuilt services
PRODUCT_PACKAGES += \
@@ -54,7 +55,8 @@ PRODUCT_PACKAGES += \
PRODUCT_PACKAGES += \
libion \
vehicle.default \
- android.hardware.automotive.vehicle@2.0-service
+ android.hardware.automotive.vehicle@2.0-service \
+ android.hardware.broadcastradio@2.0-service
# Add Trusty OS backed gatekeeper and secure storage proxy
PRODUCT_PACKAGES += \
注:实际上就是编译android.hardware.broadcastradio@2.0-service、拷贝android.hardware.broadcastradio.xml文件以及将HAL加入清单manifest_car.xml,可根据自己习惯修改位置。
编译完成后会在out/target/product/mek_8q/vendor/bin/hw/目录下生成android.hardware.broadcastradio@2.0-service,在out/target/product/mek_8q/vendor/etc/permissions/目录下生成android.hardware.broadcastradio.xml,
在out/target/product/mek_8q/vendor/etc/init/目录下生成android.hardware.broadcastradio@2.0-service.rc
编译完成重新烧写系统后,因为SELinux的原因,在命令行输入
setenforce 0
使SELinux处于permissive状态,避开Selinux,用于调试,重启失效。
后面的SELinux的关闭方法可以查看我前面的文章:androidP:SElinux权限
为啥加上上边那些,收音机服务就可以启动了。后面还要研究一下。如果有大神可以指导,不胜感激。下面就应用能开始启动收音机服务的源码调用流程 做个记录,以方便后续学习。
二、收音机服务的启动流程
https://www.jianshu.com/p/4a20fcfcb1be (Radio Bringup(App-Framework-Hal))
根据该文档介绍,在android启动启动之后,init进程会启动Zygote进程,Zygote进程进一步启动SystemServer进程。(上述启动流程比较复杂,后面参考 Android 9§之init进程启动源码分析指南之一:https://blog.csdn.net/tkwxty/article/details/106020050 这个链接进一步学习)
从SystemServer进程启动开始分析。
参考链接:https://www.jianshu.com/p/4a20fcfcb1be (Radio Bringup(App-Framework-Hal))
源码路径:
1、SystemServer.java
/frameworks/base/services/java/com/android/server/SystemServer.java
2、ApplicationPackageManager.java
/frameworks/base/core/java/android/app/ApplicationPackageManager.java
3、PackageManagerService.java
/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
4、SystemConfig.java
/frameworks/base/core/java/com/android/server/SystemConfig.java
5、Environment.java
/frameworks/base/core/java/android/os/Environment.java
6、PackageManager.java
/frameworks/base/core/java/android/content/pm/PackageManager.java
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/0c872894f2f6ed57d60cfb31760ddebf.png
备注1:
startOtherServices() {
...
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_BROADCAST_RADIO)) {
traceBeginAndSlog("StartBroadcastRadioService");
mSystemServiceManager.startService(BroadcastRadioService.class);
traceEnd();
}
...
}
没有添加第一部分的解决方案之前,该服务是不能启动的,也就是if条件判断不匹配。hasSystemFeature函数用来判断该设备(硬件)是否支持相应功能(收音机)。下面分析,函数后面的调用流程。其中:public static final String FEATURE_BROADCAST_RADIO = “android.hardware.broadcastradio”;
备注2:
@Override
public boolean hasSystemFeature(String name, int version) {
try {
return mPM.hasSystemFeature(name, version);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
备注3:
SystemConfig systemConfig = SystemConfig.getInstance();
final ArrayMap<String, FeatureInfo> mAvailableFeatures;
mAvailableFeatures = systemConfig.getAvailableFeatures();
@Override
public boolean hasSystemFeature(String name, int version) {
// allow instant applications
synchronized (mAvailableFeatures) {
final FeatureInfo feat = mAvailableFeatures.get(name);
if (feat == null) {
return false;
} else {
return feat.version >= version;
}
}
}
备注4:
在备注3中,定义systemConfig变量获取SystemConfig实例,
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
if (sInstance == null) {
sInstance = new SystemConfig();
}
return sInstance;
}
}
//SystemConfig构造函数中实现方法
SystemConfig() {
...
// Read configuration from the old permissions dir
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
...
}
void readPermissions(File libraryDir, int permissionFlag) {
// Read permissions from given directory.
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
if (permissionFlag == ALLOW_ALL) {
Slog.w(TAG, "No directory " + libraryDir + ", skipping");
}
return;
}
if (!libraryDir.canRead()) {
Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
return;
}
// Iterate over the files in the directory and scan .xml files
File platformFile = null;
for (File f : libraryDir.listFiles()) {
//这里打印日志,截图1
// We'll read platform.xml last
if (f.getPath().endsWith("etc/permissions/platform.xml")) {
platformFile = f;
continue;
}
if (!f.getPath().endsWith(".xml")) {
Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
continue;
}
if (!f.canRead()) {
Slog.w(TAG, "Permissions library file " + f + " cannot be read");
continue;
}
readPermissionsFromXml(f, permissionFlag);
}
// Read platform permissions last so it will take precedence
if (platformFile != null) {
readPermissionsFromXml(platformFile, permissionFlag);
}
}
private void readPermissionsFromXml(File permFile, int permissionFlag) {
...
else if ("feature".equals(name) && allowFeatures) {
String fname = parser.getAttributeValue(null, "name");
int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
boolean allowed;
if (!lowRam) {
allowed = true;
} else {
String notLowRam = parser.getAttributeValue(null, "notLowRam");
allowed = !"true".equals(notLowRam);
}
if (fname == null) {
Slog.w(TAG, "<feature> without name in " + fname + " at "
+ parser.getPositionDescription());
} else if (allowed) {
//这里打印fname = android.hardware.broadcastradio,fversion = 0;
addFeature(fname, fversion);
}
XmlUtils.skipCurrentTag(parser);
continue;
}
...
}
//终于到了addFeature方法,这里只有把name 进去,前面mAvailableFeatures.get(name)才能取到值
private void addFeature(String name, int version) {
FeatureInfo fi = mAvailableFeatures.get(name);
if (fi == null) {
fi = new FeatureInfo();
fi.name = name;
fi.version = version;
mAvailableFeatures.put(name, fi);
} else {
fi.version = Math.max(fi.version, version);
}
}
截图1:
备注5:
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
public static File getRootDirectory() {
return DIR_ANDROID_ROOT;
}
根据上述备注4和备注5流程,想要mAvailableFeatures.get(name)方法取到对应值,首先需要addFeature方法,将对应的name添加到FeatureInfo中。下面分析addFeature是在哪里获取的。直接将链接https://www.jianshu.com/p/4a20fcfcb1be的结论放到这里来。readPermissions(Environment.buildPath(
Environment.getRootDirectory(), “etc”, “permissions”), ALLOW_ALL);}
可以看出来,该权限是在system/etc/permissions下边的xml配置文件中进行的配置。(我使用的是androidP 恩智浦imx8平台 android.hardware.broadcastradio.xml在板子里的位置为 vendor/etc/permissions,从上述日志截图1可以看出,这里文件路径两种都有)。按照第一部分解决问题的方法,添加对应补丁可以生成 android.hardware.broadcastradio.xml文件并将该文件拷贝到vendor/etc/permissions路径下,android系统跑起来后实现收音机服务的启动。
另外我的android.hardware.broadcastradio.xml文件的内容是:
<permissions>
<feature name="android.hardware.broadcastradio" />
</permissions>
三、收音机服务调用流程
从打开RadioActivity开始。
源码路径
1、CarRadioActivity.java
/packages/apps/Car/Radio/src/com/android/car/radio/CarRadioActivity.java
2、RadioController.java
/packages/apps/Car/Radio/src/com/android/car/radio/RadioController.java
3、IRadioManager.aidl
/packages/apps/Car/Radio/src/com/android/car/radio/service/IRadioManager.aidl
IRadioManager.aidl实现类RadioService.java
/packages/apps/Car/Radio/src/com/android/car/radio/RadioService.java
4、RadioManagerExt.java
/packages/apps/Car/Radio/src/com/android/car/radio/platform/RadioManagerExt.java
5、RadioManager.java
/frameworks/base/core/java/android/hardware/radio/RadioManager.java
6、IRadioService.aidl
/frameworks/base/core/java/android/hardware/radio/IRadioService.aidl
IRadioService.aidl实现类在 BroadcastRadioService.java中的内部类ServiceImpl
/frameworks/base/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java
备注1:mRadioManager = (RadioManager)ctx.getSystemService(Context.RADIO_SERVICE);
备注2:@NonNull private final IRadioService mService;
mService = IRadioService.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.RADIO_SERVICE));
备注3:内部类ServiceImpl中两个功能
1)判断收音机服务权限,
private void enforcePolicyAccess() {
if (PackageManager.PERMISSION_GRANTED != getContext().checkCallingPermission(
Manifest.permission.ACCESS_BROADCAST_RADIO)) {
throw new SecurityException("ACCESS_BROADCAST_RADIO permission not granted");
}
}
2)实现三个重载方法:listModules、 openTuner 、addAnnouncementListener
以上为收音机服务调用的流程,后面慢慢深入到hal层,与linux层通信,应该收音机功能就可以了。不知道我想的是不是对的。