Android源码个个击破之“设置”

6.0设置源码分析:https://blog.csdn.net/zrf1335348191/article/details/51469058?locationNum=8

 

1. 源码位置:/packages/apps/Settings/

2. Settings.java即是应用的首页

3. |----SettingsActivity.java   (和Settings.java就在一个包下,不要全局搜索,否则会搜到多个SettingsActivity这个类)

4.  Android6.0源码分析:

       1-1:清单文件分析

       通过清单文件可以知道设置应用的启动类是Settings.java

        image.png       

         看看清单文件里,可以发现很多的activity都有这样的meta标签定义,这些activity都是Settings.java的内部类,UI都是通过Fragment来实现的。

        image.png

         1-2:Settings.java分析

                image.png

                继承自SettingsActivity,其它的都是一些内部类,虽然是空实现,但是都继承了SettingsActivity.

               说实话,这么诡异的写法我生平还是第一次见!

     

                

         1-3:SettingsActivity分析

         第一步:看onCreate方法
         image.png

         首先调用了一个getMetaData的方法

     private void getMetaData() {
    try {
        ActivityInfo ai = getPackageManager().getActivityInfo(getComponentName(),
                PackageManager.GET_META_DATA);
        if (ai == null || ai.metaData == null) return;
        mFragmentClass = ai.metaData.getString(META_DATA_KEY_FRAGMENT_CLASS);
    } catch (NameNotFoundException nnfe) {
        // No recovery
        Log.d(LOG_TAG, "Cannot get Metadata for: " + getComponentName().toString());
    }
}

        PackageManager从清单文件里解析当前Activity的信息,并且从META_DATA获取到了对应的Fragment的类名。

   private static final String META_DATA_KEY_FRAGMENT_CLASS =212        "com.android.settings.FRAGMENT_CLASS";

        这个正与清单文件里的meta的key一样。

        

    1-4:Usb网络共享界面

        项目有个需要,需要判断一下USB网络共享开关是否打开 。但是百度几乎是没有答案的,我就想着研究一下源码,看看设置里的那个页面是怎么去刷新这个UI的。

 

        image.png

         

        找到网络共享设置页面:TetherSettings.java

        TetherSettings

                |-----SettingsPreferenceFragment

                     |---------InstrumentedPreferenceFragment

                            |--------PreferenceFragment

        

        布局是在PreferenceFragment这个类里实现的,默认使用image.png布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:background="@android:color/transparent"
        android:layout_removeBorders="true">
    
        <ListView android:id="@android:id/list"
            style="?attr/preferenceFragmentListStyle"
            android:layout_width="match_parent"
            android:layout_height="0px"
            android:layout_weight="1"
            android:paddingTop="0dip"
            android:paddingBottom="@dimen/preference_fragment_padding_bottom"
            android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
            android:clipToPadding="false"
            android:drawSelectorOnTop="false"
            android:cacheColorHint="@android:color/transparent"
            android:scrollbarAlwaysDrawVerticalTrack="true" />
    
        <TextView android:id="@android:id/empty"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="@dimen/preference_fragment_padding_side"
            android:gravity="center"
            android:visibility="gone" />
    
        <RelativeLayout android:id="@+id/button_bar"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:layout_weight="0"
            android:visibility="gone">
    
            <Button android:id="@+id/back_button"
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_margin="5dip"
                android:layout_alignParentStart="true"
                android:text="@string/back_button_label"
            />
            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true">
    
                <Button android:id="@+id/skip_button"
                    android:layout_width="0dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@string/skip_button_label"
                    android:visibility="gone"
                />
    
                <Button android:id="@+id/next_button"
                    android:layout_width="0dip"
                    android:layout_height="wrap_content"
                    android:layout_margin="5dip"
                    android:text="@string/next_button_label"
                />
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>

        这些设置里的按钮,是如何更新UI的呢?

        image.png

        

       可以看到USB网络共享的开关是通过mUsbTether这个对象来控制的。至此,USB网络共享是否开启的代码也就找到了。

 

      image.png

        findPreference是父类PreferenceFragment的方法:

     image.png

    方法引用了PreferenceManager对象,对象在fragment创建时创建:

      image.png

          通过代码可以判断刷新布局的应该这个方法:

        image.png

          将preferenceScreen这个对象与前面提到的布局里的ListView绑定起来。

         这个preferenceScreen对象通过下面的方法赋值:

         image.png

            那么,问题来了,这两个方法什么在哪里被调用的呢?

           

         image.png

          

        文件内容如下:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    
        <SwitchPreference
            android:key="usb_tether_settings"
            android:title="@string/usb_tethering_button_text"
            android:persistent="false" />
    
        <SwitchPreference
            android:key="enable_wifi_ap"
            android:title="@string/wifi_tether_checkbox_text"
            android:persistent="false" />
    
        <Preference
            android:key="wifi_ap_ssid_and_security"
            android:title="@string/wifi_tether_configure_ap_text"
            android:persistent="false" />
    
        <SwitchPreference
            android:key="enable_bluetooth_tethering"
            android:title="@string/bluetooth_tether_checkbox_text"
            android:persistent="false" />
    
    </PreferenceScreen>
   到这里就清楚了,settings里的这些开关,并不是单独使用switchButtont等控制堆叠起来的,而是通过解析res/xml下的xml文件,将标签解析(采用XmlPullParser)出现,转化成PreferenceScreen对象,
   再将PreferenceBean对象与布局里的ListView绑定起来。

       

当然,设置里的源码粘贴过来,不能直接使用,很多字段和方法是hide的,需要通过反射来搞定。下面是我测试过的判断usb网络共享是否开启的源码:

