解析 setContentView(R.layout.activity_home)流程

Android应用开发:深入理解Activity布局加载过程
本文详细剖析了Android应用中Activity如何通过setContentView()加载布局的过程,从AppCompatActivity到AppCompatDelegate,再到AppCompatDelegateImpl,解释了布局XML文件如何通过LayoutInflater解析并添加到视图树中,帮助开发者深入了解Android应用的内部工作机制。

我们使用activity,设置布局的时候会用setContentView(R.layout.activity_home),那到底是怎么加载的呢?

public class HomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

    }
}

1 首先进入AppCompatActivity---->setcontentview,看下源码

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider 


{ 
    //首先调用的是父类(AppCompatActivity)的setcontentView方法
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);//接着调用的AppCompatDelegate的方法
    }

    //AppCompatDelegate 此类表示一个委托,您可以用来将AppCompat的支持扩展到任何activity 中
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}

2 AppCompatDelegate 是一个抽象类,AppCompatDelegateImpl 是抽象类的实例。activity的步骤都在此类中代理操作。

public abstract class AppCompatDelegate {


   @NonNull
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);//真实 的实例去操作
    }

}

3 AppCompatDelegateImpl 的具体步骤

@RestrictTo(LIBRARY)
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {


 1//构造方法,传入当前activity 和 当前activity window.callback
  AppCompatDelegateImpl(Activity activity, AppCompatCallback callback) {
        this(activity, null, callback, activity);
    }

2 //
 private AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback,
            Object host) {
        mContext = context;
        mAppCompatCallback = callback;
        mHost = host; //当前activity

        if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED && mHost instanceof Dialog) {
            final AppCompatActivity activity = tryUnwrapContext();
            if (activity != null) {
                // This code path is used to detect when this Delegate is a child Delegate from
                // an Activity, primarily for Dialogs. Dialogs use the Activity as it's Context,
                // so we want to make sure that the this 'child' delegate does not interfere
                // with the Activity config. The simplest way to do that is to match the
                // outer Activity's local night mode
                mLocalNightMode = activity.getDelegate().getLocalNightMode();
            }
        }
        if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
            // Try and read the current night mode from our static store
            final Integer value = sLocalNightModes.get(mHost.getClass());
            if (value != null) {
                mLocalNightMode = value;
                // Finally remove the value
                sLocalNightModes.remove(mHost.getClass());
            }
        }

        if (window != null) {
            attachToWindow(window);
        }

        // Preload appcompat-specific handling of drawables that should be handled in a special
        // way (for tinting etc). After the following line completes, calls from AppCompatResources
        // to ResourceManagerInternal (in appcompat-resources) will handle those internal drawable
        // paths correctly without having to go through AppCompatDrawableManager APIs.
        AppCompatDrawableManager.preload();
    }

3//读取样式,设置window 背景等
private void attachToWindow(@NonNull Window window) {
        if (mWindow != null) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }

        final Window.Callback callback = window.getCallback();
        if (callback instanceof AppCompatWindowCallback) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }
        mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
        // Now install the new callback
        window.setCallback(mAppCompatWindowCallback);

        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
                mContext, null, sWindowBackgroundStyleable);
        final Drawable winBg = a.getDrawableIfKnown(0);
        if (winBg != null) {
            // Now set the background drawable
            window.setBackgroundDrawable(winBg);
        }
        a.recycle();

        mWindow = window;
    }

4 //调用setContentView
 @Override
    public void setContentView(int resId) {
        ensureSubDecor(); //确保 phonewindow decordeview 绘制完成
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //content 是我们自己绘制view 的父布局
        contentParent.removeAllViews(); //先清理view
        LayoutInflater.from(mContext).inflate(resId, contentParent);//通过LayoutInflater 解析xml 文件生成view,然后contentParent.addview(读取resId 创建的view);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

4.1//确保创建 主题样式的根布局
 private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (mDecorContentParent != null) {
                    mDecorContentParent.setWindowTitle(title);
                } else if (peekSupportActionBar() != null) {
                    peekSupportActionBar().setWindowTitle(title);
                } else if (mTitleView != null) {
                    mTitleView.setText(title);
                }
            }

            applyFixedSizeWindow();

            onSubDecorInstalled(mSubDecor);

            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!mIsDestroyed && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }


    }

总结:调用流程:我们创建的activity.setcontentview() --> AppCompatActivity.setcontentview() -->AppCompatDelegateImpl.setcontentview()

如何把xml 文件添加到根视图呢?通过 LayoutInflater.inflate(),在通过XmlResourceParser 解析xml 文件,生成xmlview,然后 contentv view.add(xmlview),一个view 树就创建完了。

