Android 8.1プラットフォームSystemUI仮想ナビゲーションキーロードプロセス解析

25543 ワード

需要
MTK 8163 8.1プラットフォームのカスタマイズナビゲーションバー部分に基づいて、左側で音量を増やして減らし、右側で音量を増やして加え、カスタマイズする必要がある手順は次の文章を参照してください.
構想
需要が始まる前に、SystemUI Navigationモジュールのコードフローを研究しなければなりません!!!ネット上のcopyに直接行かないでください.他の人が変更した需要コードは、ブラインドで変更すると問題が発生しやすいですが、解決できません.ネット上には古いプラットフォーム(8.0-)のSystem UIを説明するナビゲーションバーモジュールのブログがあり、自分で検索します.8.0 System UIに対してやはり多くの細部の上の変更をして、コードの変更の体現の上で比較的に多くて、しかし全体の基本的な流れは変わっていません.
ソースコードを読むと、コードの詳細を気にしないでください.例えば、私はこの需要をカスタマイズして、ナビゲーションバーの戻り(back)、デスクトップ(home)、最近のタスク(recent)の中の1つの機能とコードの流れについて、例えばrecenというviewがどの方法でどの方法を調整して最終的にロードしたのか、ロードしたキーコードがどこにあるのか、クリックイベントがどのように生成されるのか、中の具体的な論理判断を気にしないなど、大体知っています.
コードフロー
1.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;
ステータスバー入口から見ます.
 protected void makeStatusBarView() {
        final Context context = mContext;
        updateDisplaySize(); // populates mDisplayMetrics
        updateResources();
        updateTheme();

        ...
        ...
        
         try {
            boolean showNav = mWindowManagerService.hasNavigationBar();
            if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
            if (showNav) {
                createNavigationBar();//     
            }
        } catch (RemoteException ex) {
           
        }
    }
        

2.SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java;
createNavigationBarの方法に入って、主にNavigationBarFragmentで管理することを発見した.
    protected void createNavigationBar() {
        mNavigationBarView = NavigationBarFragment.create(mContext, (tag, fragment) -> {
            mNavigationBar = (NavigationBarFragment) fragment;
            if (mLightBarController != null) {
                mNavigationBar.setLightBarController(mLightBarController);
            }
            mNavigationBar.setCurrentSysuiVisibility(mSystemUiVisibility);
        });
    }

3.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFragment.java;
NavigationBarFragmentのcreateメソッドを見て、やっと分かったのは、WindowManagerがaddViewに行ってナビゲーションバーのレイアウトをし、最終的にfragmentのonCreateViewがロードしたレイアウトをaddしたことだ.(実はSystemUIのすべてのモジュールはWindowManagerでViewをロードしています.)
  public static View create(Context context, FragmentListener listener) {
        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_SLIPPERY,
                PixelFormat.TRANSLUCENT);
        lp.token = new Binder();
        lp.setTitle("NavigationBar");
        lp.windowAnimations = 0;

        View navigationBarView = LayoutInflater.from(context).inflate(
                R.layout.navigation_bar_window, null);

        if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
        if (navigationBarView == null) return null;

        context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
        FragmentHostManager fragmentHost = FragmentHostManager.get(navigationBarView);
        NavigationBarFragment fragment = new NavigationBarFragment();
        fragmentHost.getFragmentManager().beginTransaction()
                .replace(R.id.navigation_bar_frame, fragment, TAG) //  !fragment onCreateView      add   Window   view  。
                .commit();
        fragmentHost.addTagListener(TAG, listener);
        return navigationBarView;
    }
}

4.SystemUI\res\layoutavigation_bar_window.xml;
WindowManagerがロードしたこのviewのレイアウトを見てみましょう:navigation_bar_window.xml,ルートレイアウトがカスタムviewクラスNavigationBarFrameであることが分かった.(実はSystemUIや他のシステムアプリケーション、例えばLauncherは、このようなカスタムviewの方式であり、多くの論理処理もカスタムviewにあり、無視できない)
"http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/navigation_bar_frame"
    android:layout_height="match_parent"
    android:layout_width="match_parent">



