Android Activity 启动流程 二:setContentView

2023-11-06

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。

在这里插入图片描述

- > 上 篇,Activity创建后,还只是调用了onCreate方法,页面并没有展示出来,还需要调用setContentView方法,加载页面布局,并进行渲染,最后展示。

一、概览

本源码基于Android 12
看代码前,我们先上一张Activity,Window, DecorView三者之间的关系图
在这里插入图片描述

DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。
在该布局下面,有标题view和内容view两个子元素。

Activity setContentView 核心就是PhoneWindow的setContentView方法,其主要干了两件事:
1.完成DecorView的创建与加载,这个DecorView会在后面onresume后添加到window中
2.将MainActivity的布局加载到DecorView内的一个ViewGroup中

创建DecorView,即installDecor方法,其内部用到了两个核心的方法:
1.generateDecor方法创建出DecorView对象
2.generateLayout方法完成这个DecorView对象的布局加载,并完成了MainActivity的父容器的赋值(即contentParent变量)

先上一张流程图
在这里插入图片描述

二、setContentView()

我们跟踪一下源码,看看这个方法是怎么做的

    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

这里window即为 PhoneWindow,
window的初始化是在 Acticity 创建的时候初始化, 在Acticity对象创建后,会调用attach方法


    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final void attach(Context context, ActivityThread aThread, ...) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);

        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    }

PhoneWindow.java


    @Override
    public void setContentView(int layoutResID) {
        根view 为空,则初始 mDecor view
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            
            // 将布局文件添加到 mContentParent
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

    调用installDecor()进行DecorView的初始化
    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 创建出一个DecorView并返回
            mDecor = generateDecor(-1);

        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //对mContentParent进行赋值,作为Activity布局的父容器,
            mContentParent = generateLayout(mDecor);

        }
    }

首先判断了mContentParent是否为null,如果为空则执行installDecor()方法,同时初始化一个mContentParent,这个就是Activity布局的父容器

三、inflate

LayoutInflater.java

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
        }
    }

    private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root, boolean attachToRoot) {
        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);

                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
        } finally {
        }
        return null;
    }

布局就是这么添加进mContentParent中的。
在这里插入图片描述

但是,view还是没有显示出来的,此时代码所做的事情仅仅只是加载了布局,并没有开始view的测量、布局、绘制工作。
对应方法是onMeasure, onLayout, onDraw,这些操作在后面

四、view的绘制展示

每一个Activity组件都有一个关联的Window对象,用来描述一个应用程序窗口。每一个应用程序窗口内部又包含一个View对象,用来描述应用程序窗口的视图。
我们再看下图:
在这里插入图片描述

Activity#onResume()之后才是布局由不可见变为可见的,我们看源码

4.1 Activity.onResume

ActivityThread.java
下面这个方法是在Activity onCreate创建后调用的,handleResumeActivity,不清楚的可以看前面app启动文章.


    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {


        // 这个方法会调用 activity的 onResume 方法
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        
        final Activity a = r.activity;

        // window 未被添加进 windowmanager
        if (r.window == null && !a.mFinished && willBeVisible) {

            // window
            r.window = r.activity.getWindow();
    
            // decorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            
            ViewManager wm = a.getWindowManager();
            
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;

        } else if (!willBeVisible){
        }

        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                
                // DecorView 添加到 window
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
        
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {

            使布局可见
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }

        r.nextIdle = mNewActivities;
    }
    
    

在上面的代码中,会先调用Activity的onResume, 然后再是view的绘制,最后将DecorView 设置 可见;

4.2 WindowManager addView

WindowManagerImpl.java


    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    ...
    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

这里也是一个空壳代码,调用WindowManagerGlobal

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
			

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
		

        ViewRootImpl root;
        View panelParentView = null;

		// 加锁
        synchronized (mLock) {
		
			//实例化ViewRootImpl类
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
				// //调用ViewRootImpl.setView方法,把DecorView作为参数传递进去
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

在方法内部,会通过跨进程方式向WMS(WindowManagerService)发起一个调用,从而将DecorView最终加到Window上,在这个过程中,ViewRootImpl、DecorView和WMS会彼此关联。
最后,WMS调用ViewRootImpl.performTraversals 方法开始View的测量、布局、绘制。

4.3 ViewRootImpl

一个 Window 对应着一个 ViewRootImpl 和 一个 VIew。这个 View 就是被 ViewRootImpl 操作的.

从上面代码,我们可以看到,ViewRootImpl的初始化是在WindowManagerGlobal的addView中

ViewRootImpl.java


/**
 * We have one child
 */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
        
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */
        
                // 刷新布局的操作,触发view的measure -> layout -> draw 操作
                requestLayout();
        
        
                try {
                    //将 View 添加到 WMS 中
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes, ...);
                    
                } catch (RemoteException e) {
        
                } finally {
        
                }

                // Set up the input pipeline. 设置了一系列的输入通道
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
    
            }
        }
    }

