Android - Unsupported Service: audio

2019-02-12 03:53发布

问题:

I am trying to understand and resolve and error I am seeing in the Eclipse workspace log while working on an Android app that implements an IME. I am new to Android and Eclipse.

The error is "com.utterkaos.keyboard.LatinKeyboardView failed to instantiate."

The associated stack trace is:

java.lang.UnsupportedOperationException: Unsupported Service: audio at com.android.layoutlib.bridge.android.BridgeContext.getSystemService(BridgeContext.java:434) at android.inputmethodservice.KeyboardView.(KeyboardView.java:376) at android.inputmethodservice.KeyboardView.(KeyboardView.java:279) at com.utterkaos.keyboard.LatinKeyboardView.(LatinKeyboardView.java:30) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.lang.reflect.Constructor.newInstance(Unknown Source) at com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback.instantiateClass(ProjectCallback.java:402) at com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback.loadView(ProjectCallback.java:166) at android.view.BridgeInflater.loadCustomView(BridgeInflater.java:207) at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:135) at android.view.LayoutInflater.inflate(LayoutInflater.java:466) at android.view.LayoutInflater.inflate(LayoutInflater.java:372) at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:321) at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:324) at com.android.ide.common.rendering.LayoutLibrary.createSession(LayoutLibrary.java:325) at com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService.createRenderSession(RenderService.java:372) at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.renderWithBridge(GraphicalEditorPart.java:1361) at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.recomputeLayout(GraphicalEditorPart.java:1115) at com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart.activated(GraphicalEditorPart.java:941) at com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate.delegatePageChange(LayoutEditorDelegate.java:450) at com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor.pageChange(CommonXmlEditor.java:358) at org.eclipse.ui.part.MultiPageEditorPart.setActivePage(MultiPageEditorPart.java:1067) at org.eclipse.ui.forms.editor.FormEditor.setActivePage(FormEditor.java:607) at com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor.selectDefaultPage(AndroidXmlEditor.java:380) at com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor.addPages(AndroidXmlEditor.java:285) at com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor.addPages(CommonXmlEditor.java:283) at org.eclipse.ui.forms.editor.FormEditor.createPages(FormEditor.java:138) at org.eclipse.ui.part.MultiPageEditorPart.createPartControl(MultiPageEditorPart.java:348) at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:670) at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:465) at org.eclipse.ui.internal.WorkbenchPartReference.getPart(WorkbenchPartReference.java:595) at org.eclipse.ui.internal.EditorReference.getEditor(EditorReference.java:289) at org.eclipse.ui.internal.WorkbenchPage.busyOpenEditorBatched(WorkbenchPage.java:2945) at org.eclipse.ui.internal.WorkbenchPage.busyOpenEditor(WorkbenchPage.java:2850) at org.eclipse.ui.internal.WorkbenchPage.access$11(WorkbenchPage.java:2842) at org.eclipse.ui.internal.WorkbenchPage$10.run(WorkbenchPage.java:2793) at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70) at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2789) at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2773) at org.eclipse.ui.internal.WorkbenchPage.openEditor(WorkbenchPage.java:2764) at org.eclipse.ui.ide.IDE.openEditor(IDE.java:651) at org.eclipse.ui.ide.IDE.openEditor(IDE.java:610) at org.eclipse.jdt.internal.ui.javaeditor.EditorUtility.openInEditor(EditorUtility.java:355) at org.eclipse.jdt.internal.ui.javaeditor.EditorUtility.openInEditor(EditorUtility.java:164) at org.eclipse.jdt.ui.actions.OpenAction.run(OpenAction.java:249) at org.eclipse.jdt.ui.actions.OpenAction.run(OpenAction.java:228) at org.eclipse.jdt.ui.actions.SelectionDispatchAction.dispatchRun(SelectionDispatchAction.java:275) at org.eclipse.jdt.ui.actions.SelectionDispatchAction.run(SelectionDispatchAction.java:251) at org.eclipse.jdt.internal.ui.packageview.PackageExplorerActionGroup.handleOpen(PackageExplorerActionGroup.java:376) at org.eclipse.jdt.internal.ui.packageview.PackageExplorerPart$4.open(PackageExplorerPart.java:538) at org.eclipse.ui.OpenAndLinkWithEditorHelper$InternalListener.open(OpenAndLinkWithEditorHelper.java:48) at org.eclipse.jface.viewers.StructuredViewer$2.run(StructuredViewer.java:866) at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:42) at org.eclipse.ui.internal.JFaceUtil$1.run(JFaceUtil.java:49) at org.eclipse.jface.util.SafeRunnable.run(SafeRunnable.java:175) at org.eclipse.jface.viewers.StructuredViewer.fireOpen(StructuredViewer.java:864) at org.eclipse.jface.viewers.StructuredViewer.handleOpen(StructuredViewer.java:1152) at org.eclipse.jface.viewers.StructuredViewer$6.handleOpen(StructuredViewer.java:1256) at org.eclipse.jface.util.OpenStrategy.fireOpenEvent(OpenStrategy.java:275) at org.eclipse.jface.util.OpenStrategy.access$2(OpenStrategy.java:269) at org.eclipse.jface.util.OpenStrategy$1.handleEvent(OpenStrategy.java:309) at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84) at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1053) at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4165) at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3754) at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2701) at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2665) at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2499) at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:679) at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332) at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:668) at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149) at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:123) at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110) at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344) at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622) at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577) at org.eclipse.equinox.launcher.Main.run(Main.java:1410) at org.eclipse.equinox.launcher.Main.main(Main.java:1386)

