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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

流媒体平台/视频监控/安防视频汇聚EasyCVR播放暂停后视频画面黑屏是什么原因?

视频智能分析/视频监控/安防监控综合管理系统EasyCVR视频汇聚融合平台,是TSINGSEE青犀视频垂直深耕音视频流媒体技术、AI智能技术领域的杰出成果。该平台以其强大的视频处理、汇聚与融合能力,在构建全栈视频监控系统中展现出了独特的优势。视频监控管理系统EasyCVR平台内置了强大的视频解码、转码、压缩等技术,能够处理多种视频流格式,并以多种格式(RTMP、RTSP、HTTP-FLV、WebS

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get