Android 8.1 MTK平台 Settings源码解析

2024-08-22 06:38

本文主要是介绍Android 8.1 MTK平台 Settings源码解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:客制化开发的过程中接触Setting次数挺多的,但是在接触的过程中发现Setting和其他应用的逻辑很不一样,Setting到底是怎么在实现逻辑的,这个问题一直环绕着在我心里,趁现在有时间,决定写个博客记录一下,温故而知新。


从启动开始说起

进入setting的AndroidManifest.xml里看一看,找启动Activity

<activity-alias android:name="Settings"android:taskAffinity="com.android.settings"android:label="@string/settings_label_launcher"android:launchMode="singleTask"android:targetActivity="Settings"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter><meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts"/>
</activity-alias>

发现启动Activity是Settings,但是前面的标签是activity-alias,所以这是另一个Activity的别名,然后它真实的启动Activity应该是targetActivity所标注的Settings。


走进Settings.java

public class Settings extends SettingsActivity {public static class AssistGestureSettingsActivity extends SettingsActivity { /* empty */}public static class BluetoothSettingsActivity extends SettingsActivity { /* empty */ }public static class SimSettingsActivity extends SettingsActivity { /* empty */ }public static class TetherSettingsActivity extends SettingsActivity { /* empty */ }public static class VpnSettingsActivity extends SettingsActivity { /* empty */ }public static class DateTimeSettingsActivity extends SettingsActivity { /* empty */ }public static class PrivateVolumeForgetActivity extends SettingsActivity { /* empty */ }public static class PrivateVolumeSettingsActivity extends SettingsActivity { /* empty */ }public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ }public static class WifiSettingsActivity extends SettingsActivity { /* empty */ }public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ }public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ }public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ }...//代码省略

第一次打开Setting.java这个文件的时候我被吓到了,里面全是静态内部类,而且都是跟Settings一样,继承了SettingsActivity,但是所有的类内容都是empty,可想而知这里是使用了特殊的抽象才这样设计的,啥也没有,我们就需要去他们的父类去看了。

SettingsActivity

