总结UI原理和高级的UI优化方式

Android开发中UI优化方案整理 (UI渲染背景知识,系统做的优化,工程师优化方案,优化工具) UI 优化究竟指的是什么呢? 所谓的 UI 优化,应该包含两个方面: 一个是效率的提升,我们可以非常高效地把 UI 的设计图转化成应用界面,并且保证 UI 界面在不同尺寸和分辨率的手机上都是一致的; 另一个是性能的提升,在正确实现复杂、炫酷的 UI 设计的同时,需要保证用户有流畅的体验。 UI 渲染的背景知识 1. 屏幕与适配 屏幕的尺寸从 3 英寸到 10 英寸,分辨率从 320 到 1920 应有尽有,除此之外,材质也是屏幕至关重要的一个评判因素。目前智能手机主流的屏幕可分为两大类:一种是 阅读详情

不知道UI原理如何做UI优化?

本文内容分为三个部分,UI原理、LayoutInflater原理、UI优化,篇幅有点长,可以选择自己喜欢的章节进行阅读,每一个部分最后都有小结。

相信大家多多少少看过一些Activity启动源码分析的文章,也能大概说出Activity启动流程,例如这种回答:

AMS负责管理系统所有Activity,所以应用startActivity 最终会通过Binder调用到AMS的startActivity方法,AMS启动一个Activity之前会做一些检查,例如权限、是否在清单文件注册等,然后就可以启动了,AMS是一个系统服务,在单独进程,所以要将生命周期告诉应用,又涉及到跨进程调用,这个跨进程同样采用Binder,媒介是通过ActivityThread的内部类ApplicationThread,AMS将生命周期跨进程传到ApplicationThread,然后ApplicationThread 再分发给ActivityThread内部的Handler,这时候生命周期已经回调到应用主线程了,回调Activity的各个生命周期方法。

还可以细分,比如Activity、Window、DecorView之间的关系,这个其实也应该难度不大,又突然想到,setContentView为什么要放在onCreate中?,放在其它方法里行不行,能不能放在onAttachBaseContext方法里?其实,这些问题可以在源码中找到答案。

本文参考Android 9.0源码,API 28。

正文

写一个Activity,我们一般都是通过在onCreate方法中调用setContentView方法设置我们的布局,有没有想过这个问题,设置完布局,界面就会开始绘制吗?

一、从生命周期源码分析UI原理

Activity启动流程大致如下:

  1. Context -> startActivity
  2. AMS -> startActivity
  3. 进程不存在则通知Zygote启动进程,启动完进程,执行ActivityThread的main方法,进入loop循环,通过Handler分发消息。
  4. ApplicationThread -> scheduleLaunchActivity
  5. ActivityThread -> handleLaunchActivity
  6. 其它生命周期回调

从第4点开始分析

1.1 ActivityThread

1.1.1 内部类 ApplicationThread

AMS暂且先不分析,AMS启动Activity会通过ApplicationThread通知到ActivityThread,启动Activity从ApplicationThread开始说起,看下 scheduleLaunchActivity 方法

1.1.2 ApplicationThread#scheduleLaunchActivity
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
            CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
            int procState, Bundle state, PersistableBundle persistentState,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
            boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

        ActivityClientRecord r = new ActivityClientRecord();
        r.token = token;
        r.ident = ident;
        r.intent = intent;
        ...
        sendMessage(H.LAUNCH_ACTIVITY, r);
    } 

sendMessage 最终封装一个ActivityClientRecord对象到msg,调用mH.sendMessage(msg);,mH 是一个Handler,直接看处理部分吧,

1.2 ActivityThread的内部类H

private class H extends Handler {

