从setContentView到onResume应用显示过程分析

2023-11-14

之前总体笼统地分析了Acitivity从启动到显示的过程(Activty启动到显示的过程[一]Activty启动到显示的过程[二]),发现很多细节没有注意到,后续挑些过程中比较重要的部分重点分析。

在上一篇文章分析了一个app从zygote到onCreate的过程(从zygote到onCreate应用启动过程分析),onCreate方法中不能少的就是setContentView,这次就分析Activity从setContentView到onResume的过程,顺便理清楚以下类的关系:

Activity, Window, DecorView, ViewRootImpl, IWindowSession, IWindow, WindowState, SurfaceSession.

先看setContentView部分:

在这里插入图片描述

Activity在应用程序中表示显示界面,持有Window对象mWindow。

一、Activity初始化

Activity在ActivityThread的performLaunchActivity()方法中被实例化,接下来就调用attach()方法,初始化一些必要信息,Activity的Window变量就是在这里被初始化的:

    // ActivityThread.java
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
      Activity activity = null;
      activity = mInstrumentation.newActivity(
                        cl, component.getClassName(), r.intent);
      activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config,
                            r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                            r.assistToken, r.shareableActivityToken);
      return activity;
    }

查看Activity->attach()方法:

    // Activity.java
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
      //.......
      mWindow = new PhoneWindow(this, window, activityConfigCallback);
      mToken = token;
      mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        // mWindow 拥有mToken引用。
      mWindowManager = mWindow.getWindowManager();			
        // mWindowManager拥有mWindow引用, 后续getSystemService(Context.WINDOW_SERVICE)返回此对象。
    }

mWindow变量在这里被初始化为PhoneWindow对象,mToken也被赋值,同样被传递给mWindow变量,最终赋值给PhoneWindow的父类Window.mAppToken变量。

    // Window.java
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

mWindowManager通过createLocalWindowManager(Window parentWindow)方法被实例化,持有Window对象的引用,并赋值给Activity对象,也就是说Activity对象在attach()后,便拥有了一个本地的WindowManager对象,它持有window对象,window对象持有token,后续在此Activity中addView()都是通过这个本地的WindowManager获取window.mAppToken来进行验证。

这个token是在Activity启动一开始时被ActivityStater实例化ActivityRecord对象时创建的,通过Intent作为参数实例化ActivityRecord.Token, “new Token(_intent).asBinder()”:

    // ActivityRecord.java
    ActivityRecord(ActivityTaskManagerService _service, WindowProcessController _caller,
            int _launchedFromPid, int _launchedFromUid, String _launchedFromPackage,
            @Nullable String _launchedFromFeature, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo,
            String _resultWho, int _reqCode, boolean _componentSpecified,
            boolean _rootVoiceInteraction, ActivityStackSupervisor supervisor,
            ActivityOptions options, ActivityRecord sourceRecord) {
                
        super(_service.mWindowManager, new Token(_intent).asBinder(), TYPE_APPLICATION, true,
                    null /* displayContent */, false /* ownerCanManageAppTokens */);
        appToken = (Token) token;
    }
    
    static class Token extends IApplicationToken.Stub {}

ActivityRecord作为在Framework层(system_server进程)用于管理应用层(app进程)Activity的对象,其嵌套类Token的作用就是将两者一一对应关联起来,ClientTransaction对象在两者沟通中扮演了一个重要的角色,要实例化ClientTransaction对象需要提供IApplicationThread和ActivityRecord.Token参数:

    // ActivityStackSupervisor.java
    final ClientTransaction clientTransaction = ClientTransaction.obtain(
            proc.getThread(), r.appToken);

值得注意的是,token是IBinder对象,支持Parcelble()序列化接口,在app进程(客户端)和system_server(服务端)通过log打印token对象会得到不同的结果,app进程(客户端)是一个BinderProxy对象。

在这里插入图片描述

关于ActivityRecord.Token和更多Activity启动细节可以查看之前的文章(Activty启动到显示的过程[一])。

Activity初始化后,onCreate()生命周期方法被调用,调用setContentView()。

二、setContentView

    // Activity.java
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

setContentView就两行代码,第一行是调用PhoneWindow处理setContentView,第二行是处理ActionBar。