@Override
protected void onCreate(Bundle savedState) {...final ComponentName cn = intent.getComponent();final String className = cn.getClassName();mIsShowingDashboard = className.equals(Settings.class.getName());setContentView(mIsShowingDashboard ?R.layout.settings_main_dashboard :R.layout.settings_main_prefs);...mContent = findViewById(R.id.main_content);...if (savedState != null) {setTitleFromIntent(intent);ArrayList<DashboardCategory> categories =savedState.getParcelableArrayList(SAVE_KEY_CATEGORIES);if (categories != null) {mCategories.clear();mCategories.addAll(categories);setTitleFromBackStack();}mDisplayHomeAsUpEnabled = savedState.getBoolean(SAVE_KEY_SHOW_HOME_AS_UP);} else {launchSettingFragment(initialFragmentName, isSubSettings, intent);}...//代码省略

正常第一次启动自然是进入launchSettingFragment了,

void launchSettingFragment(String initialFragmentName, boolean isSubSettings, Intent intent) {if (!mIsShowingDashboard && initialFragmentName != null) {// UP will be shown only if it is a sub settingsif (mIsShortcut) {mDisplayHomeAsUpEnabled = isSubSettings;} else if (isSubSettings) {mDisplayHomeAsUpEnabled = true;} else {mDisplayHomeAsUpEnabled = false;}setTitleFromIntent(intent);Bundle initialArguments = intent.getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);switchToFragment(initialFragmentName, initialArguments, true, false,mInitialTitleResId, mInitialTitle, false);} else {// Show search icon as up affordance if we are displaying the main DashboardmDisplayHomeAsUpEnabled = true;mInitialTitleResId = R.string.dashboard_title;switchToFragment(DashboardSummary.class.getName(), null /* args */, false, false,mInitialTitleResId, mInitialTitle, false);}}

接着就去开启Fragment

private Fragment switchToFragment(String fragmentName, Bundle args, boolean validate,boolean addToBackStack, int titleResId, CharSequence title, boolean withTransition) {if (validate && !isValidFragment(fragmentName)) {throw new IllegalArgumentException("Invalid fragment for this activity: "+ fragmentName);}Fragment f = Fragment.instantiate(this, fragmentName, args);FragmentTransaction transaction = getFragmentManager().beginTransaction();transaction.replace(R.id.main_content, f);if (withTransition) {TransitionManager.beginDelayedTransition(mContent);}if (addToBackStack) {transaction.addToBackStack(SettingsActivity.BACK_STACK_PREFS);}if (titleResId > 0) {transaction.setBreadCrumbTitle(titleResId);} else if (title != null) {transaction.setBreadCrumbTitle(title);}transaction.commitAllowingStateLoss();getFragmentManager().executePendingTransactions();return f;}

可以看到,这里是直接初始化创建一个fragmentName指定的Fragment,

由于前面mIsShowingDashboard = className.equals(Settings.class.getName());

而第一次启动,当然是Settings.class,所以mIsShowingDashboard = true;

那么这里的初始化第一个Fragment就是DashboardSummary.class了

这样,第一次初始化就是打开Settings这个空Activity,然后加载DashboardSummary这个Fragment。

DashboardSummary

    @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {return inflater.inflate(R.layout.dashboard, container, false);}

dashboard.xml

<com.android.settings.dashboard.conditional.FocusRecyclerViewxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/dashboard_container"android:layout_width="match_parent"android:layout_height="match_parent"android:clipChildren="false"android:clipToPadding="false"android:focusable="false"android:paddingStart="@dimen/dashboard_padding_start"android:paddingEnd="@dimen/dashboard_padding_end"android:paddingTop="@dimen/dashboard_padding_top"android:paddingBottom="@dimen/dashboard_padding_bottom"android:scrollbars="vertical"/>

DashboardSummary的布局文件里,只有一个FocusRecyclerView,所有的视图内容都是加载到这个RecyclerView里的,这也是我们第一次打开Setting,看到的那个页面。

接着

	@Overridepublic void onViewCreated(View view, Bundle bundle) {long startTime = System.currentTimeMillis();mDashboard = view.findViewById(R.id.dashboard_container);mLayoutManager = new LinearLayoutManager(getContext());mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);if (bundle != null) {int scrollPosition = bundle.getInt(EXTRA_SCROLL_POSITION);mLayoutManager.scrollToPosition(scrollPosition);}mDashboard.setLayoutManager(mLayoutManager);mDashboard.setHasFixedSize(true);mDashboard.setListener(this);mDashboard.setDetachListener(this);mAdapter = new DashboardAdapter(getContext(), bundle,mConditionManager.getConditions(),mSuggestionParser, this /* SuggestionDismissController.Callback */);mDashboard.setAdapter(mAdapter);mDashboard.setItemAnimator(new DashboardItemAnimator());mSummaryLoader.setSummaryConsumer(mAdapter);ActionBarShadowController.attachToRecyclerView(getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);rebuildUI();}

在onViewCreated里,处理了Recyclerview的数据加载,其中mDashboard就是Recyclerview,然后我们需要去看

它的setAdapter,这个mAdapter就是DashboardAdapter。

DashboardAdapter

public class DashboardAdapter extends RecyclerView.Adapter<DashboardAdapter.DashboardItemHolder>

构造方法

public DashboardAdapter(Context context, Bundle savedInstanceState,List<Condition> conditions, SuggestionParser suggestionParser,SuggestionDismissController.Callback callback) {List<Tile> suggestions = null;DashboardCategory category = null;int suggestionConditionMode = DashboardData.HEADER_MODE_DEFAULT;mContext = context;final FeatureFactory factory = FeatureFactory.getFactory(context);mMetricsFeatureProvider = factory.getMetricsFeatureProvider();mDashboardFeatureProvider = factory.getDashboardFeatureProvider(context);mSuggestionFeatureProvider = factory.getSuggestionFeatureProvider(context);mCache = new IconCache(context);mSuggestionParser = suggestionParser;mCallback = callback;setHasStableIds(true);if (savedInstanceState != null) {suggestions = savedInstanceState.getParcelableArrayList(STATE_SUGGESTION_LIST);category = savedInstanceState.getParcelable(STATE_CATEGORY_LIST);suggestionConditionMode = savedInstanceState.getInt(STATE_SUGGESTION_CONDITION_MODE, suggestionConditionMode);mSuggestionsShownLogged = savedInstanceState.getStringArrayList(STATE_SUGGESTIONS_SHOWN_LOGGED);} else {mSuggestionsShownLogged = new ArrayList<>();}mDashboardData = new DashboardData.Builder().setConditions(conditions).setSuggestions(suggestions).setCategory(category).setSuggestionConditionMode(suggestionConditionMode).build();}

其中数据的初始化来自于mDashboardData,这里使用的是建造者模式,builder构造。

其中,设置了三个参数setConditions,setSuggestions,setCategory。

    public DashboardData build() {return new DashboardData(this);}
    private DashboardData(Builder builder) {mCategory = builder.mCategory;mConditions = builder.mConditions;mSuggestions = builder.mSuggestions;mSuggestionConditionMode = builder.mSuggestionConditionMode;mItems = new ArrayList<>();buildItemsData();}
private void buildItemsData() {final boolean hasSuggestions = sizeOf(mSuggestions) > 0;final List<Condition> conditions = getConditionsToShow(mConditions);final boolean hasConditions = sizeOf(conditions) > 0;final List<Tile> suggestions = getSuggestionsToShow(mSuggestions);final int hiddenSuggestion =hasSuggestions ? sizeOf(mSuggestions) - sizeOf(suggestions) : 0;final boolean hasSuggestionAndCollapsed = hasSuggestions&& mSuggestionConditionMode == HEADER_MODE_COLLAPSED;final boolean onlyHasConditionAndCollapsed = !hasSuggestions&& hasConditions&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED;/* Top suggestion/condition header. This will be present when there is any suggestion* and the mode is collapsed */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_TOP_HEADER, hasSuggestionAndCollapsed);/* Use mid header if there is only condition & it's in collapsed mode */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER, onlyHasConditionAndCollapsed);/* Suggestion container. This is the card view that contains the list of suggestions.* This will be added whenever the suggestion list is not empty */addToItemList(suggestions, R.layout.suggestion_condition_container,STABLE_ID_SUGGESTION_CONTAINER, sizeOf(suggestions) > 0);/* Second suggestion/condition header. This will be added when there is at least one* suggestion or condition that is not currently displayed, and the user can expand the* section to view more items. */addToItemList(new SuggestionConditionHeaderData(conditions, hiddenSuggestion),R.layout.suggestion_condition_header,STABLE_ID_SUGGESTION_CONDITION_MIDDLE_HEADER,mSuggestionConditionMode != HEADER_MODE_COLLAPSED&& mSuggestionConditionMode != HEADER_MODE_FULLY_EXPANDED&& (hiddenSuggestion > 0 || hasConditions && hasSuggestions));/* Condition container. This is the card view that contains the list of conditions.* This will be added whenever the condition list is not empty */addToItemList(conditions, R.layout.suggestion_condition_container,STABLE_ID_CONDITION_CONTAINER,hasConditions && mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED);/* Suggestion/condition footer. This will be present when the section is fully expanded* or when there is no conditions and no hidden suggestions */addToItemList(null /* item */, R.layout.suggestion_condition_footer,STABLE_ID_SUGGESTION_CONDITION_FOOTER,(hasConditions || hasSuggestions)&& mSuggestionConditionMode == HEADER_MODE_FULLY_EXPANDED|| hasSuggestions&& !hasConditions&& hiddenSuggestion == 0);if(mCategory != null) {for (int j = 0; j < mCategory.tiles.size(); j++) {final Tile tile = mCategory.tiles.get(j);addToItemList(tile, R.layout.dashboard_tile, Objects.hash(tile.title),true /* add */);}}}