首先会调用requestLayout方法来刷新布局,然后将 View 添加到 WMS 中,最后是view事件的处理;
view事件的处理,最后还是会回到了 PhoneWindow 中的 DecorView 来处理,剩下的就是从 DecorView 开始将事件层层传递给内部的子 View 中了
这里就不展开

ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    }
    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

    void doTraversal() {
        if (mTraversalScheduled) {

            performTraversals();

        }
    }

requestLayout()最终会调用到performTraversals,在这个方法中会调用 View 的 measure() ,layout() ,draw() 方法。
我们看下面源码


private void performTraversals() {

	final View host = mView;

	if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
			|| mForceNextWindowRelayout) {


		try {
			if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
			
				performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
						!mFirst, INVALID_DISPLAY /* same display */);
			}


		} catch (RemoteException e) {
		}

		if (!mStopped || wasReportNextDraw) {
                //View 的测量
				performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);


				if (measureAgain) {
                    //View 的测量
					performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
				}

				layoutRequested = true;
			}
		}
	} else {
	
	}


	if (didLayout) {
        // View 的布局
		performLayout(lp, mWidth, mHeight);


	}


	if (!cancelDraw) {
        // View 的绘制
		performDraw();
	} else {
	}

	mIsInTraversal = false;
}

4.4 addWindow & makeVisible

com.android.server.wm.Session.java


    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
                outActiveControls);
    }

Activity.java

DecorView的状态设置为可见,那么布局也就可见了
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

五、 推荐阅读

Java 专栏

SQL 专栏

数据结构与算法