需要注意的是一般的安卓应用程序继承的是AndroidX扩展包的AppCompatActivity,AppCompatActivity->setContentView()和Activity有些差异,所以也能从界面上看出继承AppCompatActivity类的Activity默认有ActionBar,而继承安卓原生Framework中的android.app.Activity类的Activity是没有ActionBar的。

    // PhoneWindow.java
    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);			//将应用程序的layout添加为mContentParent子view
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

从上面的UML图中可以看到PhoneWindow有两个比较重要的类变量,mDecor,mContentParent。在安卓的设计中,mContentParent是DecorView的子view,应用程序通过setContentView(R.layout.xml)添加的布局都被添加到mContentParent中,又是mContentParent的子view:

    mLayoutInflater.inflate(layoutResID, mContentParent);//layoutResID为应用程序setContenView(R.layout)传递的参数

一开始mContentParent变量为null,installDecor()查看DecorView的初始化流程:

    // PhoneWindow.java  
    		private void installDecor() {
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
              	//......
            }
        }

通过generateDecor(-1)实例化DecorView对象:

    // PhoneWindow.java  
    protected DecorView generateDecor(int featureId) {
      //......
      return new DecorView(context, featureId, this, getAttributes());
    }

三、DecorView初始化

和我们熟悉的View实例化不同的是,Decorview的实例化比较简单,并没有指定layout布局文件,从上面的UML图可以看到Decorview继承了FrameLayout,mContentParent是它的子view,mContentParent是怎么成为DecorView的子view的,把应用程序的布局添加到DecorView中呢?接着查看generateLayout()方法:

    protected ViewGroup generateLayout(DecorView decor) {
      //......
      int layoutResource;
      int features = getLocalFeatures();
      if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        layoutResource = res.resourceId;
      } else if(...) {
        layoutResource = R.layout.screen_progress;
      } else if(...) {
        layoutResource = R.layout.screen_custom_title;
      } else {
        layoutResource = R.layout.screen_simple;
      }
      mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      return contentParent;
    }

generateLayout()先定义了layoutResource变量,判断当前features指定合适的布局文件,在确定layout布局文件后,调用DecorView的onResourcesLoaded(mLayoutInflater, layoutResource)方法:

    // DecorView.java
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
      //......
        final View root = inflater.inflate(layoutResource, null);
      	addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
      	mContentRoot = (ViewGroup) root;
      	initializeElevation();
    }

onResourcesLoaded为我们解答了DecorView布局的问题,DecorView继承了FrameLayout,它将layoutResource指定的布局文件实例化为一个view添加到自己内部,宽高都为“MATCH_PARENT”,并把它赋值给mContentRoot变量,自此DecorView才有了布局,而不是一个FrameLayout空壳,完成了真正的初始化。

我们查看DecorView常用的布局文件R.layout.screen_simple:

    <!-- screen_simple.xml -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
        <ViewStub android:id="@+id/action_mode_bar_stub"
                  android:inflatedId="@+id/action_mode_bar"
                  android:layout="@layout/action_mode_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:theme="?attr/actionBarTheme" />
        <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>

接着看generateLayout()方法中mContentParent变量的初始化,contentParent被赋值给id为“ID_ANDROID_CONTENT”的view,并返回赋值给mContentParent。

"ID_ANDROID_CONTENT"在Window.java文件中定义,值为“com.android.internal.R.id.content”。

    // Window.java
    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

对,就是上面布局文件R.layout.screen_simple中 id 为 “content” 的view。

说明了mContentParent是DecorView的子view,其在DecorView中的布局位置。

使用UI检查工具查看继承android.app.Activity类Activity的布局解构,可以看到"id/content"的mContentParent。

在这里插入图片描述

作为对比,看下继承AppCompatActivity类Activity的布局解构,可以看到“id/content”和“id/action_bar_container”处于平级,顶部蓝色的ActionBar就处于“id/action_bar_container”了,中间大片的白色控件就是“id/content”留给应用显示区域,它们都是“id/decor_content_parent”的子view。
在这里插入图片描述

四、handleResumeActivity

onResume负责Activity的显示,ActivityThread->handleResumeActivity()方法负责处理onResume。

    // ActivityThread.java
    @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
      //......
            final Activity a = r.activity;
                View decor = r.window.getDecorView();
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                
      					wm.addView(decor, l);
    }

可以看到,显示Activity就是通过ViewManager.addView(),addView的对象就是刚才创建的DecorView。