这里加载了很多tile,然后全都调用了addToItemList,

    private void addToItemList(Object item, int type, int stableId, boolean add) {if (add) {mItems.add(new Item(item, type, stableId));}}

这个Item就是存储设置选项的信息

    static class Item {// valid types in field typeprivate static final int TYPE_DASHBOARD_TILE = R.layout.dashboard_tile;private static final int TYPE_SUGGESTION_CONDITION_CONTAINER =R.layout.suggestion_condition_container;private static final int TYPE_SUGGESTION_CONDITION_HEADER =R.layout.suggestion_condition_header;private static final int TYPE_SUGGESTION_CONDITION_FOOTER =R.layout.suggestion_condition_footer;private static final int TYPE_DASHBOARD_SPACER = R.layout.dashboard_spacer;@IntDef({TYPE_DASHBOARD_TILE, TYPE_SUGGESTION_CONDITION_CONTAINER,TYPE_SUGGESTION_CONDITION_HEADER, TYPE_SUGGESTION_CONDITION_FOOTER,TYPE_DASHBOARD_SPACER})
public Item(Object entity, @ItemTypes int type, int id) {this.entity = entity;this.type = type;this.id = id;}

这里的构造方法显示三个参数,其中根据不同的TYPE,存储不同的layout的ID,可以知道有五种类型的layout,分别是Tile,Container,Header,Footer,Space,我们在设置里能看到的就这几种条目类型。

我们最基础的类型就是TYPE_DASHBOARD_TILE了,

<LinearLayout>//省略细节<ImageViewandroid:id="@android:id/icon"<LinearLayout<TextView android:id="@android:id/title"<TextView android:id="@android:id/summary"</LinearLayout>
</LinearLayout>

可以看到在普通的设置选项条目里,我们能看到的就是一个图标,一个标题,和一个摘要。

就像下面这样

上面Item的构造方法这里传入的其中一个参数是Object类型,但是我们需要知道到底这个Object的实例是什么。

看看前面的addToItemList,分别传入的是

“SuggestionConditionHeaderData”,

“List suggestions”,

“List conditions”,

DashboardCategory mCategory的“Tile”。

suggestions,conditions是在DashboardAdapter创建的时候传递的,我们看看这里的数据来源。

conditions

当时DashboardAdapter的第二个构造参数是

mConditionManager.getConditions()
mConditionManager = ConditionManager.get(activity, false);

单例模式

    public static ConditionManager get(Context context, boolean loadConditionsNow) {if (sInstance == null) {sInstance = new ConditionManager(context.getApplicationContext(), loadConditionsNow);}return sInstance;}
    private ConditionManager(Context context, boolean loadConditionsNow) {mContext = context;mConditions = new ArrayList<>();if (loadConditionsNow) {Log.d(TAG, "conditions loading synchronously");ConditionLoader loader = new ConditionLoader();loader.onPostExecute(loader.doInBackground());} else {Log.d(TAG, "conditions loading asychronously");new ConditionLoader().execute();}}

new ConditionLoader().execute();

        protected ArrayList<Condition> doInBackground(Void... params) {Log.d(TAG, "loading conditions from xml");ArrayList<Condition> conditions = new ArrayList<>();mXmlFile = new File(mContext.getFilesDir(), FILE_NAME);if (mXmlFile.exists()) {readFromXml(mXmlFile, conditions);}addMissingConditions(conditions);return conditions;}@Overrideprotected void onPostExecute(ArrayList<Condition> conditions) {Log.d(TAG, "conditions loaded from xml, refreshing conditions");mConditions.clear();mConditions.addAll(conditions);refreshAll();}

所以,mConditions来自这里,这里显示的却是readFromXml。从xml里去读吗?

private static final String FILE_NAME = "condition_state.xml";

然后很尴尬的发现系统里找不到这个文件,这样只能去看addMissingConditions了。

    private void addMissingConditions(ArrayList<Condition> conditions) {addIfMissing(AirplaneModeCondition.class, conditions);addIfMissing(HotspotCondition.class, conditions);addIfMissing(DndCondition.class, conditions);addIfMissing(BatterySaverCondition.class, conditions);addIfMissing(CellularDataCondition.class, conditions);addIfMissing(BackgroundDataCondition.class, conditions);addIfMissing(WorkModeCondition.class, conditions);addIfMissing(NightDisplayCondition.class, conditions);Collections.sort(conditions, CONDITION_COMPARATOR);}
    private void addIfMissing(Class<? extends Condition> clz, ArrayList<Condition> conditions) {if (getCondition(clz, conditions) == null) {if (DEBUG) Log.d(TAG, "Adding missing " + clz.getName());Condition condition = createCondition(clz);if (condition != null) {conditions.add(condition);}}}

好吧,原来是这些condition,那这些condition是什么呢?

举个例子,如果我们打开飞行模式,就会看到这个条目了。

condition已经搞明白了,剩下的还有suggestions和category了。


suggestions

suggestions是一个Tile的列表

在DashboardSummary的onViewCreated里,最后调用的rebuildUI

rebuildUI

   @VisibleForTestingvoid rebuildUI() {if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {Log.d(TAG, "Suggestion feature is disabled, skipping suggestion entirely");updateCategoryAndSuggestion(null /* tiles */);} else {new SuggestionLoader().execute();// Set categories on their own if loading suggestions takes too long.mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);}, MAX_WAIT_MILLIS);}}

这里做了一个判断,isSuggestionEnabled就是建议是否开启,然后决定是否去加载Suggestion。

我们需要去看看isSuggestionEnabled是什么

mSuggestionFeatureProvider.isSuggestionEnabled(getContext())
----------------
private SuggestionFeatureProvider mSuggestionFeatureProvider;
----------------
public interface SuggestionFeatureProvider
----------------
public class SuggestionFeatureProviderImpl implements SuggestionFeatureProvider 

SuggestionFeatureProviderImpl是接口SuggestionFeatureProvider的实例,

    @Overridepublic boolean isSuggestionEnabled(Context context) {final ActivityManager am =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);return !am.isLowRamDevice();}

我们可以看到,这里决定是否isSuggestionEnabled的结果是判断当前设备是否是低Ram设备。

所以,如果我们是Ram低于512M的设备就返回false,如果是大于512M设备就返回true。

当前我们开发的设备是1G Ram的,所以返回True

new SuggestionLoader().execute();
// Set categories on their own if loading suggestions takes too long.
mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);
}, MAX_WAIT_MILLIS);