The relevant part of LatinKeyboardView.java is:

public class LatinKeyboardView extends KeyboardView {

    static final int KEYCODE_OPTIONS = -100;

    public LatinKeyboardView(Context context, AttributeSet attrs) {
        super(context, attrs);

}

Line 30 is "super(context, attrs);"

Looking at KeyboardView.java, line 376:

mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

Here "Context.AUDIO_SERVICE" appears to be the string "audio", which appears in the error stack trace.

The relevant bit of BridgeContext.java is:

    public Object getSystemService(String service) {
414        if (LAYOUT_INFLATER_SERVICE.equals(service)) {
415            return mBridgeInflater;
416        }
417
418        if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
419            // we need to return a valid service to avoid NPE
420            return TextServicesManager.getInstance();
421        }
422
423        // AutoCompleteTextView and MultiAutoCompleteTextView want a window
424        // service. We don't have any but it's not worth an exception.
425        if (WINDOW_SERVICE.equals(service)) {
426            return null;
427        }
428
429        // needed by SearchView
430        if (INPUT_METHOD_SERVICE.equals(service)) {
431            return null;
432        }
433
434        throw new UnsupportedOperationException("Unsupported Service: " + service);
435    }

What I find particularly puzzling in this routine is that I don't see how it could ever handle the "audio" service, yet BridgeContext.java and KeyboardView.java are both part of the Android code, not classes I have written incorrectly.

Any pointers to help me understand why this error is occurring and how to avoid it would be much appreciated.

回答1:

Are you using API 14 or higher? If so thats the problem. I guess it is a bug in that version. In API 11 it works.

If you try API 11 you have to do some hack with overriding the getResources() method. Check this for more info. After this it will work.

Actually i think there is no way to jump through this from your LatinKeyboardView on API 14 (or maybe higher) because you can not even use the isInEditMode() because you definitely have to invoke the View's constructor with super. And that constructor will try to get the audio system service which just simply fails because i guess you try to run this in eclipse graphical editor (actually i got this error when i tried to place my custom view into a layout in the grapical layout editor)

I think that the only way to hack this is to implement your own KeyboardView without the getSystemService. Maybe should not call that method if isInEditMode == true.



回答2:

This is a bug in android.inputmethodservice.KeyboardView

The failing code is

public KeyboardView(Context context, AttributeSet attrs, int defStyle) { 
...
    mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 
...
}

It should be wrapped in isInEditMode() check to skip getting audio manager during layout editing. Strange but I cannot find any issue reported in the Android bug tracker!



回答3:

I found solution.

Use KeyboardViewFix as replace KeyboardView:

public class KeyboardViewFix extends KeyboardView {
    public static boolean inEditMode = true;

    @TargetApi(21) // Build.VERSION_CODES.L
    public KeyboardViewFix(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(inEditMode ? new ContextWrapperInner(context) : context, attrs, defStyleAttr, defStyleRes);
    }

    public KeyboardViewFix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(inEditMode ? new ContextWrapperInner(context) : context, attrs, defStyleAttr);
    }

    public KeyboardViewFix(Context context, AttributeSet attrs) {
        super(inEditMode ? new ContextWrapperInner(context) : context, attrs);
    }

    public static class ContextWrapperInner extends ContextWrapper {
        Context base;
        public ContextWrapperInner(Context base) {
            super(base);
            this.base = base;
        }
        public Object getSystemService(String name) {
            return Context.AUDIO_SERVICE.equals(name) ? null : base.getSystemService(name);
        }       
    }
}

One note: On start your app, before any other code you need set KeyboardViewFix.inEditMode = false; or you can get some errors.



回答4:

I improved the solution by @Enyvy by extending ContextWrapper instead of Context (far less code). Uses ContextWrapper class with delegation of all methods to the base Context, except for the getService() method that is forbid to ask for "audio":

public class ContextWrapperFix extends ContextWrapper {
    private boolean editMode;

    public ContextWrapperFix(Context context, boolean editMode) {
        super(context);
        this.editMode = editMode;
    }

    public Object getSystemService(String name) {
        if (editMode && Context.AUDIO_SERVICE.equals(name)) {
            return null;
        }
        return super.getSystemService(name);
    }
}

Next step is create own class extends KeyboardView:

public class KeyboardViewFix extends KeyboardView {
    public static boolean inEditMode = true;

    @TargetApi(21) // Build.VERSION_CODES.L
    public KeyboardViewFix(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(new ContextWrapperFix(context, inEditMode), attrs, defStyleAttr, defStyleRes);
    }

    public KeyboardViewFix(Context context, AttributeSet attrs, int defStyleAttr) {
        super(new ContextWrapperFix(context, inEditMode), attrs, defStyleAttr);
    }

    public KeyboardViewFix(Context context, AttributeSet attrs) {
        super(new ContextWrapperFix(context, inEditMode), attrs);
    }

}

And use KeyboardViewFix as replace KeyboardView.

One note: On start your app, before any other code you need set KeyboardViewFix.inEditMode = false; or you can get some errors.



回答5:

I've had this issue in the past, now I updated to Android Studio 3.0.1 and I cannot reproduce any more (may have been fixed a long time ago, I didn't open this project for a while).

Two configurations I tried with AS3:

minSdkVersion 10, compileSdkVersion 26, support library 25.4.0
minSdkVersion 14, compileSdkVersion 26, support library 27.0.2

There was probably a change in how Android Studio's Preview pane works.