五、addView

    // WindowManagerGlobal.java
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
      		//......
      					ViewRootImpl root;
                if (windowlessSession == null) {
                    root = new ViewRootImpl(view.getContext(), display);
                } else {
                    root = new ViewRootImpl(view.getContext(), display,
                            windowlessSession, new WindowlessWindowLayout());
                }
      
                view.setLayoutParams(wparams);
    
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
      
      					root.setView(view, wparams, panelParentView, userId);
    }

创建ViewRootImpl对象,调用其setView()方法。ViewRootImpl 是 View 的最高层级, 实现了 View 和 WindowManager 之间所需要的协议。

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
            AttachedSurfaceControl {
    
        final IWindowSession mWindowSession;
    		final W mWindow;
        
        public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            WindowLayout windowLayout) {
      			//......
            mWindowSession = session;
            mDisplay = display;
            mThread = Thread.currentThread();
            mWindow = new W(this);
            mChoreographer = Choreographer.getInstance();
    		}
        
        static class W extends IWindow.Stub {
          //......
        }
    }

这里不会对View的显示过程有细致的分析,不过可以通过ViewRootImpl的构造方法查看几个关键类之间的关系。

从WindowManagerGlobal->addView()可以看出,每次一个新的Activity被创建显示,都会创建一个新的ViewRootImpl对象,把Activity中的显示内容DecorView交给它去处理。

  • IWindowSession

ViewRootImpl关联了IWindowSession对象,在addView()方法中,如果windowlessSession==null,就会通过WindowManagerGlobal.getWindowSession()方法获取sWindowSession,这是一个静态变量,使用了单例设计模式,确保一个进程中只有一个IWindowSession对象,IWindowSession是app和WindowManger沟通的桥梁。

  • IWindow

IWindow在ViewRootImpl中被嵌套类W实现,和上一篇文件中(https://blog.csdn.net/qq_36063677/article/details/129756237)分析的ActivityThread内部类ApplicationThread一样,都使用了静态代理设计模式,用于WMS(system_server进程)管理ViewRootImpl,或者说Activity(Activity和ViewRootImpl是一一对应的关系)。和IWindowSession进程单例不一样的是,每次创建新的ViewRootImpl对象,构造方法也会实例化新的IWindow对象mWindow = new W(this),IWindowSession对应每个app应用程序,ViewRootImpl和IWindow对应每个Activity。

六、setView

接着往下看setView()方法:

    // ViewRootImpl.java
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
      //......
          mView = view;
      
      	  requestLayout();
      
    	  res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), userId,
                                mInsetsController.getRequestedVisibilities(), 				inputChannel, mTempInsets,
                                mTempControls, attachedFrame, sizeCompatScale);  
    }

这里先不关注requestLayout();,查看mWindowSession.addToDisplayAsUser()方法,Session类是IWindowSession的实现类,addToDisplayAsUser将逻辑交给WMS处理,在这里开始初始化WindowState对象:

七、WindowState

    // WindowManagerService.java
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
      //......
      
      					// 1.获取DisplayContent
      					final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
      					// 2.获取token(ActivityRecord)
      					WindowToken token = displayContent.getWindowToken(
                        hasParent ? parentWindow.mAttrs.token : attrs.token);
      
      					// 3.初始化WindowState
               			 final WindowState win = new WindowState(this, session, client, token, parentWindow,
                        appOp[0], attrs, viewVisibility, session.mUid, userId,
                        session.mCanAddInternalSystemWindow);
      					// 4.给这个窗体描述打开一个输入通道, 用于接收屏幕的点击事件(事件分发)
               		 	final boolean openInputChannels = (outInputChannel != null
		                        && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
		                if  (openInputChannels) {
		                    win.openInputChannel(outInputChannel);
		                }
               			 // 5.初始化SurfaceSession
      					win.attach();
      					// 6.添加到mWindowMap用于WMS管理
                		mWindowMap.put(client.asBinder(), win);
      
      					// 7.维护WindowContainer.mChildren变量
      					win.mToken.addWindow(win);
      					// 8.更新接收消费输入事件的Window
                		displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
    }
  1. 创建DisplayContent,InputMonitor在DisplayContent构造方法中被初始化。
  2. 既然是在Activity中addView,首先要做的就是获取代表Activity的token,验证token合法性。
  3. 对于WMS,每一个View都需要一个WindowState对象用来管理,传递给构造方法的参数有 WMS, session(IWindowSession),client(IWindow),token(ActivityRecord)等。
  4. 给这个窗体描述打开一个输入通道InputChannle, 用于接收屏幕的点击事件(事件分发)。关于InputChannel参考:Android InputChannel事件发送接收系统分析
  5. 接下来又是attach()方法,每个命名为attach的方法都不简单,判断刚在构造方法传递的IWindowSession对象是否初始化了SurfaceSession,如果没有开始初始化SurfaceSession与surfaceflinger建立连接,更多关于SurfaceSession细节(https://blog.csdn.net/qq_36063677/article/details/129369308)。
  6. 然后把创建的WindowState添加到mWindowMap用于后续管理:
    /** Mapping from an IWindow IBinder to the server's Window object. */
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
    
  7. mToken继承了WindowContainer类,在这里是ActivityRecord对象,mParent是Task对象,mChildren是WindowState,将创建的win对象添加到mChildren中管理。
  8. 更新接收消费输入事件的Window。

关于WindowContainer结构,参考(https://juejin.cn/post/7140286041121882126)

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

从setContentView到onResume应用显示过程分析 的相关文章

  • 将roottools.jar导入Android Studio

    我正在尝试从这里导入 roottools https code google com p roottools https code google com p roottools jar 文件 到 Android Studio 项目 到目前为
  • 有关 paddingStart 使用的冲突 lint 消息

    API 17 RTL 支持发布后 我将以下内容添加到我的清单中 android supportsRtl true 这导致 Lint 在我的视图中有 paddingLeft Right 的地方正确地向我发出这些警告 考虑添加 android
  • RecyclerView 未显示列表中的所有项目

    我在用RecyclerView在我的应用程序中 每次我打开屏幕时 我只能看到一项 但当我调试时 它每次都会出现onBindViewHolder method 这是我的适配器 Override public ViewHolder onCrea
  • “此版本中使用了已弃用的 Gradle 功能,使其与 Gradle 7.0 不兼容。” -反应-原生

    当我尝试运行反应本机应用程序时 我遇到此错误react native run android 我无法安装该应用程序 我正在尝试构建一个相机应用程序 我当前的react native版本 0 62 0 React cli版本 2 0 1 De
  • 删除视图并重新创建它

    有没有办法删除设置的视图 setContentView R layout set map center mapView MapView findViewById R id mapview 如果我再次调用此视图 则会收到一条错误消息 java
  • Android Ble GATT_ERROR 133 经常使用三星设备

    我正在研究 BLE 应用程序 我已经使用 Nexus Moto Samsung LG 等不同设备进行了测试 我仅在三星设备中收到 GATT 错误 133 三星 A5 2016 尝试连接 10 次 但只连接了 2 或 3 次 请帮助我 Non
  • Android平台源码中哪里可以找到版本信息

    Android 平台源文件中的版本信息在哪里找到 我尝试查找 设置 gt gt 中列出的有关手机的一些信息 显示的一些信息包括固件版本 模块编号 基带版本 内核版本 你可以给它办理登机手续platform build core versio
  • 删除 json 对象字符串中的“\”

    如何删除下面字符串中的特殊字符 String x message content toom recipients id 1000001865 room subject room 我使用了 x replaceAll 但它不起作用 您必须转义正
  • Android 从键盘读取

    我的登录屏幕根本没有文本字段 当用户使用 RFID 扫描仪扫描他的 id 令牌时 我会得到一个 8 个字符长的字符串 其原理与使用键盘相同 只是更快 我希望我的登录活动在用户扫描其令牌时而不是之前执行 有一个聪明的方法来实现这个吗 我不能有
  • Android/Java 创建辅助类来创建图表

    Goal 创建用于图形生成的辅助类 背景 我有 3 个片段 每个片段收集一些传感器数据 加速度计 陀螺仪 旋转 并使用 GraphView 绘制图表 以下是其中一个片段的代码 该代码当前工作正常 public class Gyroscope
  • 无论如何,要控制宋何时选择Android.bp,何时不选择?

    使用新的构建系统 即 Soong 安卓取代Android mk with Android bp 还有 Android Q 及以上版本 Soong将选择所有Android bp文件 无论所有文件都存在于何处 早些时候 对于 2 级和 3 级模
  • 将 firebase auth 与 google app engine 云端点集成

    有人可以指定 使用一些示例代码 如何验证谷歌云端点中的 firebase 令牌吗 最近提出的问题根本没有澄清 如何将 Firebase 身份验证与 Google 应用引擎端点集成 https stackoverflow com questi
  • Android 性能:SharedPreferences 的成本

    当我的应用程序启动时 我使用分片首选项中的值填充容器类 这个想法是处理 SharedPreferences 和 PreferenceManager 一次 因为我猜它们很重 这是一个示例 SharedPreferences prefs Pre
  • 取消通知

    我使用Onesignal推送通知 需要取消所有onPause和onResume的通知 NotificationManager notificationManager NotificationManager getApplicationCon
  • twitter4j => AndroidRuntime(446): java.lang.NoClassDefFoundError: twitter4j.http.AccessToken

    我正在尝试使用 twitter4j 我的应用程序来连接并发布到 Twitter 我正在关注本教程 http blog doityourselfandroid com 2011 02 13 guide to integrating twitt
  • 从 Handler.obtainMessage() 获取什么参数

    我正在使用线程来执行一些 BT 任务 我正在尝试向 UI 线程发送消息 以便我可以基于我的 BT 线程执行 UI 工作 为此 我使用处理程序 但我不知道如何检索发送到处理程序的数据 要发送数据 我使用 handler obtainMessa
  • 在 Android ADT Eclipse 插件中滚动布局编辑器

    有谁知道当布局编辑器的内容溢出一个 屏幕 时如何滚动这些内容 我说的是在设计时使用 ADT 布局编辑器 而不是在物理设备上运行时滚动 效果很好 关闭 Android 布局编辑器中的剪辑 切换剪辑 按钮位于 Android 布局编辑器的右上角
  • 如何在 Android 中保存 Edittext 中的文本而不丢失文本的粗体、斜体等功能

    我想做的就是从 Edittext 中获取文本 该文本具有粗体和斜体等功能 并将其保存在文本文件中 但是当我读回并显示它时 这些功能丢失了 它们不显示 如何通过将文本保存在文本文件或任何文件中来保持丰富的功能 您可以使用Html toHtml
  • Android:如何在布局中放置纯色矩形?

    我有一个可以很好地膨胀的relativelayout 我想在顶部添加一个跨越布局宽度的纯色矩形 我尝试将以下内容放入我的 xml 中
  • 在DialogFragment中,onCreate应该做什么?

    我目前正在摆弄 DialogFragment 以学习使用它 我假设相比onCreateView onCreate 可以这样做 public void onCreate Bundle savedInstanceState super onCr

随机推荐

  • Router-Link详解

  • 文件(或文件夹)的复制(Java)

    将源文件 或目录 复制到另一个目录里 三种方法 1 最普通的方法 主要用File类中的方法和IO流相关的类进行递归复制 2 主要用Files类中的copy 方法递归复制 3 主要用Files类中的copy 方法和walkFileTree 方
  • 构建Buildroot根文件系统(I.MX6ULL)

    Busybox构建的根文件系统只有一些常用的命令和文件 Buildroot不仅集成了 busybox 而且还集成了各种常见的第三方库和软件 开发环境 Buildroot 版本 buildroot 2019 02 6 tar gz 虚拟机 4
  • XSS-通关小游戏(1-20)

    在玩游戏之前先简单的了解下 什么是XSS 1 什么是xss XSS攻击全称跨站脚本攻击 是为不和层叠样式表 Cascading Style Sheets CSS 的缩写混淆 故将跨站脚本攻击缩写为XSS XSS是一种在web应用中的计算机安
  • Unity --- UGUI(Unity Graphical user interface)--- Canvas画布

    1 UI User Interface 使用者与机器之间的交互界面 1 所谓的自适应系统指的是分辨率的适应 比如在一个分辨率下做的UI放到另一个分辨率下显示时 如果没有自适应系统的话就会导致UI过大 过小 被辟成一半等等情况 而有了自适应系
  • Android:项目结构

    前言 默认情况下 在 Android Studio 中创建 Android 项目后 将默认生成 Project Packages Scratches Android Project Fines Problems Production Tes
  • 性能指标有哪些

    1 响应时间 Response time 响应时间就是用户感受软件系统为其服务所耗费的时间 对于网站系统来说 响应时间就是从点击了一个页面计时开始 到这个页面完全在浏览器里展现计时结束的这一段时间间隔 看起来很简单 但其实在这段响应时间内
  • 开源GIS浅谈

    开源GIS浅谈 转 http blog csdn net happyduoduo1 article details 51773850 谈到GIS软件 首先让我们想到的是GIS界的龙头大哥ESRI公司旗下的ArcGIS产品 从最初接触的ver
  • js中的微任务和宏任务,附面试题

    因为javascript是一门单线程语言 所以代码的解析执行都要以自上而下的执行 直到任务队列 task queue 的出现 js开始有了异步任务 当一段代码需要稍后执行时 便可以使用异步方案 setTimeout setInterval
  • Eclipse C debug报错Can‘t find a source file at “xxxxx“Locate the file or edit the source lookup path

    笔记备忘 1 操作入下 Debug configerations进入如下界面 双击你的放置器对应的选项 添加新的选项 在source位置记得添加如下选项 2 解决完上面报错还提示如下 no source for main step1 工程右
  • C++构造函数的各种用法全面解析(C++初学面向对象编程)

    文章目录 一 构造函数的基本用法 二 带参构造函数与其调用 三 拷贝构造函数 四 构造函数的重载 一 构造函数的基本用法 1 构造函数概念 一个类的对象被创建的时候 编译系统对象分配内存空间 并自动调用该构造函数 由构造函数完成成员的初始化
  • 解决:如何将pytorch的版本改为和cuda对应、如何使用笔记本电脑自带的NVIDIA使用GPU跑深度学习

    Step1 安装cuda 网址 https developer nvidia com cuda toolkit archive PS 此处必须先看看电脑显卡是否自己就装了cuda 可以通过执行命令行语句nvcc V以此检查cuda是否有 如
  • 字典表设计

    为什么字典表 存在问题 某些变量在多个地方使用 而且一般是固定的 但是随着系统升级和后期变化 可能需要改变 如果这些变量写死在代码里面将会变得难以维护 所以要将其从代码中抽离出来 一般的业务系统客户端与用户交互的时候都会使用下拉框组件 对于
  • Kafka3.0.0版本——消费者(分区的分配以及再平衡)

    目录 一 分区的分配以及再平衡 1 1 消费者分区及消费者组的概述 1 2 如何确定哪个consumer来消费哪个partition的数据 1 3 消费者分区分配策略 一 分区的分配以及再平衡 1 1 消费者分区及消费者组的概述 一个con
  • 样本的均值和方差的无偏估计与测试阶段均值方差的关系

    什么是无偏估计 估计是用样本统计量 可以理解为随机抽样 来估计总体参数时的一种无偏推断 无偏估计的要求就是 估计出来的参数的数学期望等于被估计参数的真实值 所以呢 可以看出 估计值也是一个变量 因为是随机的嘛 真实值谁也不知道啊 因为你不可
  • 数据隐藏之Qt中d指针详解

    最近看到代码有用到了Qt中的Q D指针 就去学习了下 发现真的很好用 因此写一篇文章总结下 student h class CStudent public CStudent CStudent private string m name in
  • LeetCode 1. 两数之和

    题目链接 https leetcode cn problems two sum 思路如下 从前往后遍历 n u m s nums
  • 分销系统开发 分销商城开发 分销功能 Java开发"三级分销"业务功能

    众所周知 互联网时代 分享经济现在随处可见 各行各业都有各种分销系统 最为常见的就是三级分销 那么如何实现这个业务功能了 笔者通过本篇图文案例给大家分享一下实现过程 此处以二级分级分销实现为例 1 什么是二级分销 二级分销其实是一种让用户分
  • 【面试】赢时胜和花旗

    一 赢时胜 1 springmvc的底层 DispatcherServlet gt HandlerMaping gt Handler gt HandlerAda 2 spring注入的方式 spring循环依赖 注解是否解决了循环依赖 3
  • 从setContentView到onResume应用显示过程分析

    之前总体笼统地分析了Acitivity从启动到显示的过程 Activty启动到显示的过程 一 Activty启动到显示的过程 二 发现很多细节没有注意到 后续挑些过程中比较重要的部分重点分析 在上一篇文章分析了一个app从zygote到on