SuggestionLoader加载suggestions

@Overrideprotected List<Tile> doInBackground(Void... params) {final Context context = getContext();boolean isSmartSuggestionEnabled =mSuggestionFeatureProvider.isSmartSuggestionEnabled(context);final SuggestionList sl = mSuggestionParser.getSuggestions(isSmartSuggestionEnabled);final List<Tile> suggestions = sl.getSuggestions();if (isSmartSuggestionEnabled) {List<String> suggestionIds = new ArrayList<>(suggestions.size());for (Tile suggestion : suggestions) {suggestionIds.add(mSuggestionFeatureProvider.getSuggestionIdentifier(context, suggestion));}// TODO: create a Suggestion class to maintain the id and other infomSuggestionFeatureProvider.rankSuggestions(suggestions, suggestionIds);}for (int i = 0; i < suggestions.size(); i++) {Tile suggestion = suggestions.get(i);if (mSuggestionsChecks.isSuggestionComplete(suggestion)) {suggestions.remove(i--);}}if (sl.isExclusiveSuggestionCategory()) {mSuggestionFeatureProvider.filterExclusiveSuggestions(suggestions);}return suggestions;}

其中

mSuggestionParser.getSuggestions(isSmartSuggestionEnabled);
public SuggestionList getSuggestions(boolean isSmartSuggestionEnabled) {final SuggestionList suggestionList = new SuggestionList();final int N = mSuggestionList.size();for (int i = 0; i < N; i++) {final SuggestionCategory category = mSuggestionList.get(i);if (category.exclusive && !isExclusiveCategoryExpired(category)) {final List<Tile> exclusiveSuggestions = new ArrayList<>();readSuggestions(category, exclusiveSuggestions, false );if (!exclusiveSuggestions.isEmpty()) {final SuggestionList exclusiveList = new SuggestionList();exclusiveList.addSuggestions(category, exclusiveSuggestions);return exclusiveList;}} else {final List<Tile> suggestions = new ArrayList<>();readSuggestions(category, suggestions, isSmartSuggestionEnabled);suggestionList.addSuggestions(category, suggestions);}}return suggestionList;}
void readSuggestions(SuggestionCategory category, List<Tile> suggestions, boolean isSmartSuggestionEnabled) {int countBefore = suggestions.size();Intent intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(category.category);if (category.pkg != null) {intent.setPackage(category.pkg);}TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,mAddCache, null, suggestions, true, false, false, true );filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);if (!category.multiple && suggestions.size() > (countBefore + 1)) {Tile item = suggestions.remove(suggestions.size() - 1);while (suggestions.size() > countBefore) {Tile last = suggestions.remove(suggestions.size() - 1);if (last.priority > item.priority) {item = last;}}if (!isCategoryDone(category.category)) {suggestions.add(item);}}}

其中就是从SuggestionCategory来加载Tile到suggestions,但是我们需要知道category是从哪里来的,

final SuggestionCategory category = mSuggestionList.get(i);

mSuggestionList = suggestionList;

在SuggestionParser的构造时候,传入了suggestionList,