5.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarFrame.java;
NavigationBarFrameクラスに入ります.発見クラスは私たちの予想ではなく、FrameLayoutであり、DeadZone機能下のtouchイベントに手を出した.
6.NavigationBarFragmentのライフサイクルを見てみましょう.onCreateView()では、ナビゲーションバーの本物のrootView.
    
 @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        return inflater.inflate(R.layout.navigation_bar, container, false);
    }
    

7.SystemUI\res\layoutavigation_bar.xml;
ナビゲーションバーの真のルートレイアウトに入る:navigation_bar.xml、いいでしょうまたカスタムviewで、NavigationBarViewとNavigationBarInflaterViewはよく研究しなければなりません.
"http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:background="@drawable/system_bar_background">

    "@+id/navigation_inflater"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />




8.SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarInflaterView.java;FrameLayoutから受け継ぐ
まず構造方法を見てみましょう.xmlレイアウトをロードするにはまず初期化が行われますから.
    public NavigationBarInflaterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        createInflaters();//           view(  back home or recent)    
        Display display = ((WindowManager)
                context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
        Mode displayMode = display.getMode();
        isRot0Landscape = displayMode.getPhysicalWidth() > displayMode.getPhysicalHeight();
    }
 private void inflateChildren() {
        removeAllViews();
        mRot0 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout, this, false);
        mRot0.setId(R.id.rot0);
        addView(mRot0);
        mRot90 = (FrameLayout) mLayoutInflater.inflate(R.layout.navigation_layout_rot90, this,
                false);
        mRot90.setId(R.id.rot90);
        addView(mRot90);
        updateAlternativeOrder();
    }

さらにonFinishInflate()メソッドを見ると、これはviewのライフサイクルであり、各viewがinflateされた後にコールバックされます.
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        inflateChildren();//          
        clearViews();//          
        inflateLayout(getDefaultLayout());//    :    back.home.recent     layout
    }

inflateLayout():中のnewLayoutパラメータはとても重要です!!!前の方法でgetDefaultLayout()を見て、彼はxmlで死んだ文字列をreturnしました.さらにinflateLayoutメソッドを見て、xmlに配置された文字列を解析的に分割し、inflateButtonsメソッドに伝えた.
    protected void inflateLayout(String newLayout) {
        mCurrentLayout = newLayout;
        if (newLayout == null) {
            newLayout = getDefaultLayout();
        }
        String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//  “;”       3   
        String[] start = sets[0].split(BUTTON_SEPARATOR);//  “,”   ,   left[.5W] back[1WC]
        String[] center = sets[1].split(BUTTON_SEPARATOR);//  home
        String[] end = sets[2].split(BUTTON_SEPARATOR);//  recent[1WC] right[.5W]
        // Inflate these in start to end order or accessibility traversal will be messed up.
        inflateButtons(start, mRot0.findViewById(R.id.ends_group), isRot0Landscape, true);
        inflateButtons(start, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, true);

        inflateButtons(center, mRot0.findViewById(R.id.center_group), isRot0Landscape, false);
        inflateButtons(center, mRot90.findViewById(R.id.center_group), !isRot0Landscape, false);

        addGravitySpacer(mRot0.findViewById(R.id.ends_group));
        addGravitySpacer(mRot90.findViewById(R.id.ends_group));

        inflateButtons(end, mRot0.findViewById(R.id.ends_group), isRot0Landscape, false);
        inflateButtons(end, mRot90.findViewById(R.id.ends_group), !isRot0Landscape, false);
    }
    
        protected String getDefaultLayout() {
        return mContext.getString(R.string.config_navBarLayout);
    }
    //SystemUI\res\values\config.xml
     
    "config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]

さらにinflateButtons()メソッドを見て、inflateButtonをループロードします.
    private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
            boolean start) {
        for (int i = 0; i < buttons.length; i++) {
            inflateButton(buttons[i], parent, landscape, start);
        }
    }
    
    @Nullable
    protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
            boolean start) {
        LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
        View v = createView(buttonSpec, parent, inflater);//  view
        if (v == null) return null;

        v = applySize(v, buttonSpec, landscape, start);
        parent.addView(v);//addView    
        addToDispatchers(v);
        View lastView = landscape ? mLastLandscape : mLastPortrait;
        View accessibilityView = v;
        if (v instanceof ReverseFrameLayout) {
            accessibilityView = ((ReverseFrameLayout) v).getChildAt(0);
        }
        if (lastView != null) {
            accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
        }
        if (landscape) {
            mLastLandscape = accessibilityView;
        } else {
            mLastPortrait = accessibilityView;
        }
        return v;
    }