	public void handleMessage(Message msg) {
		switch (msg.what) {
            case LAUNCH_ACTIVITY: {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                r.packageInfo = getPackageInfoNoCheck(
                        r.activityInfo.applicationInfo, r.compatInfo);
				//1、从msg.obj获取ActivityClientRecord 对象,调用handleLaunchActivity 处理消息
                handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
			...

	}


} 

上面是Handler很基础的东西,应该都能看懂,注释1,启动Activity直接进入 handleLaunchActivity 方法

1.3 ActivityThread#handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    // Make sure we are running with the most recent config.
    handleConfigurationChanged(null, null);

    // Initialize before creating the activity //初始化WindowManagerGlobal
    WindowManagerGlobal.initialize();

    //1. 启动一个Activity,涉及到创建Activity对象,最终返回Activity对象
    Activity a = Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
	//2. Activity 进入onResume方法
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

        if (!r.activity.mFinished && r.startsNotResumed) {
	    //3. 如果没能在前台显示,就进入onPuse方法
            performPauseActivityIfNeeded(r, reason);

        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
	//4. activity启动失败,则通知AMS finish掉这个Activity
        try {
            ActivityManagerNative.getDefault()
                .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                        Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
} 

注释1:performLaunchActivity,开始启动Activity了
注释2:Activity进入Resume状态,handleResumeActivity
注释3:如果没能在前台显示,那么进入pause状态,performPauseActivityIfNeeded
注释4,如果启动失败,通知AMS去finishActivity。

主要看performLaunchActivity(1.3.1) 和 handleResumeActivity(1.3.2)

1.3.1 ActivityThread#performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

	//1 创建Activity对象
	activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);

	//2 调用Activity的attach方法
	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);

	//3.回调onCreate
	mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
	
	//4.回调onStart
	activity.performStart();
	
	
	if (r.state != null || r.persistentState != null) {
	//5.如果有保存状态,则调用onRestoreInstanceState 方法,例如Activity被异常杀死,重写onSaveInstanceState保存的一些状态
        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
    }

	//6.回调 onPostCreate,这个方法基本没用过
	mInstrumentation.callActivityOnPostCreate(activity, r.state);

} 

从这里可以看出Activity几个方法调用顺序:

  1. Activity#attach
  2. Activity#onCreate
  3. Activity#onStart
  4. Activity#nnRestoreInstanceState
  5. Activity#onPostCreate

我们主要来分析 attach 方法 和 最熟悉的onCreate方法

1.Activity#attach
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) {
	//1、回调 attachBaseContext
    attachBaseContext(context);

	//2、创建PhoneWindow
    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    ...
    
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config; 

attach方法主要关注两点:
1.会调用attachBaseContext 方法;
2.创建PhoneWindow,赋值给mWindow,这个后面会经常遇到。

2. Activity#onCreate

大家有没有思考过,为什么 setContentView 要放在onCreate方法中?
不能放在onStart、onResume中我们大概是知道的,因为按Home键再切回来会回调生命周期onStart、onResume,setContentView多次调用是没必要的。 那如果是放在attachBaseContext 里面行不行?
看下 setContentView 里面的逻辑

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

public Window getWindow() {
    return mWindow;
} 

getWindow 返回的是mWindow,mWindow是在 Activity 的 attach 方法初始化的,上面刚刚分析过attach方法,mWindow是一个PhoneWindow对象。所以setContentView 必须放在attachBaseContext之后。

4. PhoneWindow#setContentView
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
	//1、 创建DecorView
        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 {
	//2、mContentParent是DecorView中的FrameLayout,将我们的布局添加到这个FrameLayout里
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ...
} 

installDecor 方法是创建DecorView,看一下主要代码

注释1:installDecor,创建根布局 DecorView,
注释2:mLayoutInflater.inflate(layoutResID, mContentParent); 将xml布局渲染到mContentParent里,第二节会重点分析LayoutInflater原理。

先看注释1

5. PhoneWindow#installDecor
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
	//1.创建DecorView
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
	// 2. mDecor 不为空,就是创建过,只需设置window
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
	//3.找到DecorView 内容部分,findViewById(ID_ANDROID_CONTENT)
        mContentParent = generateLayout(mDecor);

        // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
        mDecor.makeOptionalFitsSystemWindows();

	//4.找到DecorView的根View,给标题栏部分,设置图标和标题啥的
        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                R.id.decor_content_parent);

	//5.设置标题图标啥的
        if (decorContentParent != null) {
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setWindowCallback(getCallback());
            if (mDecorContentParent.getTitle() == null) {
                mDecorContentParent.setWindowTitle(mTitle);
            }

            ...
            mDecorContentParent.setUiOptions(mUiOptions);

            if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                    (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                mDecorContentParent.setIcon(mIconRes);
            } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                    mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                mDecorContentParent.setIcon(
                        getContext().getPackageManager().getDefaultActivityIcon());
                mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
            }
            if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                    (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                mDecorContentParent.setLogo(mLogoRes);
            }

            ...
        } 
        
		...
    }
} 