(List<SuggestionCategory>) new SuggestionOrderInflater(context).parse(orderXml)

然后

public Object parse(int resource) {XmlPullParser parser = mContext.getResources().getXml(resource);final AttributeSet attrs = Xml.asAttributeSet(parser);try {int type;do {type = parser.next();} while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT);if (type != XmlPullParser.START_TAG) {throw new InflateException(parser.getPositionDescription()+ ": No start tag found!");}Object xmlRoot = onCreateItem(parser.getName(), attrs);rParse(parser, xmlRoot, attrs);return xmlRoot;} catch (XmlPullParserException | IOException e) {Log.w(TAG, "Problem parser resource " + resource, e);return null;}}

也是从xml里读取

继续找,发现前面传递的是 R.xml.suggestion_ordering

mSuggestionParser = new SuggestionParser(activity,mSuggestionFeatureProvider.getSharedPrefs(activity), R.xml.suggestion_ordering);
<optional-steps><step category="com.android.settings.suggested.category.DEFERRED_SETUP"exclusive="true" /><step category="com.android.settings.suggested.category.LOCK_SCREEN" /><step category="com.android.settings.suggested.category.TRUST_AGENT" /><step category="com.android.settings.suggested.category.EMAIL" /><step category="com.android.settings.suggested.category.PARTNER_ACCOUNT"multiple="true" /><step category="com.android.settings.suggested.category.GESTURE" /><step category="com.android.settings.suggested.category.HOTWORD" /><step category="com.android.settings.suggested.category.DEFAULT"multiple="true" /><step category="com.android.settings.suggested.category.SETTINGS_ONLY"multiple="true" />
</optional-steps>

parse解析的过程里

rParse(parser, xmlRoot, attrs);

private void rParse(XmlPullParser parser, Object parent, final AttributeSet attrs)throws XmlPullParserException, IOException {final int depth = parser.getDepth();int type;while (((type = parser.next()) != XmlPullParser.END_TAG ||parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {if (type != XmlPullParser.START_TAG) {continue;}final String name = parser.getName();Object item = onCreateItem(name, attrs);onAddChildItem(parent, item);rParse(parser, item, attrs);}}
protected Object onCreateItem(String name, AttributeSet attrs) {if (name.equals(TAG_LIST)) {return new ArrayList<SuggestionCategory>();} else if (name.equals(TAG_ITEM)) {SuggestionCategory category = new SuggestionCategory();category.category = attrs.getAttributeValue(null, ATTR_CATEGORY);category.pkg = attrs.getAttributeValue(null, ATTR_PACKAGE);String multiple = attrs.getAttributeValue(null, ATTR_MULTIPLE);category.multiple = !TextUtils.isEmpty(multiple) && Boolean.parseBoolean(multiple);String exclusive = attrs.getAttributeValue(null, ATTR_EXCLUSIVE);category.exclusive =!TextUtils.isEmpty(exclusive) && Boolean.parseBoolean(exclusive);String expireDaysAttr = attrs.getAttributeValue(null,ATTR_EXCLUSIVE_EXPIRE_DAYS);long expireDays = !TextUtils.isEmpty(expireDaysAttr)? Integer.parseInt(expireDaysAttr): -1;category.exclusiveExpireDaysInMillis = DateUtils.DAY_IN_MILLIS *expireDays;return category;} else {throw new IllegalArgumentException("Unknown item " + name);}}

当扫描到step的时候去创建SuggestionCategory,

所以这里的SuggestionCategory列表最后Item内容就是上面XML里的item内容。

我们仔细看这个category,发现很像我们在AndroidManifest.xml里定义的category。

那么前面mSuggestionList就是parse解析的结果new ArrayList();

在前面readSuggestions的时候传入的就是携带这xml里step的category。

我们继续回到readSuggestions

        Intent intent = new Intent(Intent.ACTION_MAIN);intent.addCategory(category.category);if (category.pkg != null) {intent.setPackage(category.pkg);}TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);

这里把category的category给了Intent然后调用了addCategory

然后走到TileUtils.getTilesForIntent,去把对应的Tile加到maddCache里。

private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();

找到完了suggestion,最后找category


category

在DashboardAdapter构造的时候category = null;一直到setCategory都是null,那么category到底是什么情况呢?

最后我们需要看看**DashboardAdapter的setCategory(category)**这里的category可是null无疑,

但是前面 SuggestionLoader().execute() 获取 suggestion完了,后面还有个post更新

mHandler.postDelayed(() -> {updateCategoryAndSuggestion(null /* tiles */);
}, MAX_WAIT_MILLIS);

updateCategoryAndSuggestion

看到上面传入的tiles 是NULL。

    @VisibleForTestingvoid updateCategoryAndSuggestion(List<Tile> suggestions) {final Activity activity = getActivity();if (activity == null) {return;}final DashboardCategory category = 				mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGO RY_HOMEPAGE);mSummaryLoader.updateSummaryToCache(category);if (suggestions != null) {mAdapter.setCategoriesAndSuggestions(category, suggestions);} else {mAdapter.setCategory(category);}}

DashboardCategory

mDashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGO RY_HOMEPAGE);

找到实例