createView()メソッドを見てみましょう.ホームボタンを例にとると、ホームのbuttonがロードされていますが、実はR.layoutがロードされています.ホームのlayoutレイアウト
    private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
        View v = null;
     
        ...
        ...
        
        if (HOME.equals(button)) {
            v = inflater.inflate(R.layout.home, parent, false);
        } else if (BACK.equals(button)) {
            v = inflater.inflate(R.layout.back, parent, false);
        } else if (RECENT.equals(button)) {
            v = inflater.inflate(R.layout.recent_apps, parent, false);
        } else if (MENU_IME.equals(button)) {
            v = inflater.inflate(R.layout.menu_ime, parent, false);
        } else if (NAVSPACE.equals(button)) {
            v = inflater.inflate(R.layout.nav_key_space, parent, false);
        } else if (CLIPBOARD.equals(button)) {
            v = inflater.inflate(R.layout.clipboard, parent, false);
        } 
        
        ...
        ...
        
        return v;
    }
    //SystemUI\res\layout\home.xml 
    //       src  home icon,          
    //       view:KeyButtonView
    "http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home"
    android:layout_width="@dimen/navigation_key_width"//   dimens.xml  navigation_key_width
    android:layout_height="match_parent"
    android:layout_weight="0"
    systemui:keyCode="3"//systemui      
    android:scaleType="fitCenter"
    android:contentDescription="@string/accessibility_home"
    android:paddingTop="@dimen/home_padding"
    android:paddingBottom="@dimen/home_padding"
    android:paddingStart="@dimen/navigation_key_padding"
    android:paddingEnd="@dimen/navigation_key_padding"
    />
    

9.SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.JAvaはまずKeyButtonViewの構造方法を見てみましょう.xmlのsystemui:keyCode=「3」の方法はここで取得しました.さらにTouchイベントを見ると、sendEvent()メソッドにより、backなどのviewのクリックtouchイベントは自分で処理するのではなく、システムによって実体ボタン(keycode)の形で処理されていることがわかる.
もちろんKeyButtonViewクラスでは,長押をサポートするbutton,ボタンの音なども扱っているが,ここでは無視する.
これで、ナビゲーションバーのボタンイベントを整理しました.
    public KeyButtonView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
                defStyle, 0);

        mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, 0);

        mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true);
        mPlaySounds = a.getBoolean(R.styleable.KeyButtonView_playSound, true);

        TypedValue value = new TypedValue();
        if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
            mContentDescriptionRes = value.resourceId;
        }

        a.recycle();

        setClickable(true);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

        mRipple = new KeyButtonRipple(context, this);
        setBackground(mRipple);
    }
    
    ...
    ...
    
 public boolean onTouchEvent(MotionEvent ev) {
       
       ...
       
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mDownTime = SystemClock.uptimeMillis();
                mLongClicked = false;
                setPressed(true);
                if (mCode != 0) {
                    sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);//    
                } else {
                    // Provide the same haptic feedback that the system offers for virtual keys.
                    performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
                }
                playSoundEffect(SoundEffectConstants.CLICK);
                removeCallbacks(mCheckLongPress);
                postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
                break;
          
            ...
            ...
            
        }

        return true;
    }
    
 void sendEvent(int action, int flags, long when) {
        mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
                .setType(MetricsEvent.TYPE_ACTION)
                .setSubtype(mCode)
                .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
                .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
        final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
        //    mCode new   KeyEvent  ,  injectInputEvent     。
        final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
                0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                InputDevice.SOURCE_KEYBOARD);
        InputManager.getInstance().injectInputEvent(ev,
                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    }
    