Android学习专栏

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Activity 启动流程 二:setContentView 的相关文章

  • 从关卡堆栈中获取相对比例的数学

    为这个可怕的标题道歉 我花了 10 分钟试图用一句话来解释这一点 但失败了 虽然提示这个问题的应用程序是用Java Android 编写的 但我认为它非常通用并且适用于任何语言 欢迎使用伪代码 或简单的英语 回复 我不确定是否应该标记所有通
  • 从 sbt 程序集运行 uber jar 会导致错误:无法找到或加载主类

    我有一个使用 sbt 程序集插件打包为 uber jar 的 Spark 作业 这build sbt指定一个可运行的 main 作为生成的 uber jar 的目标 mainClass in assembly Some com foo Ba
  • Java元数据读写

    是否可以以通用方式 对于所有图像类型 在 Java 中读取和写入元数据 我找到了一些示例 但它们总是特定的 例如 JPEG 或 PNG 我需要一些足够通用的东西 而不是到处都有 if else 语句 我不想重写源代码 但这是一个很好的例子
  • Java:使用 Java.util.concurrent 线程访问读取线程串行端口

    我正在尝试编写一个 Java 串行设备驱动程序并想使用 对我来说是新的 java util concurrent包裹 我有一种发送数据包然后等待 ACK 的方法 我打算有炭 接收在不同的线程中运行 如果接收线程收到 ACK 它应该使用发送数
  • 如何使用 Guava 连接字符串?

    我写了一些代码来连接字符串 String inputFile for String inputLine list inputFile inputLine trim 但我不能使用 连接 所以我决定使用 Guava 所以我需要使用Joiner
  • Facebook Android 意图

    我对这个意图有疑问 这个意图是发送文本类型的消息 一切正常 电子邮件 短信 推特以及手机上的任何内容 但唯一有问题的是facebook 它会尝试以链接而不是文本的形式发布 Intent s new Intent android conten
  • Android - 主页按钮发出什么意图?

    我想知道设备的主页按钮到底执行什么操作 即 当您单击主页按钮时会发出什么意图 意图类别和操作 回到空白的主屏幕 我想知道在单击我自己的自定义按钮时实现此操作涉及什么 谢谢 PS我知道这不是标准的 但我的设备也不是 如果您想显示主屏幕 可以通
  • 使用Java开发跨平台,不同平台字体缩放不同

    我正在为我的大学制作一些软件 需要一个 GUI 在它的第一个版本中 我让它使用系统外观 因此它看起来像 Linux Mac Windows 中的本机应用程序 我发现这很麻烦 因为我必须根据操作系统使所有 JLabel 具有不同的大小 无论分
  • Webview 电子邮件链接 (mailto)

    我有一个视图并查看该网站有用于发送电子邮件的 malito 代码 当我打开链接时 会出现错误 我希望当我打开链接时打开 Gmail 应用程序或其他电子邮件应用程序 感谢所有帮助者 public class teacher extends A
  • 战争库中的罐子爆炸

    我们可以将分解的 jar 文件放入 war web inf 库中吗 它在 JBOSS 4 2 中对我不起作用 我收到以下错误并且无法部署应用程序 Caused by javax management RuntimeOperationsExc
  • 无法映射 ftl 文件中的 jsonRequest 属性

    我想在 FTL 文件中映射下面的 json 文件市场和子市场字段 但是当我尝试下面的代码时 它没有映射 有人可以帮助我吗 我从 2 天开始就无法映射它 Json请求 ProcessOrderRequest prevalidationMode
  • 查找配对的 Android 蓝牙设备是否在范围内的正确方法?

    我想编写一个充当蓝牙客户端的应用程序 我想做的是找出最好的方法来确定我支持的特定设备是否在范围内 而不是一直尝试在其上执行 BluetoothDevice connect 并失败如果不在范围内 这里我们假设设备已经配对 恐怕在特定设备处于范
  • 读/写带有特殊字符的.txt文件

    I open Notepad Windows 并写 Some lines with special characters Special 并前往另存为 someFile txt 与Encoding set to UTF 8 在Java中我有
  • Selenium - 等待网络流量

    我们将 Selenium 与 Java API 和一些 Javascript 用户扩展一起使用 我们在应用程序中使用了大量 AJAX 调用 我们的许多测试随机失败 因为有时 AJAX 调用完成得比其他时候慢 因此页面未完全加载 我们通过等待
  • 方法签名中带或不带synchronized关键字的方法具有相同的字节码

    对于以下 2 个类 获得相同的 Java 字节码 java版本 java 版本 1 8 0 181 Java TM SE 运行时环境 构建 1 8 0 181 b13 Java HotSpot TM 64 位服务器 VM 内部版本 25 1
  • 如何将QR码中的3个方块替换为圆圈以使用Paint android使用zxing自定义QR码?

    我用它作为自定义的参考 从方形到圆形使用zxing生成的QR码它是在java中所以我尝试将它转换为在android中使用 使用 zxing 生成具有自定义点形状的 QR 码 https stackoverflow com questions
  • 如何从spark中的hbase表中获取所有数据

    我在 hbase 中有一个大表 名称为 UserAction 它具有三个列族 歌曲 专辑 歌手 我需要从 歌曲 列族中获取所有数据作为 JavaRDD 对象 我尝试了这段代码 但效率不高 有更好的解决方案来做到这一点吗 static Spa
  • 如何使用剪辑来减少绘画时间?

    我正在尝试使用 Clip 来减少 CPU 负载 但剪辑在屏幕上留下了一些我似乎无法摆脱的垃圾 另外 打开和关闭剪辑似乎对 CPU 负载没有影响 在任一情况下 大部分时间似乎都花在重绘管理器和绘制缓冲图像上 import static jav
  • 如何检测用户是否禁用 GPS(Android - Play 服务)

    我使用 gms location LocationListener Google Play 服务 来获取用户的位置 它工作正常 但我想检测用户何时禁用或启用他 她的 GPS 就像这张照片一样 当我打开 关闭位置时 不会调用任何方法 当我切换
  • 我找不到 IntelliJ 快捷方式

    我使用 vim 一段时间 我知道有一个 intellij vim 插件 我很好奇内置的 IntelliJ 文本导航存在什么 如何打开实时模板来创建模板 如何查看以 tr 开头的现有模板列表 如何进行全局搜索并在当前文档中进行搜索 然后转到下