@Override
public DashboardCategory getTilesForCategory(String key) {return mCategoryManager.getTilesByCategory(mContext, key);
}
---------------------------------------------
public synchronized DashboardCategory getTilesByCategory(Context context, String 		categoryKey) {return getTilesByCategory(context, categoryKey, TileUtils.SETTING_PKG);
}
---------------------------------------------
public synchronized DashboardCategory getTilesByCategory(Context context, String categoryKey,String settingPkg) {tryInitCategories(context, settingPkg); //return mCategoryByKeyMap.get(categoryKey);
}

在获取category的时候,有一个初始化的过程

private synchronized void tryInitCategories(Context context, String settingPkg) {tryInitCategories(context, false /* forceClearCache */, settingPkg);
}
---------------------------------------------
private synchronized void tryInitCategories(Context context, booleanforceClearCache,String settingPkg) {if (mCategories == null) {if (forceClearCache) {mTileByComponentCache.clear();}mCategoryByKeyMap.clear();mCategories = TileUtils.getCategories(context, mTileByComponentCache,false /* categoryDefinedInManifest */, mExtraAction, settingPkg);for (DashboardCategory category : mCategories) {mCategoryByKeyMap.put(category.key, category);}backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);sortCategories(context, mCategoryByKeyMap);filterDuplicateTiles(mCategoryByKeyMap);}
}

其关键在于mCategories当我们第一次去获取category的时候mCategories == null。

于是

mCategories = TileUtils.getCategories(context, mTileByComponentCache,false, mExtraAction, settingPkg);
TileUtils.getCategories
public static List<DashboardCategory> getCategories(Context context,Map<Pair<String, String>, Tile> cache, boolean categoryDefinedInManifest,String extraAction, String settingPkg) {final long startTime = System.currentTimeMillis();boolean setup = Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0)!= 0;ArrayList<Tile> tiles = new ArrayList<>();UserManager userManager = (UserManager)context.getSystemService(Context.USER_SERVICE);for (UserHandle user : userManager.getUserProfiles()) {// TODO: Needs much optimization, too many PM queries going on here.if (user.getIdentifier() == ActivityManager.getCurrentUser()) {// Only add Settings for this user.getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,true, settingPkg);getTilesForAction(context, user, OPERATOR_SETTINGS, cache,OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);}...//省略}HashMap<String, DashboardCategory> categoryMap = new HashMap<>();for (Tile tile : tiles) {DashboardCategory category = categoryMap.get(tile.category);if (category == null) {category = createCategory(context, tile.category, categoryDefinedInManifest);if (category == null) {Log.w(LOG_TAG, "Couldn't find category " + tile.category);continue;}categoryMap.put(category.key, category);}category.addTile(tile);}ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());for (DashboardCategory category : categories) {Collections.sort(category.tiles, TILE_COMPARATOR);}Collections.sort(categories, CATEGORY_COMPARATOR);return categories;}

我们发现,在初始化categories的时候,做了很多事情,

其中第一步是查询并填充ArrayList tiles,

通过

getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles,true, settingPkg);
getTilesForAction(context, user, OPERATOR_SETTINGS, cache,OPERATOR_DEFAULT_CATEGORY, tiles, false, true, settingPkg);
getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,MANUFACTURER_DEFAULT_CATEGORY, tiles, false, true, settingPkg);

这三步去填充tiles,

然后再用for循环,创建category

for (Tile tile : tiles)。。。省略category = createCategory(context, tile.category, categoryDefinedInManifest);。。。

这里的category是DashboardCategory,

category.addTile(tile);
public void addTile(Tile tile) {tiles.add(tile);
}
/*** List of the category's children*/
public List<Tile> tiles = new ArrayList<>();

所以,category.addTile的时候,是把Tile给加到了一个内部List里,这就说明,一个Category就是一些Tile的集合。

getTilesForAction

private static void getTilesForAction(Context context,UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,String settingPkg) {getTilesForAction(context, user, action, addedCache, defaultCategory, outTiles,requireSettings, requireSettings, settingPkg);
}
    private static void getTilesForAction(Context context,UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,String defaultCategory, ArrayList<Tile> outTiles, boolean requireSettings,boolean usePriority, String settingPkg) {Intent intent = new Intent(action);if (requireSettings) {intent.setPackage(settingPkg);}getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,usePriority, true, true);}

然后走到了和前面获取suggestion一样的地方,都是getTilesForIntent,只不过这里的category和suggestion那里的xml的category不一样。

这里三个category分别是

    private static final String SETTINGS_ACTION ="com.android.settings.action.SETTINGS";private static final String OPERATOR_SETTINGS ="com.android.settings.OPERATOR_APPLICATION_SETTING";private static final String MANUFACTURER_SETTINGS ="com.android.settings.MANUFACTURER_APPLICATION_SETTING";

最后一起来看看是怎么获取Tile的。

TileUtils