installDecor 主要做了两件事,一个是创建DecorView,一个是根据主题,填充DecorView中的一些属性,比如默认就是标题栏+内容部分

先看注释1:generateDecor 创建DecorView

6. PhoneWindow#generateDecor
protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
} 
7. DecorView 构造方法
DecorView(Context context, int featureId, PhoneWindow window,
        WindowManager.LayoutParams params) {
    super(context);
    ...

    updateAvailableWidth();

    //设置window
    setWindow(window);
} 

创建DecorView传了一个PhoneWindow进去 。

installDecor 方法里面注释2,如果判断DecorView已经创建过的情况下,直接调用mDecor.setWindow(this);

再看 installDecor 方法注释3

if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
		... 

给 mContentParent 赋值,看下 generateLayout 方法

8. PhoneWindow#generateLayout
 protected ViewGroup generateLayout(DecorView decor) {

    // 1.窗口各种属性设置,例如 requestFeature(FEATURE_NO_TITLE);
	...
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    }
	...
	if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
    }
	...
	
	//2.获取 windowBackground 属性,默认窗口背景
	if (mBackgroundDrawable == null) {
            if (mBackgroundResource == 0) {
                mBackgroundResource = a.getResourceId(
                        R.styleable.Window_windowBackground, 0);
            }
			...
	}

	...
	// 3.获取DecorView里面id为 R.id.content的布局
	ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	...
	if (getContainer() == null) {
	//4.在注释2的时候已经获取了窗口背景属性id,这里转换成drawable,并且设置给DecorView
        final Drawable background;
        if (mBackgroundResource != 0) {
            background = getContext().getDrawable(mBackgroundResource);
        } else {
            background = mBackgroundDrawable;
        }
        mDecor.setWindowBackground(background);

		

	}


	return contentParent;

} 

注释1: 首先是窗口属性设置,例如我们的主题属性设置了没有标题,则走:requestFeature(FEATURE_NO_TITLE);, 全屏则走setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));

还有其他很多属性判断,这里只列出两个代表性的,为什么说代表性呢,因为我我们平时要让Activity全屏,去掉标题栏,可以在主题里设置对应属性,还可以通过代码设置,也就是在onCreate方法里setContentView之前调用

getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN ); 

注释2:获取窗口背景属性id,这个id就是style.xml 里面我们定义的主题下的一个属性

<style name="AppThemeWelcome" parent="Theme.AppCompat.Light.DarkActionBar">
	<item name="colorPrimary">@color/colorPrimary</item>
    ...

    <item name="android:windowBackground">@mipmap/logo</item> //就是这个窗口背景属性

</style> 

注释3,这个ID_ANDROID_CONTENT 定义在Window中,
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; 找到id对应的ViewGroup叫contentParent,最终是要返回回去。

注释4:在注释2的时候已经获取了窗口背景属性id,这里转换成drawable,并且设置给DecorView,DecorView是一个FrameLayout,所以我们在主题中设置的默认背景图,最终是设置给DecorView

generateLayout 方法分析完了,主要做的事情是读取主题里配置的一堆属性,然后给DecorView 设置一些属性,例如背景,还有获取DecorView 里的内容布局,作为方法返回值。

回到 installDecor 方法注释4,填充DecorContentParent 内容