随机推荐

  • Google Test源码浅析(二) -------- TEST宏

    一 TEST宏的定义 一个例子 TEST TestCaseName1 TestName1 cout lt lt hello1 lt lt endl
  • 使用pyfinance进行证券收益分析!金融界的一大帮手!

    pyfinance简介 在查找如何使用Python实现滚动回归时 发现一个很有用的量化金融包 pyfinance 顾名思义 pyfinance是为投资管理和证券收益分析而构建的Python分析包 主要是对面向定量金融的现有包进行补充 如py
  • Git简介与基本使用

    文章目录 一 Git简介 1 1产生历史 二 Git的安装与配置 三 创建一个版本库 四 版本创建与回退 4 1使用 4 2工作区和暂存区 4 2 1工作区 Working Directory 4 2 2版本库 Repository 4 3
  • Python3 输入和输出

    1 输出格式美化 Python两种输出值的方式 表达式语句和 print 函数 第三种方式是使用文件对象的 write 方法 标准输出文件可以用 sys stdout 引用 如果你希望输出的形式更加多样 可以使用 str format 函数
  • 关于AD如何按1:1比例保存成PDF的具体做法

    之前用AD09保存一个A4大小原理图 但是通过智能PDF导出的PDF不能按1 1比例占满A4纸 保存出来的PDF如图所示 现在需要把这个原理图保存成竖着占满整个A4纸 试了很多办法 网上也找了好久 有说在页面设置里面设置纸张方向 但是并没有
  • 【XSS漏洞05】XSS通关大挑战

    目录 1 实验简介 2 通关过程 2 0 通关前准备 2 1 关卡1 2 2 关卡2 2 3 关卡3 2 4 关卡4 2 5 关卡5 2 6 关卡6 2 7 关卡7 2 8 关卡8 2 9 关卡9 2 10 关卡10 2 11 关卡11 2
  • 关于如何将代码推到自己的gitee分支上去

    我的方法只是用于自己的推送 如果哪里有错希望大佬指正 第一步 找到自己项目的本地文件 在项目文件里点击自己的 Git bush here 唤醒控制台 第二步 初始化自己的git 输入git init 第三步 git config globa
  • Vscode格式化代码后 代码不自动换行

    在做项目的时候原本可以在一行可以清晰展示的代码 格式化后就多行展示 有点烦人 可以尝试加入以下代码试试 Vscode中找到设置 有2种方法 vscode gt 首选项 gt 设置 之后在出现的界面输入settings json 也可以在vs
  • c# 中重载WndProc,实现重写“最小化”自定义功能的方法

    code 1 复制代码 代码如下 private void Form1 SizeChanged object sender EventArgs e 最小化隐藏窗体 if this WindowState FormWindowState Mi
  • 关于在servlet中创建了cookie,但在jsp中无法读取到该cookie的问题(已解决)

    servlet中的代码 Cookie c new Cookie userlogin ul toString ul是一个实体类的实例 我重写了该类的toString 方法返回的将是 用户名 密码 的字符串 c setMaxAge 60 res
  • String类——判断功能

    String类 判断功能 断点设置 字符串 查看API文档 String类的构造方法 字符串的长度 内存结构图 断点设置 一般情况下 需要在每个方法的第一条有效语句设置断点 String类 0 String类所在的包 java lang 所
  • 树的引进以及二叉树的基础讲解——【数据结构】

    W Y的主页 代码仓库分享 当我们学习完前面的数据结构 难度也就会上升 但是这个也是非常重要的数据结构 今天我们来学习一种新的数据类型 树 目录 树的概念以及结构 树的概念 树的相关概念 树的表示 树在实际中的运用 二叉树概念及结构 二叉树
  • QT5.12.6+QGIS3.10二次开发(Qtcreater)(一)环境搭建

    一 前言 QtCreater的编译模式有msvc和mingw两种 msvc调用的第三方库是 lib结尾的 而mingw调用的库是 a结尾的 在windows下下载的QGIS开发包中的库文件是 lib库 所以 在QtCreater中直接使用下
  • sublime text3实用教程-安装js智能提示插件

    转载请注明出处 原文连接 http blog huanghanlian com article 5c7f637d0577597db4c0a289 在诸多工具中 Sublime Text无疑是一款利器 它界面优美 功能强悍 性能令人惊讶 需要
  • java有啥区别 jsp_javaweb和jsp的区别

    展开全部 JAVA是一种编程语言62616964757a686964616fe4b893e5b19e31333366306533 可以编写应用程序 主要应用在网络编程上 JSP是建立在JAVA基础上的一种网络编程语言 只能在网页上应用 可以
  • Linux文件权限

    Linux 的安全性 每个能进入Linux系统的用户都会被分配唯一的用户账户 用户对系统中各种对象的访问权限取决于他们登录系统时用的账户 用户权限是通过创建用户时分配的用户ID User ID 通常缩写为UID 来跟踪的 UID是数值 每个
  • react使用ant-design的confirm弹窗Modal和别的Modal样式冲突解决方案

    前言 当同一个页面使用不同的Modal 很有可能会有样式冲突 但是只需要在Modal里加一个className 就能避免 只是如果遇到的是编程式写法的Modal呢 其实也很简单 import styles from index less f
  • 篇五:创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求元素的值各不相同。

    创建一个长度为6的int型数组 要求数组元素的值都在1 30之间 且是随机赋值 同时 要求 元素的值各不相同 创建一个长度为6的int型数组 要求数组元素的值都在1 30之间 且是随机赋值 同时 要求 元素的值各不相同 author xia
  • U盘 / 移动硬盘在 Mac 苹果电脑上无法使用问题

    原因 U盘 移动硬盘的磁盘格式是 NTFS 而 Mac 操作系统并不支持该格式的磁盘 所以导致文件无法被写入 需要将U盘 移动硬盘格式化成ExFAT文件格式 Mac OS X 系统的 HFS Windows 的 NTFS 格式 HFS 在
  • Android Activity 启动流程 二:setContentView

    关于作者 CSDN内容合伙人 技术专家 从零开始做日活千万级APP 专注于分享各领域原创系列文章 擅长java后端 移动开发 商业变现 人工智能等 希望大家多多支持 目录 一 概览 二 setContentView 三 inflate 四