public static void getTilesForIntent(Context context, UserHandle user, Intent intent,Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,boolean shouldUpdateTiles) {PackageManager pm = context.getPackageManager();List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,PackageManager.GET_META_DATA, user.getIdentifier());Map<String, IContentProvider> providerMap = new HashMap<>();for (ResolveInfo resolved : results) {if (!resolved.system && !ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,resolved.activityInfo.packageName)) {continue;}ActivityInfo activityInfo = resolved.activityInfo;Bundle metaData = activityInfo.metaData;String categoryKey = defaultCategory;// Load categoryif (checkCategory && ((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))&& categoryKey == null) {Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for intent "+ intent + " missing metadata "+ (metaData == null ? "" : EXTRA_CATEGORY_KEY));continue;} else {categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);/// M: set default category when extra data not set categoryif (categoryKey == null) {categoryKey = defaultCategory;}}Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,activityInfo.name);Tile tile = addedCache.get(key);if (tile == null) {tile = new Tile();tile.intent = new Intent().setClassName(activityInfo.packageName, activityInfo.name);tile.category = categoryKey;/// M: white list could use prioritytile.priority = usePriority || ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,activityInfo.packageName) ? resolved.priority : 0;tile.metaData = activityInfo.metaData;/// M: Support priority in whitelist appif (tile.metaData.containsKey(META_DATA_KEY_PRIORITY) && ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST, activityInfo.packageName)) {tile.priority = tile.metaData.getInt(META_DATA_KEY_PRIORITY);}updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,pm, providerMap, forceTintExternalIcon);if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);addedCache.put(key, tile);} else if (shouldUpdateTiles) {updateSummaryAndTitle(context, providerMap, tile);}if (!tile.userHandle.contains(user)) {tile.userHandle.add(user);}if (!outTiles.contains(tile)) {outTiles.add(tile);}}}
List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,PackageManager.GET_META_DATA, user.getIdentifier());

这个Results是用PackageManager来query的结果,最后查询的结果是当前包里有相同的category的activity信息

ActivityInfo activityInfo = resolved.activityInfo;
Bundle metaData = activityInfo.metaData;
String categoryKey = defaultCategory;

然后我们看到了下面是创建Tile的过程

Tile tile = addedCache.get(key);
if (tile == null) {tile = new Tile();tile.intent = new Intent().setClassName(activityInfo.packageName, activityInfo.name);tile.category = categoryKey;/// M: white list could use prioritytile.priority = usePriority || ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST,activityInfo.packageName) ? resolved.priority : 0;tile.metaData = activityInfo.metaData;/// M: Support priority in whitelist appif (tile.metaData.containsKey(META_DATA_KEY_PRIORITY) &&ArrayUtils.contains(EXTRA_PACKAGE_WHITE_LIST, activityInfo.packageName)) {tile.priority = tile.metaData.getInt(META_DATA_KEY_PRIORITY);}updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,pm, providerMap, forceTintExternalIcon);if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);addedCache.put(key, tile);
} else if (shouldUpdateTiles) {updateSummaryAndTitle(context, providerMap, tile);
}

分别把activityInfo.packageName, activityInfo.name,categoryKey等这些信息赋值给Tile,然后把tile加入到addedCache,

private final ArrayMap<Pair<String, String>, Tile> mAddCache = new ArrayMap<>();

就这样通过不同的category来得到不同activity在xml里定义的信息。

三个类型,分别对应着

setConditions(conditions)
setSuggestions(suggestions)
setCategory(category)

这就是DashboardAdapter在初始化的时候,获取的数据的过程。

这里有一点需要说明的是,我在使用suggestion_ordering.xml里的category去AndroidMannifest.xml找,没有找到一个对应的activity,而使用SETTINGS_ACTION却能找到许多acticity。

比如

	<activity android:name=".Settings$NetworkDashboardActivity"android:taskAffinity="com.android.settings"android:label="@string/network_dashboard_title"android:icon="@drawable/ic_settings_wireless"android:parentActivityName="Settings"><intent-filter android:priority="1"><action android:name="android.settings.WIRELESS_SETTINGS" /><action android:name="android.settings.AIRPLANE_MODE_SETTINGS" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.VOICE_LAUNCH" /></intent-filter><intent-filter android:priority="11"><action android:name="com.android.settings.action.SETTINGS"/></intent-filter><meta-data android:name="com.android.settings.category"android:value="com.android.settings.category.ia.homepage"/><meta-data android:name="com.android.settings.FRAGMENT_CLASS"android:value="com.android.settings.network.NetworkDashboardFragment"/><meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"android:value="true" /></activity>

这里的NetworkDashboardActivity是在Setting里面定义了的。

这里可以看到,它其实是action。

而我们就是根据这个action来分门别类的找到对应的目录。

加载view

数据加载告一段落,回到DashboardAdapter的加载view的流程。

DashboardAdapter

构造ViewHolder,根据viewType不同来加载不同的viewType并创建Viewholder

这里的viewtype就是layoutID

@Override
public DashboardItemHolder onCreateViewHolder(ViewGroup parent, int viewType) {final View view = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false);if (viewType == R.layout.suggestion_condition_header) {return new SuggestionAndConditionHeaderHolder(view);}if (viewType == R.layout.suggestion_condition_container) {return new SuggestionAndConditionContainerHolder(view);}return new DashboardItemHolder(view);
}

默认ViewHolder就是DashboardItemHolder

public DashboardItemHolder(View itemView) {super(itemView);icon = itemView.findViewById(android.R.id.icon);title = itemView.findViewById(android.R.id.title);summary = itemView.findViewById(android.R.id.summary);
}

各个item的type就是前面说的那个layout的ID

@Override
public int getItemViewType(int position) {return mDashboardData.getItemTypeByPosition(position);
}

public int getItemTypeByPosition(int position) {return mItems.get(position).type;
}

然后我们看onBindViewHolder