10.画像を設定するiconはいったいどこにあるのかという質問も残っています.私たちがずっと読んでいたのはNavigationBarInflaterViewで、レイアウトによって私たちはもう一つのクラスを見ていません.NavigationBarView.java
SystemUI\src\com\android\systemui\statusbar\phone\NavigationBarView.java;
NavigationBarViewクラスに入り、構造方法を見つけます.
    public NavigationBarView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mDisplay = ((WindowManager) context.getSystemService(
                Context.WINDOW_SERVICE)).getDefaultDisplay();

      
        ...
        ...
        
        updateIcons(context, Configuration.EMPTY, mConfiguration);//    

        mBarTransitions = new NavigationBarTransitions(this);

        //mButtonDispatchers      home back recent  view    ,      child,NavigationBarInflaterView  
        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
        mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
        mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
        mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
        mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
        mButtonDispatchers.put(R.id.accessibility_button,
                new ButtonDispatcher(R.id.accessibility_button));

    }
    
     private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) {
 
           ...

            iconLight = mNavBarPlugin.getHomeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_home));
            iconDark = mNavBarPlugin.getHomeImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_home_dark));
            //mHomeDefaultIcon = getDrawable(ctx,
            //        R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark);
            mHomeDefaultIcon = getDrawable(iconLight,iconDark);

            //   icon  
            iconLight = mNavBarPlugin.getRecentImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_recent));
            //   icon  
            iconDark = mNavBarPlugin.getRecentImage(
                                        ctx.getDrawable(R.drawable.ic_sysbar_recent_dark));
            //mRecentIcon = getDrawable(ctx,
            //        R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
            mRecentIcon = getDrawable(iconLight,iconDark);


            mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu,
                                        R.drawable.ic_sysbar_menu_dark);
         
           ...
           ...
        
    }

11.第10から分かるように、recentを例にとると、初期化時にmRecentIconのリソースが得られ、誰がmRecentIconを呼び出したかを見れば分かる、すなわち呼び出しの流れを反転して見ることができる.
  private void updateRecentsIcon() {
        getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
        mBarTransitions.reapplyDarkIntensity();
    }

updateRecentsIconこの方法はrecentピクチャのリソースを設定し、誰がupdateRecentsIconメソッドを呼び出したかを見てみましょう:onConfigurationChanged画面が回転するとリソースピクチャが再設定されます
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        boolean uiCarModeChanged = updateCarMode(newConfig);
        updateTaskSwitchHelper();
        updateIcons(getContext(), mConfiguration, newConfig);
        updateRecentsIcon();
        if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi
                || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) {
            // If car mode or density changes, we need to reset the icons.
            setNavigationIconHints(mNavigationIconHints, true);
        }
        mConfiguration.updateFrom(newConfig);
    }
    public void setNavigationIconHints(int hints, boolean force) {
       
        ...
        ...
        
        mNavigationIconHints = hints;

        // We have to replace or restore the back and home button icons when exiting or entering
        // carmode, respectively. Recents are not available in CarMode in nav bar so change
        // to recent icon is not required.
        KeyButtonDrawable backIcon = (backAlt)
                ? getBackIconWithAlt(mUseCarModeUi, mVertical)
                : getBackIcon(mUseCarModeUi, mVertical);

        getBackButton().setImageDrawable(backIcon);

        updateRecentsIcon();

        ...
        ...
        
    }

reorient()はsetNavigationIconHints()メソッドも呼び出します.
    public void reorient() {
        updateCurrentView();

        ...
        
        setNavigationIconHints(mNavigationIconHints, true);

        getHomeButton().setVertical(mVertical);
    }

さらに上に進むと、最終的にNavigationBarFragmentのonConfigurationChanged()メソッドとNavigationBarViewのonAttachedToWindow()メソッドとonSizeChanged()メソッドに遡る.つまり、NavigationBarViewナビゲーションバーというレイアウトがロードされたときに画像リソースが設定され、長さが変更され、画面の回転が再設定される可能性があります
これで、SystemUIの仮想ナビゲーションバーモジュールコード処理が終了します.
まとめ
  • windowプロパティの親view
  • を作成
  • 解析xml内のconfigの構成、addViewに必要なicon、または置換順序
  • を読み取る.
  • srcピクチャリソースコードにより明るい色と暗い色を設定
  • touchイベントはkeycode方式でシステム処理
  • に渡す.
    次のセクションでは、音量の増減のカスタマイズ手順について説明します.
    転載先:https://juejin.im/post/5ad9a077f265da0b767d0669