学习笔记:参考资源:https://blog.csdn.net/Otaku_627/article/details/108618647
了解更多:https://blog.csdn.net/Otaku_627/article/details/108843487
代码路径:packages/app/Settings/
<!-- Alias for launcher activity only, as this belongs to each profile. -->
<activity-alias android:name="Settings"
android:label="@string/settings_label_launcher"
android:launchMode="singleTask"
android:targetActivity=".homepage.SettingsHomepageActivity">
<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>
Settings的主界面是Settings.java,但是从Settings.java来看,除了大量的静态类继承SettingsActivity,就无其他有效信息了。但看其xml定义可以发现targetActivity属性,实质应是SettingsHomepageActivity.java。
先看其xml配置:
<activity android:name=".homepage.SettingsHomepageActivity"
android:label="@string/settings_label_launcher"
android:theme="@style/Theme.Settings.Home"
android:launchMode="singleTask">
<intent-filter android:priority="1">
<action android:name="android.settings.SETTINGS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
SettingsHomepageActivity.java,主要从onCreate()方法开始:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_homepage_container);
final View root = findViewById(R.id.settings_homepage_container);
root.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
setHomepageContainerPaddingTop();
final Toolbar toolbar = findViewById(R.id.search_action_bar);
FeatureFactory.getFactory(this).getSearchFeatureProvider()
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
final ImageView avatarView = findViewById(R.id.account_avatar);
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(this, avatarView);
getLifecycle().addObserver(avatarViewMixin);
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
// Only allow contextual feature on high ram devices.
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
}
showFragment(new TopLevelSettings(), R.id.main_content);
((FrameLayout) findViewById(R.id.main_content))
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
}
可以看到主界面的layout为settings_homepage_container.xml:
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/settings_homepage_container"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.core.widget.NestedScrollView
android:id="@+id/main_content_scrollable_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.android.settings.widget.FloatingAppBarScrollingViewBehavior">
<LinearLayout
android:id="@+id/homepage_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:descendantFocusability="blocksDescendants">
<FrameLayout
android:id="@+id/contextual_cards_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/contextual_card_side_margin"
android:layout_marginEnd="@dimen/contextual_card_side_margin"/>
<FrameLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="?android:attr/windowBackground"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/search_bar"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
主界面布局中主要包含三部分:两个FrameLayout,一个顶部快捷搜索栏。其中Id为main_content的FrameLayout就是用来显示主设置内容的,即Settings的一级菜单项界面。.homepage.SettingsHomepageActivity 中的逻辑并不复杂,直接加载了TopLevelSettings这个Fragment。
showFragment(new TopLevelSettings(), R.id.main_content);
TopLevelSettings通过AndroidX的Preference来展示设置项列表,设置项列表的内容通过静态配置+动态添加的方式获取。
后面分开分析:SettingsActivity.java、DashboardFragment.java。
Settings 继承了 SettingsActivity,有着大量的静态类,但其中并没有实现任何逻辑,那它是怎么加载到自己应有的布局的呢?
其实这些Activity的逻辑都是在SettingsActivity中实现。
在父类SettingsActivity的onCreate()中:
@Override
protected void onCreate(Bundle savedState) {
super.onCreate(savedState);
long startTime = System.currentTimeMillis();
//工厂类实现方法com.android.settings.overlay.FeatureFactoryImpl.java
final FeatureFactory factory = FeatureFactory.getFactory(this);
//获取菜单信息的工厂类,实现类为DashboardFeatureProviderImpl.java
mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
mMetricsFeatureProvider = factory.getMetricsFeatureProvider();
// 第一步 从intent信息中获取<meta-data/>标签名为"com.android.settings.FRAGMENT_CLASS"的值(下文用于加载Fragment的类名)
getMetaData();
// 第二步
final Intent intent = getIntent();
if (intent.hasExtra(EXTRA_UI_OPTIONS)) {
getWindow().setUiOptions(intent.getIntExtra(EXTRA_UI_OPTIONS, 0));
}
//获取上面getMetaData()得到的类名
final String initialFragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
//是否为快捷进入方式(如从其它的应用进入Settings的某个设置项)
mIsShortcut = isShortCutIntent(intent) || isLikeShortCutIntent(intent) ||
intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SHORTCUT, false);
... ...
if (savedState != null) {
... ...
} else {
// 第三步 加载布局
launchSettingFragment(initialFragmentName, isSubSettings, intent);
}
... ...
}
第一步:
首先通过getMetaData()获取该Activity在manifest中配置的fragment, 并赋值给mFragmentClass。
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());
}
}
第二步:
通过getIntent()方法、getStartingFragmentClass()方法筛选出要启动的Fragment。
第三步:
通过launchSettingFragment()启动对应Fragment,这里的initialFragmentName参数就是第二步Intent中包含的EXTRA_SHOW_FRAGMENT参数,mFragmentClass不为空的情况下传入的就是mFragmentClass。
通过上面知道,SettingsHomepageActivity 直接加载了TopLevelSettings这个Fragment。而该Fragment继承了DashboardFragment,先来看TopLevelSettings的构造方法:
public TopLevelSettings() {
final Bundle args = new Bundle();
// Disable the search icon because this page uses a full search view in actionbar.
args.putBoolean(NEED_SEARCH_ICON_IN_ACTION_BAR, false);
setArguments(args);
}
可以看到构造方法中仅设置了个标志位,再根据framgments生命周期先来看onAttach()方法:
@Override
public void onAttach(Context context) {
super.onAttach(context);
use(SupportPreferenceController.class).setActivity(getActivity());
}
调用父类DashboardFragment.java的onAttach()方法,此方法主要是完成mPreferenceControllers的加载。
接着看onCreate()方法,因为TopLevelSettings未重写父类的方法,所以直接看父类DashboardFragment的onCreate()方法。
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
// Set ComparisonCallback so we get better animation when list changes.
getPreferenceManager().setPreferenceComparisonCallback(
new PreferenceManager.SimplePreferenceComparisonCallback());
if (icicle != null) {
// Upon rotation configuration change we need to update preference states before any
// editing dialog is recreated (that would happen before onResume is called).
updatePreferenceStates();
}
}
根据log定位发现,其后调用DashboardFragment.java的onCreatePreferences()方法:这里我也不知道怎么调用到这来的,哈哈。
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
refreshAllPreferences(getLogTag());
}
/**
* Refresh all preference items, including both static prefs from xml, and dynamic items from
* DashboardCategory.
*/
private void refreshAllPreferences(final String TAG) {
final PreferenceScreen screen = getPreferenceScreen();
// First remove old preferences.
if (screen != null) {
// Intentionally do not cache PreferenceScreen because it will be recreated later.
screen.removeAll();
}
// Add resource based tiles.
displayResourceTiles();
refreshDashboardTiles(TAG);
final Activity activity = getActivity();
if (activity != null) {
Log.d(TAG, "All preferences added, reporting fully drawn");
activity.reportFullyDrawn();
}
updatePreferenceVisibility(mPreferenceControllers);
}
以看到此方法主要是用来加载显示的preference items,主要分为两部分,一个是静态xml定义的prefs(调用displayResourceTiles()方法),另一部分是从DashboardCategory动态加载(调用refreshDashboardTiles(TAG)方法,其中TAG为 “TopLevelSettings”)。
此方法主要是从xml资源文件中加载显示prefs:
/**
* Displays resource based tiles.
*/
private void displayResourceTiles() {
final int resId = getPreferenceScreenResId();
if (resId <= 0) {
return;
}
addPreferencesFromResource(resId);
final PreferenceScreen screen = getPreferenceScreen();
screen.setOnExpandButtonClickListener(this);
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
}
首先调用getPreferenceScreenResId()方法获取所要加载的xml的ID:
@Override
protected abstract int getPreferenceScreenResId();
最终回调用到子类TopLevelSettings.java的getPreferenceScreenResId()方法:
@Override
protected int getPreferenceScreenResId() {
return R.xml.top_level_settings;
}
此主要是调用androidX Preference的addPreferencesFromResource()方法。此方法主要是将preferenceScreen下所有Preference添加到ArrayList中,然后再根据此集合构建生成PreferenceGroupAdapter,最后将此adapter设置到listview中,完成数据绑定,从而完成界面加载。在这里就要明白mPreferenceControllers是什么,在哪初始化的?
我们很快就可以找到:在onAttach()中添加的。
final List<AbstractPreferenceController> controllers = new ArrayList<>();
// Load preference controllers from code
final List<AbstractPreferenceController> controllersFromCode =
createPreferenceControllers(context);
// Load preference controllers from xml definition
final List<BasePreferenceController> controllersFromXml = PreferenceControllerListHelper
.getPreferenceControllersFromXml(context, getPreferenceScreenResId());
// Filter xml-based controllers in case a similar controller is created from code already.
final List<BasePreferenceController> uniqueControllerFromXml =
PreferenceControllerListHelper.filterControllers(
controllersFromXml, controllersFromCode);
// Add unique controllers to list.
if (controllersFromCode != null) {
controllers.addAll(controllersFromCode);
}
controllers.addAll(uniqueControllerFromXml);
// And wire up with lifecycle.
final Lifecycle lifecycle = getSettingsLifecycle();
uniqueControllerFromXml
.stream()
.filter(controller -> controller instanceof LifecycleObserver)
.forEach(
controller -> lifecycle.addObserver((LifecycleObserver) controller));
mPlaceholderPreferenceController =
new DashboardTilePlaceholderPreferenceController(context);
controllers.add(mPlaceholderPreferenceController);
for (AbstractPreferenceController controller : controllers) {
addPreferenceController(controller);
}
可以发现:
1、从代码中加载preference controllers,调用createPreferenceControllers()方法;
2、从xml定义中加载preference controllers,调用getPreferenceControllersFromXml()方法。
3、过滤重复定义的controller等,赋值填充mPreferenceControllers。
再回到displayResourceTiles()方法中的:
mPreferenceControllers.values().stream().flatMap(Collection::stream).forEach(
controller -> controller.displayPreference(screen));
此语句主要就是调用各个controller的displayPreference()方法。
以网络和互联网菜单项为例,xml中配置的controller为"com.android.settings.network.TopLevelNetworkEntryPreferenceController",查看TopLevelNetworkEntryPreferenceController.java发现,其内并未实现displayPreference()方法,查看继承关系:是继承BasePreferenceController的,接着查看BasePreferenceController中的displayPreference()方法。
/**
* Displays preference in this controller.
*/
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
// Disable preference if it depends on another setting.
final Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setEnabled(false);
}
}
}
又是调用BasePreferenceController父类AbstractPreferenceController中的displayPreference:
/**
* Displays preference in this controller.
*/
public void displayPreference(PreferenceScreen screen) {
final String prefKey = getPreferenceKey();
if (TextUtils.isEmpty(prefKey)) {
Log.w(TAG, "Skipping displayPreference because key is empty:" + getClass().getName());
return;
}
if (isAvailable()) {
setVisible(screen, prefKey, true /* visible */);
if (this instanceof Preference.OnPreferenceChangeListener) {
final Preference preference = screen.findPreference(prefKey);
preference.setOnPreferenceChangeListener(
(Preference.OnPreferenceChangeListener) this);
}
} else {
setVisible(screen, prefKey, false /* visible */);
}
}
1、getPreferenceKey()获取preference的key,会调用到子类BasePreferenceController.java的getPreferenceKey()方法:
@Override
public String getPreferenceKey() {
return mPreferenceKey;
}
而据上面分析到mPreferenceKey实质上即为xml中每个preference配置的android:key属性的值,即此处应为"top_level_network"。(以网络和互联网菜单项为例)
2、isAvailable();判断此preference是否可用即是否应该被显示。如果返回true,则被显示出来,反之则不被显示,最终也会调用到BasePreferenceController.java的isAvailable()方法:
@Override
public final boolean isAvailable() {
final int availabilityStatus = getAvailabilityStatus();
return (availabilityStatus == AVAILABLE
|| availabilityStatus == AVAILABLE_UNSEARCHABLE
|| availabilityStatus == DISABLED_DEPENDENT_SETTING);
}
注意:看这里的BasePreferenceController.java中的isAvailable()方法中的getAvailabilityStatus(),一直跟进去,会发现调用的是:BasePreferenceController子类TopLevelNetworkEntryPreferenceController.java的getAvailabilityStatus()方法:
@Override
public int getAvailabilityStatus() {
return Utils.isDemoUser(mContext) ? UNSUPPORTED_ON_DEVICE : AVAILABLE_UNSEARCHABLE;
}
3、 调用setVisible()方法设置是否可被显示:setVisible(screen, prefKey, true /* visible */);
// frameworks/base/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
final Preference pref = group.findPreference(key);
if (pref != null) {
pref.setVisible(isVisible);
}
}
4、判断controller是否实现了Preference.OnPreferenceChangeListener接口,是,则设置监听。
2、继续看查看BasePreferenceController.java的displayPreference()方法的剩余语句:
if (getAvailabilityStatus() == DISABLED_DEPENDENT_SETTING) {
// Disable preference if it depends on another setting.
final Preference preference = screen.findPreference(getPreferenceKey());
if (preference != null) {
preference.setEnabled(false);
}
}
根据子类controller实现的getAvailabilityStatus()方法的返回值判断是否需要将此preference置为不可点击。
至此,DashboardFragment.java中displayResourceTiles()方法分析完成。
1、Settings的主Activity实质实现是在SettingsHomepageActivity.java内;
2、Settings的主界面设置item的显示是在fragment上,fragment为TopLevelSettings.java,加载显示的布局为top_level_settings.xml;
3、Settings主界面设置项item的加载显示主要分为两部分,一部分是xml定义的静态加载,xml为top_level_settings.xml;一部分是DashboardCategory来获取动态加载。
4、每个设置项item均为一个preference,通过xml定义加载时,必须要有一个controller,可以是在xml中定义"settings:controller"属性声明,名称必须与类的包名路径相同;也可直接在相关fragment中实现createPreferenceControllers()方法去调用构造相关controller。此二者存其一即可。
5、xml中配置preference时,必须定义”android:key“属性;
6、需要隐藏不显示某个设置项时,一是可以直接在xml中注释其定义;二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值不为AVAILABLE、AVAILABLE_UNSEARCHABLE、DISABLED_DEPENDENT_SETTING即可;
7、如果需要某个设置项不可点击,一是可以直接调用setEnabled()。二是可以在相关设置项preference的controller类中实现getAvailabilityStatus()方法,使此方法的返回值为DISABLED_DEPENDENT_SETTING即可。
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
我一直致力于让我们的Rails2.3.8应用程序在JRuby下正确运行。一切正常,直到我启用config.threadsafe!以实现JRuby提供的并发性。这导致lib/中的模块和类不再自动加载。使用config.threadsafe!启用:$rubyscript/runner-eproduction'pSim::Sim200Provisioner'/Users/amchale/.rvm/gems/jruby-1.5.1@web-services/gems/activesupport-2.3.8/lib/active_support/dependencies.rb:105:in`co
我们目前正在为ROR3.2开发自定义cms引擎。在这个过程中,我们希望成为我们的rails应用程序中的一等公民的几个类类型起源,这意味着它们应该驻留在应用程序的app文件夹下,它是插件。目前我们有以下类型:数据源数据类型查看我在app文件夹下创建了多个目录来保存这些:应用/数据源应用/数据类型应用/View更多类型将随之而来,我有点担心应用程序文件夹被这么多目录污染。因此,我想将它们移动到一个子目录/模块中,该子目录/模块包含cms定义的所有类型。所有类都应位于MyCms命名空间内,目录布局应如下所示:应用程序/my_cms/data_source应用程序/my_cms/data_ty
最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路
我刚刚安装了带有RVM的Ruby2.2.0,并尝试使用它得到了这个:$rvmuse2.2.0--defaultUsing/Users/brandon/.rvm/gems/ruby-2.2.0dyld:Librarynotloaded:/usr/local/lib/libgmp.10.dylibReferencedfrom:/Users/brandon/.rvm/rubies/ruby-2.2.0/bin/rubyReason:Incompatiblelibraryversion:rubyrequiresversion13.0.0orlater,butlibgmp.10.dylibpro
我正在运行Ubuntu11.10并像这样安装Ruby1.9:$sudoapt-getinstallruby1.9rubygems一切都运行良好,但ri似乎有空文档。ri告诉我文档是空的,我必须安装它们。我执行此操作是因为我读到它会有所帮助:$rdoc--all--ri现在,当我尝试打开任何文档时:$riArrayNothingknownaboutArray我搜索的其他所有内容都是一样的。 最佳答案 这个呢?apt-getinstallri1.8编辑或者试试这个:(非rvm)geminstallrdocrdoc-datardoc-da
我已经通过提供MagickWand.h的路径尝试了一切,我安装了命令工具。谁能帮帮我?$geminstallrmagick-v2.13.1Buildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingrmagick:ERROR:Failedtobuildgemnativeextension./Users/ghazanfarali/.rvm/rubies/ruby-1.8.7-p357/bin/rubyextconf.rbcheckingforRubyversion>=1.8.5...yescheckingfor/
如何只加载map边界内的标记gmaps4rails?当然,在平移和/或缩放后加载新的。与此直接相关的是,如何获取map的当前边界和缩放级别? 最佳答案 我是这样做的,我只在用户完成平移或缩放后替换标记,如果您需要不同的行为,请使用不同的事件监听器:在你看来(index.html.erb):{"zoom"=>15,"auto_adjust"=>false,"detect_location"=>true,"center_on_user"=>true}},false,true)%>在View的底部添加:functiongmaps4rail
我正在使用macos,我想使用ruby驱动程序连接到sqlserver。我想使用tiny_tds,但它给出了缺少free_tds的错误,但它已经安装了。怎么能过这个?~brewinstallfreetdsWarning:freetds-0.91.112alreadyinstalled~sudogeminstalltiny_tdsBuildingnativeextensions.Thiscouldtakeawhile...ERROR:Errorinstallingtiny_tds:ERROR:Failedtobuildgemnativeextension.完整日志如下:/System