9. 填充 DecorContentParent
//这个是DecorView的外层布局,也就是titlebar那一层
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                R.id.decor_content_parent);

        if (decorContentParent != null) {
	
            mDecorContentParent = decorContentParent;
            mDecorContentParent.setWindowCallback(getCallback());
            
			//有该属性的话就进行相关设置,例如标题、图标
			mDecorContentParent.setWindowTitle(mTitle
《Unity3D高级编程 主程手记》第四章 用户界面(六) UI 优化(上) 动”指的是元素移动,或放大/缩小频率比较高的 UI。“静”就是静止不动的 UI,准确的说是界面上不会移动、旋转、缩放、更换贴图和颜色的 UI。 阅读详情

相关推荐

iOS——图像显示原理以及UI流畅性优化方案

文章目录图像显示原理CPU和GPU分别干了哪些事CPU和GPU在具体的工作上都有哪些承担UI卡顿和掉帧的原因滑动优化方案前的准备知识FPS像素点如何出现到屏幕上渲染参与者像素相关知识合成过程对齐与不对齐滑动优化方案UIView的绘制原理异步绘制离频渲染什么是离屏渲染?何时会触发离屏渲染为何要避免离屏渲染?总结 图像显示原理 关于CPU和GPU都是由总线连接起来的,在CPU输出的往往是一个位图,上...

咚个里个呛的博客 2024

Android中的postOnAnimation方法详解及示例代码

然后,在观察者的回调方法中,我们使用postOnAnimation方法添加了一个Runnable对象到消息队列中。本文将详细介绍postOnAnimation的使用方法,并提供相应的示例代码。本文详细介绍了Android中postOnAnimation方法的使用方法,并提供了一个示例代码来演示如何利用该方法在UI线程中执行动画或更新UI。需要注意的是,在使用postOnAnimation方法时,我们应该确保在适当的时机停止动画并从消息队列中移除Runnable对象,以避免内存泄漏或意外的UI更新。

PixelEnigma的博客 596

Godot引擎开发:VR UI和用户交互_(9).VR用户界面动画与过渡效果

通过本节的学习,您应该能够掌握在Godot引擎中实现VR用户界面动画与过渡效果的基本方法。包括使用节点、编写脚本、以及使用Shaders来实现复杂的动画效果。同时,还介绍了如何优化动画性能,确保在VR应用中获得流畅的用户体验。希望这些内容对您的VR开发有所帮助。如果您有任何问题或需要进一步的指导,请随时联系。

chenlz2007的博客 1870

NGUI 3.7.3:Unity用户界面设计与优化资源包

本文还有配套的精品资源,点击获取 简介:NGUI是为Unity 3D定制的用户界面系统,提供全面的工具集和组件,便于创建美观且功能强大的UI。NGUI 3.7.3unitypackage是最新改进版本,包含性能提升、用户体验优化及新功能。NGUI核心特性涵盖Widget系统、事件处理、图集和精灵管理、层级管理、2D物理系统、自动缩放、性能优化、脚本支持、XML配置,以及丰富...

weixin_33419305的博客 1393

资治通鉴:文言文、白话文对照读本

介绍一处可以免费下载资治通鉴的网址,该文献共20卷,排版为左边是文言文,右边是白话文翻译,对照阅读非常方便。下面是1-20卷的链接地址:第一卷:http://www.docin.com/p-9705067.html 第二卷:http://www.docin.com/p-9705979.html 第三卷:http://www.docin.com/p-9709382.html 第四卷:

aiwis的专栏 2548

脑机接口(BCI)从原理到开发实战 01|意念控制真的来了?一文看懂脑机接口的技术革命

类型定义典型应用主动式用户主动产生特定思维模式来控制设备运动想象(想象左手/右手移动)反应式用户被动接受外部刺激,系统检测大脑反应P300拼写器、SSVEP(稳态视觉诱发电位)被动式系统监测用户自发脑状态,不要求主动控制疲劳监测、情绪识别、注意力评估本系列后续文章将重点围绕主动式(运动想象)和反应式(P300/SSVEP)展开实战。脑机接口,这个曾经只存在于科幻中的概念,正在政策、资本、技术的三重驱动下,加速从实验室走向产业落地。2026年,注定是脑机接口发展史上浓墨重彩的一年。

小明的博客 1431

[RK3588-Android12] 深入解析USB Type-C PD充电功能的DTS配置与内核驱动修改

本文深入探讨了在RK3588 Android12平台上禁用USB Type-C PD充电功能的实现方法。通过分析设备树(DTS)配置与内核驱动(TCPM框架)的联动修改,详细解读了如何通过添加`pd-disable`属性及修改`tcpm.c`驱动代码,使Type-C端口绕过PD协议协商,仅工作在传统电源模式,以满足工控等特定场景对供电稳定性和成本控制的需求。

9o8p7i6u5y的博客 994

AMSR-2数据下载实战:从JAXA到LPRM的算法选择与效率优化

本文详细介绍了AMSR-2数据的下载实战,重点对比了JAXA与LPRM算法的差异及适用场景,并提供了高效下载方案和网络优化策略。通过实际案例和代码示例,帮助用户快速获取和处理AMSR-2数据,提升研究效率。

rl6adventurer的博客 691

全网最全RT-DETR涨点改进,基于官方源码1:1还原,在Ultralytics项目中配置rtdetr-r18、rtdetr-r34、rtdetr-r50、rtdetr-r101版本~小论文大论文必备

本文详细介绍了如何将RT-DETR的ResNet18/34/50/101/154版本移植到ultralytics项目中。主要内容包括:1) RT-DETR核心特点,如高效混合编码器、IoU感知查询选择等;2) 提供完整ResNet核心代码;3) 分步配置指南,包括创建模块文件和修改tasks.py;4) 各版本yaml配置文件的创建方法。该方案可帮助研究者快速实现RT-DETR改进,适用于目标检测相关论文研究。所有配置文件均可正常运行,为实验部署提供了便利。