<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".HomeActivity"> <TextView android:id="@+id/tv_user" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> package com.lss.loginregister; import android.os.Bundle; import android.widget.TextView; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class HomeActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_home); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); // 获取 Intent 中传递的数据 int userId = getIntent().getIntExtra("user_id", -1); // 默认值为 -1 String username = getIntent().getStringExtra("username"); String password = getIntent().getStringExtra("password"); String phone = getIntent().getStringExtra("phone"); String email = getIntent().getStringExtra("email"); TextView tvUser = findViewById(R.id.tv_user); String user = "账户:" + username + "\n" + "密码:" + password + "\n" + "手机:" + phone + "\n" + "邮箱:" + email; tvUser.setText(user); } } ------ 要求对这个home页进行修改,采用material提供组件,在home页可以显示DataTransmissionActivity、JsonParseActivity和NoteActivity
07-24
你好像越搞越乱了啊,我给你的是RoutePlanActivity的代码。最初的样子是这样的package com.example.bus; import android.os.Bundle; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import com.amap.api.maps.AMap; import com.amap.api.maps.MapView; import com.amap.api.services.route.BusPath; import com.amap.api.services.route.BusRouteResult; import com.amap.api.services.route.DriveRouteResult; import com.amap.api.services.route.RouteResult; import com.amap.api.services.route.RouteSearch; public class RoutePlanActivity extends AppCompatActivity implements RouteSearch.OnRouteSearchListener { private MapView mapView; private AMap aMap; private RouteSearch routeSearch; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_route_plan); mapView = findViewById(R.id.map_view); mapView.onCreate(savedInstanceState); if (aMap == null) aMap = mapView.getMap(); routeSearch = new RouteSearch(this); routeSearch.setRouteSearchListener(this); parseIntentAndStartSearch(); } private void parseIntentAndStartSearch() { String startMode = getIntent().getStringExtra("start_mode"); double targetLat = getIntent().getDoubleExtra("target_lat", 0); double targetLng = getIntent().getDoubleExtra("target_lng", 0); if (targetLat == 0 || targetLng == 0) { Toast.makeText(this, "目标位置无效", Toast.LENGTH_SHORT).show(); finish(); return; } // 构造目标点 com.amap.api.services.core.LatLonPoint startPoint, targetPoint; if ("my_location".equals(startMode)) { // TODO: 实际应使用定位获取“我的位置” // 这里模拟北京某坐标 startPoint = new com.amap.api.services.core.LatLonPoint(39.909186, 116.397411); } else { double startLat = getIntent().getDoubleExtra("start_lat", 0); double startLng = getIntent().getDoubleExtra("start_lng", 0); startPoint = new com.amap.api.services.core.LatLonPoint(startLat, startLng); } targetPoint = new com.amap.api.services.core.LatLonPoint(targetLat, targetLng); RouteSearch.FromAndTo fromAndTo = new RouteSearch.FromAndTo(startPoint, targetPoint); RouteSearch.BusRouteQuery query = new RouteSearch.BusRouteQuery(fromAndTo, RouteSearch.BUS_DEFAULT, "", 0); routeSearch.calculateBusRouteAsyn(query); } @Override public void onBusRouteSearched(BusRouteResult result, int rCode) { if (rCode == 1000 && result != null && result.getPaths() != null && !result.getPaths().isEmpty()) { BusPath best = result.getPaths().get(0); Toast.makeText(this, "推荐路线:" + best.getDuration() / 60 + "分钟,步行" + best.getWalkDistance() + "米", Toast.LENGTH_LONG).show(); // TODO: 绘制路线(后续可加 Polyline 或 RouteOverlay) } else { Toast.makeText(this, "未找到公交路线", Toast.LENGTH_SHORT).show(); } } @Override public void onDriveRouteSearched(DriveRouteResult result, int rCode) {} @Override protected void onResume() { mapView.onResume(); super.onResume(); } @Override protected void onPause() { mapView.onPause(); super.onPause(); } @Override protected void onDestroy() { mapView.onDestroy(); super.onDestroy(); } @Override protected void onSaveInstanceState(Bundle outState) { mapView.onSaveInstanceState(outState); super.onSaveInstanceState(outState); } } 你结合一下上下文看看,我正在做个公交查询系统,刚刚没跟你说完多久,希望在home跟map界面实现搜索功能。home的搜索先跳转到界面1,用户点击到这去跳转到界面2,map的搜索是既有起点又有终点的,直接跳转到界面2,好像问久了之后你都脱离上下文了
最新发布
11-04
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值