package com.refactor.usb_share;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * Created by XinYi on 8/8/10.
 * 监听USB网络共享是否开启
 */
public class UsbShareStateMonitor {
    private Context context;
    private final String TAG = "UsbShareStateMonitor";
    private static UsbShareStateMonitor instance = new UsbShareStateMonitor();
    private ConnectivityManager cm;
    private String[] forReflect = new String[]{};
    private boolean mMassStorageActive;
    private boolean mUsbConnected;

    private String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED";
    /**
     * @hide gives a String[] listing all the interfaces configured for
     * tethering and currently available for tethering.
     */
    private final String EXTRA_AVAILABLE_TETHER = "availableArray";

    /**
     * @hide gives a String[] listing all the interfaces currently tethered
     * (ie, has dhcp support and packets potentially forwarded/NATed)
     */
    private final String EXTRA_ACTIVE_TETHER = "activeArray";

    /**
     * @hide gives a String[] listing all the interfaces we tried to tether and
     * failed.  Use {@link #getLastTetherError} to find the error code
     * for any interfaces listed here.
     */
    private final String EXTRA_ERRORED_TETHER = "erroredArray";

    /**
     * Broadcast Action:  External media is no longer being shared via USB mass storage.
     * The path to the mount point for the previously shared media is contained in the Intent.mData field.
     *
     * @hide
     */
    private final String ACTION_MEDIA_UNSHARED = "android.intent.action.MEDIA_UNSHARED";
    private final String ACTION_USB_STATE =
            "android.hardware.usb.action.USB_STATE";
    private final String USB_CONNECTED = "connected";
    private final int TETHER_ERROR_NO_ERROR = 0;
    private String[] mUsbRegexs;
    private boolean isUsbShareOpened;
    private TetherChangeReceiver mTetherChangeReceiver;

    private UsbShareStateMonitor() {
        isUsbShareOpened = false;
    }

    public static UsbShareStateMonitor getInstance() {
        return instance;
    }

    public void regist(Context context) {
        this.context = context;
        cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        mUsbRegexs = getTetherableUsbRegexs();
        registTetherChangeReceiver();
    }

    public void unRegist(){
        if(context != null){
            context.unregisterReceiver(mTetherChangeReceiver);
            mTetherChangeReceiver = null;
        }
    }

    private void registTetherChangeReceiver() {
        mTetherChangeReceiver = new TetherChangeReceiver();
        IntentFilter filter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
        Intent intent = context.registerReceiver(mTetherChangeReceiver, filter);

        filter = new IntentFilter();
        filter.addAction(ACTION_USB_STATE);
        context.registerReceiver(mTetherChangeReceiver, filter);

        filter = new IntentFilter();
        filter.addAction(Intent.ACTION_MEDIA_SHARED);
        filter.addAction(ACTION_MEDIA_UNSHARED);
        filter.addDataScheme("file");
        context.registerReceiver(mTetherChangeReceiver, filter);
        if (intent != null) mTetherChangeReceiver.onReceive(context, intent);
    }

    public boolean isUsbShareOpened() {
        return isUsbShareOpened;
    }


    private class TetherChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context content, Intent intent) {
            String action = intent.getAction();
            if (action.equals(ACTION_TETHER_STATE_CHANGED)) {
                // TODO - this should understand the interface types
                ArrayList<String> available = intent.getStringArrayListExtra(
                        EXTRA_AVAILABLE_TETHER);
                ArrayList<String> active = intent.getStringArrayListExtra(
                        EXTRA_ACTIVE_TETHER);
                ArrayList<String> errored = intent.getStringArrayListExtra(
                        EXTRA_ERRORED_TETHER);
                updateState(available.toArray(new String[available.size()]),
                        active.toArray(new String[active.size()]),
                        errored.toArray(new String[errored.size()]));
            } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
                mMassStorageActive = true;
                updateState();
            } else if (action.equals(ACTION_MEDIA_UNSHARED)) {
                mMassStorageActive = false;
                updateState();
            } else if (action.equals(ACTION_USB_STATE)) {
                mUsbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
                updateState();
            }
        }
    }

    private void updateState() {
        String[] available = getTetherableIfaces();
        String[] tethered = getTetheredIfaces();
        String[] errored = getTetheringErroredIfaces();
        updateState(available, tethered, errored);
    }

    private void updateState(String[] available, String[] tethered,
                             String[] errored) {
        updateUsbState(available, tethered, errored);
    }


    private void updateUsbState(String[] available, String[] tethered,
                                String[] errored) {
        mUsbRegexs = getTetherableUsbRegexs();
        if(mUsbRegexs == null) {
            Log.e(TAG, "mUsbRegexs == null ");
            return;
        }
        boolean usbTethered = false;
        for (String s : tethered) {
            for (String regex : mUsbRegexs) {
                if (s.matches(regex)) usbTethered = true;
            }
        }
        isUsbShareOpened = usbTethered;
    }

    //hide方法
    private String[] getTetherableUsbRegexs() {
        return getCmHideMethods("getTetherableUsbRegexs");
    }

    private String[] getTetherableIfaces() {
        return getCmHideMethods("getTetherableIfaces");
    }

    private String[] getTetheredIfaces() {
        return getCmHideMethods("getTetheredIfaces");
    }

    private String[] getTetheringErroredIfaces() {
        return getCmHideMethods("getTetheringErroredIfaces");
    }


    private String[] getCmHideMethods(String methodName) {
        try {
            Method method = cm.getClass().getDeclaredMethod(methodName);
            method.setAccessible(true);
            String[] result = (String[]) method.invoke(cm);
            return result;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }


}

 


猜你喜欢

转载自blog.51cto.com/4259297/2161333