Ai缝合怪、助力小伙伴高效发论文 3919

AI赋能合同审查:构建租赁合同智能审核系统的全流程解析

在商业地产、设备租赁等场景中,合同审核效率直接影响业务流转速度。传统人工审核存在三大痛点:审核周期长(平均2-3个工作日)、合规风险高(条款遗漏率约15%)、人力成本居高不下。智能审核系统可将审核时间压缩至秒级,风险识别准确率提升至98%以上。

理论都是虚的,代码才是王道。 1356

GMSL转eDP芯片MAX9686X(ADI)

MAX96860/MAX96862/MAX96864解串器可以把单路或双路GMSL转换成eDP V1.4a,支持820MHzPCLK的无压缩的24bpp(每个像素的位数)视频,或660MHz的30bpp视频。芯片的GMSL支持NRZ编码的6Gbps/187Mbps的GMSL2,PAM4编码的12Gbps/187Mbps的GMSL3。同时支持单线(COAX模式)双向全双工传输控制数据和video数据。支持DSC压缩的芯片,最大支持像素时钟1080MHz@24bpp或者864MHz@30bpp,传输方式包括同轴

qflook的博客 2837

XCP详解「3.1·ASAP2新建A2L文件」

后面是ASAP2新建A2L的操作步骤,如果是APE中新建的A2L就无需以下操作,APE只需要导入现成的A2L即可。退出点击保存时会提示保存A2L文件,是因为前面导出的是1.70版本,工程最低1.71,可以另外起个名字即可。导入elf文件,elf路径建议直接索引编译生成的Debug下的,防止编译后忘记更新。导出V1.70格式A2L文件,APE17支持V1.70。通过ASAP2生成需要A2L文件更常用些。同一个信号可以创建不同属性量。ASAP2中新建A2L文件。设置标定信号数量最大值。XCP详解「总目录」

光明磊学习&积累的地方 3059

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

上一篇: Android之Camera1实现相机开发
普通网友
博客等级 码龄5年 1粉丝 7原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值