@Override
public void onBindViewHolder(DashboardItemHolder holder, int position) {final int type = mDashboardData.getItemTypeByPosition(position);switch (type) {case R.layout.dashboard_tile:final Tile tile = (Tile)mDashboardData.getItemEntityByPosition(position);onBindTile(holder, tile);holder.itemView.setTag(tile);holder.itemView.setOnClickListener(mTileClickListener);break;case R.layout.suggestion_condition_container:onBindConditionAndSuggestion((SuggestionAndConditionContainerHolder) holder, position);break;case R.layout.suggestion_condition_header:onBindSuggestionConditionHeader((SuggestionAndConditionHeaderHolder)holder,(SuggestionConditionHeaderData)mDashboardData.getItemEntityByPosition(position));break;case R.layout.suggestion_condition_footer:holder.itemView.setOnClickListener(v -> {mMetricsFeatureProvider.action(mContext,MetricsEvent.ACTION_SETTINGS_CONDITION_EXPAND, false);DashboardData prevData = mDashboardData;mDashboardData = new DashboardData.Builder(prevData).setSuggestionConditionMode(DashboardData.HEADER_MODE_COLLAPSED).build();notifyDashboardDataChanged(prevData);mRecyclerView.scrollToPosition(SUGGESTION_CONDITION_HEADER_POSITION);});break;}
}

在Bindview的时候分别根据Tile,Container,header和footer来创建view。

看看onBindTile

private void onBindTile(DashboardItemHolder holder, Tile tile) {if (tile.remoteViews != null) {final ViewGroup itemView = (ViewGroup) holder.itemView;itemView.removeAllViews();itemView.addView(tile.remoteViews.apply(itemView.getContext(), itemView));} else {holder.icon.setImageDrawable(mCache.getIcon(tile.icon));holder.title.setText(tile.title);if (!TextUtils.isEmpty(tile.summary)) {holder.summary.setText(tile.summary);holder.summary.setVisibility(View.VISIBLE);} else {holder.summary.setVisibility(View.GONE);}}
}

就是这样,根据不同的layout,来加载不同的item,实现RecyclerView的填充。

点击事件

显示问题搞清楚了,接着我们看点击事件

holder.itemView.setTag(tile);
holder.itemView.setOnClickListener(mTileClickListener);

在bindview的时候设置了OnClickListener

private View.OnClickListener mTileClickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {mDashboardFeatureProvider.openTileIntent((Activity) mContext, (Tile)v.getTag());}
};

DashboardFeatureProviderImpl实现了DashboardFeatureProvider接口,这里是面向接口编程的思想。

@Override
public void openTileIntent(Activity activity, Tile tile) {if (tile == null) {Intent intent = new Intent(Settings.ACTION_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);mContext.startActivity(intent);return;}if (tile.intent == null) {return;}final Intent intent = new Intent(tile.intent).putExtra(SettingsActivity.EXTRA_SOURCE_METRICS_CATEGORY,MetricsEvent.DASHBOARD_SUMMARY).putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);launchIntentOrSelectProfile(activity, tile, intent,MetricsEvent.DASHBOARD_SUMMARY);
}

在前面创建每个Tile的时候,Intent是这样的,

tile.intent = new Intent().setClassName(
activityInfo.packageName, activityInfo.name);

里面包含了对应包名,activity的名字。

private void launchIntentOrSelectProfile(Activity activity, Tile tile, Intent intent,int sourceMetricCategory) {if (!isIntentResolvable(intent)) {Log.w(TAG, "Cannot resolve intent, skipping. " + intent);return;}ProfileSelectDialog.updateUserHandlesIfNeeded(mContext, tile);if (tile.userHandle == null) {mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);activity.startActivityForResult(intent, 0);} else if (tile.userHandle.size() == 1) {mMetricsFeatureProvider.logDashboardStartIntent(mContext, intent, sourceMetricCategory);activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));} else {ProfileSelectDialog.show(activity.getFragmentManager(), tile);}
}activity.startActivityForResultAsUser(intent, 0, tile.userHandle.get(0));

直接开启对应的activity,然后这里几乎所有activity都是继承自SettingsActivity,所有,就是启动它本身,换一个子类来显示。

然后在SettingsActivity的oncreate里,这时候的mIsShowingDashboard就是false,

就会显示R.layout.settings_main_prefs这个layout了,而且这时候的Intent是携带者具体要启动的activity信息的

final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);

然后就回到循环之中。

总结:Setting里所有的Activity都继承了SettingsActivity,通过action条目来统一管理加载,切换不同的activity实际也是调用它本身的不同子类,然后根据不同子类加载不同的fragment,所以说到底,Setting只有一个父Activity,所有的显示通过不同的Fragment来处理。实在是厉害。

文章转自

https://blog.csdn.net/csdnxialei/article/details/86555050

这篇关于Android 8.1 MTK平台 Settings源码解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095525

相关文章

使用Python实现批量访问URL并解析XML响应功能

《使用Python实现批量访问URL并解析XML响应功能》在现代Web开发和数据抓取中,批量访问URL并解析响应内容是一个常见的需求,本文将详细介绍如何使用Python实现批量访问URL并解析XML响... 目录引言1. 背景与需求2. 工具方法实现2.1 单URL访问与解析代码实现代码说明2.2 示例调用

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

SpringCloud配置动态更新原理解析

《SpringCloud配置动态更新原理解析》在微服务架构的浩瀚星海中,服务配置的动态更新如同魔法一般,能够让应用在不重启的情况下,实时响应配置的变更,SpringCloud作为微服务架构中的佼佼者,... 目录一、SpringBoot、Cloud配置的读取二、SpringCloud配置动态刷新三、更新@R

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript