- 浏览: 6023 次
- 性别:
-
最近访客 更多访客>>
最新评论
-
lfkabc:
做人要厚道,不能自己有收获了也不动声色的这么走了。顶起!
Android最佳实践 -
zhangjinpeng:
很棒 受益匪浅
Android最佳实践 -
yzhong_sa:
正是我香了解的好东西。。
Android最佳实践
为性能设计:
1)避免创建对象
对象的创建从来不是免费的。虽然GC使得内存申请代价不再高昂,但是申请总是比不申请来得昂贵。如果你在一个用户接口循环中申请对象,你将会强行执行周期性的GC,在用户体验上出现一些小的“打嗝”,因此除非不得已,你应该避免创建对象实例,下面是一些例子可以帮助理解:
当你在一组输入数据中抽取字符串时,尝试返回源数据的子串,而非创建一个副本。你将会创建一个新的String对象,但是它会和数据共享字符数组char[]。
如果你有一个返回String的方法,而且你知道它的结果将会一直被追加到StringBuffer,改变你的签名和实现,使这个函数里面直接追加,避免创建临时对象。
一个更激进的主意是将多维数组切成与之平行的一维数组:
一个int数组比Integer数组要好,但也有一个公认的事实就是两个平行的int数组要比一个(int,int)对象数组要高效很多。对于其它原始数据类型亦如是。
如果你需要实现一个存储一组对象(Foo,Bar)的容器,请记住两个平等的Foo[]和Bar[]数组通常元比一个定制对象数组要好(当然,对于此有个例外,就是当你设计一个API供其它代码访问时;在那样的情况下,通常最好是为保证API的正确性而牺牲一点速度。但是在你的内部代码,你应该尽可能保持高效)。
通常来说,避免创建临时对象,如果你可以的话。更少的对象创建意味着更小频率的GC,这对用户体验有直接的影响。
2)用Native方法
当处理字符串时,要毫不犹豫地使用诸如String.indexOf()、String.lastIndexOf()之类的专门方法,这些是典型的用C/C++代码实现的方法,它们可以轻易地比实现同样功能的Java循环快10-100倍。
对此建议的一反面是调用一个native方法要比调用一个解析的方法,不要将native方法用于琐碎的计算,如果可以避免的话。
优先使用Virtual而非Interface
假如你有一个HashMap对象,你可以声明它为一个HashMap或一个通用的Map:
Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();
哪一个更好?
一般的会说你该选择Map,因为它允许你改变其实现,对于通常的编程来说这是对的,但是对于嵌入式系统来说这并不是太妙。通过接口的引用来调用一个方法要比通过一个具体类型的引用调用virtual方法花多2倍的时间。
如果你已经选择了一个HashMap,因为它正好适用你正在做的事情,那通过Map来调用就没有什么价值了。考虑到IDE可以为你重构代码,用Map来调用就没有太大价值了,即使你不知道你代码将去向何方(但是,再一次的,公共的API是又是一个例外:好的API较少考虑性能)。
3)优先选择static而非virtual
如果你不必访问一个对象的字段,使你的方法成为static方法。它可以被更快地调用,因为它不需要一个虚拟方法表间接调用。同时它也是一个好的做法,因为从方法的签名可以看出调用这个方法不会改变对象的状态。
4)避免内部的Getter/Setter
在一些像C++的语言中,通常的做法是用getter(如:i=getCount())代替直接地访问字段(i=mCount),在C++这是一个很好的习惯,因为编译器通常能够内联这个访问,并且你需要限制或debug字段访问,你可以在任何时候增加代码。
在Android,这是一个坏主意。虚拟方法调用代价是昂贵的,实例字段查找代价更高。沿用面一般向对象编程实践在公开接口中提供gettter和setter是合理的,但在一个类中你应该直接访问字段。
Cache字段查找
访问对象字段要比访问本地变量慢得多,如下面这段:
for (int i = 0; i < this.mCount; i++)
dumpItem(this.mItems[i]);
应该写成这样:
int count = this.mCount;
Item[] items = this.mItems;
for (int i = 0; i < count; i++)
dumpItems(items[i]);
(我们用一个显式的"this"来表明这是一个成员变量。)
有一个相似的指引就是,不要在for语句中的第二个从句中调用方法。例如下面这段代码将会在每次迭代中都会执行一次getCount(),这是一个巨大的浪费,你可以将它的值cache为一个int。
for (int i = 0; i < this.getCount(); i++)
dumpItems(this.getItem(i));
通常,如果你将要访问一个实例字段多次,一个好的习惯就是创建一个临时变量。例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
if (isHorizontalScrollBarEnabled()) {
int size = mScrollBar.getSize(false);
if (size <= 0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams( computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
mScrollBar.draw(canvas);
}
}
这是对成员字段mScrollBar的四次分开查找,通过将mScrollBar缓存到本地变量,四次成员字段查找变成四次本地变量引用,这样更为高效。
同样地,方法参数作为本地变量拥有相同的性能特征。
声明常量为final
考虑在类开头的如下声明:
static int intVal = 42;
static String strVal = "Hello, world!";
编译器产生一个叫<clinit>的类初始化器方法,它在类首次使用时被执行。这个方法将42存到intVal,并为intVal从类文件字符串常量表中抽出一个引用,当这些值在后面被引用到时,它们以字段查找的方式被访问。
我们可以用final关键字改进之:
static final int intVal = 42;
static final String strVal = "Hello, world!";
这个类不再需要一个<clinit>方法,因为常量存到直接由VM处理的类文件静态字段初始化器,代码访问intVal将会直接使用integer值42,而对intVal的访问会用一个相对廉价的“字符串常量”指令来代替一个字段查找。
声明一个方法或类为final并不能直接获得性能上的好处,但它确实能起到某些优化作用。例如,假如编译器知道一个"getter"不能被一个子类重写,它能够内联这个方法调用。
你也可以将本地变量声明为final,然而这并无真正意义上的性能提升。对于要地变量,只有在使代码更清晰(或你不得不,如为了在匿名内部类中使用)时使用final。
小心使用增强的For循环语句
增强的For语句可以用于实现了Iterable接口的Collection,对于这些对象,一个iterator被申请用来进行接口调用hasNext()和next()。对于ArrayList,你最好直接遍历它,但对于其它collections,增强的For循环语句将会等同于显式的迭代用法。
尽管如此,下面的代码展示了增强的For语句的可为人接受的用法:
public class Foo {
int mSplat;
static Foo mArray[] = new Foo[27];
public static void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; i++) {
sum += mArray[i].mSplat;
}
}
public static void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; i++) {
sum += localArray[i].mSplat;
}
}
public static void two() {
int sum = 0;
for (Foo a: mArray) {
sum += a.mSplat;
}
}
}
zero()在循环中每次迭代获取静态字段两次计算数组长度一次。
one()将所有东西存到本地变量,避免查找。
two()用到了增强的For循环语句,由编译器产生的代码拷贝数组引用和数组长度到本地变量,使之成为一个遍历数组元素的一个很好的选择。它确实在主循环中产生了一个额外的本地载入/存储,使得它比起one()有点慢并且长了4bytes。
总之,增强的For语句对于数组表现良好,但对iterable对象要小心使用,因为有额外的对象创建。
避免Enum类型
Enum非常方便,但不幸的是当考虑到时间和速度时就让人痛苦。例如这个:
public class Foo {
public enum Shrubbery { GROUND, CRAWLING, HANGING }
}
将编译成一个900byte的.class文件,在首次使用是时,类初始化器在代表每个被枚举的值对象上激活<init>方法。每个对象都有其静态字段,并且整个集合就存储在一个数组(一个称为“$values”的静态字段)上,对于仅仅的三个integer来说,那是太多的代码和数据了。
这个:
Shrubbery shrub = Shrubbery.GROUND;
导致了一次静态字段查找。如果“GROUND”是一个static final int编译器将会将它看作是一个常量并内联它。
相反地,当然,是运用enum你可以得到一个更优雅的API和一些编译时的值检查。因此,通常折衷的办法是:为API,你应该千方百计地使用enum,但是当考虑到性能时尝试避免使用它。
利用内部类使用包作用方域
考虑下面的类定义:
public class Foo {
private int mValue;
public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}
private void doStuff(int value) {
System.out.println("Value is " + value);
}
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}
}
在这里我们要特别指出的是这里定义了一个内部类(Foo$Inner),它可以直接访问外部类的私有方法和私有实例字段,这是合法的,代码的执行的结果是如预期般的“Value is 27”。
问题在于,Foo$Inner是一个完全独立的类,这使得直接访问其私有方法是非法的,为了架起桥梁,编译器会产生如下两个虚拟方法
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
当内部类代码需要访问外部类的mValue变量或激活doStuff方法时就会调用这些方法。这就意味着上面的代码清楚表明了你是通过访问器来访问成员字段的,而非直接访问。前面我们讨论过访问器是比直接访问是要慢的,所以这是一个由于某种特定语言方言所导致的隐性性能打击。
我们可以通过声明由内部类访问的字段和方法为具有包作用域而非私有作用域来解决这个问题。这样运行得更快并且移除了额外产生的方法(不幸的是,这也意味着这些字段可以被同包下的其它类所访问,这个是违反了使所有的字段成为私有的标准OO做法的,再一次的,如果你是在设计一个公共的API的话,你可能要慎重地考虑这一优化策略)。
9)避免使用Float类型
在Pentium CPU发布之前,对于游戏作者来说做很多整型计算是很正常的事。有了Pentium之后,浮点计算联合处理器成了内置功能,你的游戏通过交错整型和浮点操作比只有整型计算时运行起来要快得多。在桌面系统上通常的可以自由的使用浮点数。
不幸的是,嵌入式处理器很少具有硬件浮点支持,所以所有的"float"和"double"操作都是在软件上进行。某些基本的浮点操作可能会花费数微秒。
还有,甚至对于整型数,一些芯片支持硬件乘法但缺少硬件除法,在这种情况下,整型除法和取模运算是在软件上执行的——如果你是在设计一个哈希表或做很多数学运算这就是你需要考虑的事情。
为响应灵敏性设计
代码可能通过各种性能测试,但是当用户使用时还是会需要漫长的等待,这些就是那种响应不够灵敏的应用——它们反应迟钝,挂起或冻住周期很长,或者要花很长时间来处理输入。
在Android上,系统通过向用户显示一个称为应用无响应(ANR:Application Not Responding)的对话框来防止在一段时间内响应不够快。用户可以选择让应用继续,但是用户并不会想要每次都来处理这个对话框。因此应把你的应用设计得响应灵敏,使得系统不必显示ANR给用户。
通常地,当不能响应用户输入时系统显示一个ANR。例如,如果一个应用在IO操作(经常是网络访问)上阻塞了,那么主应用线程就会无法处理正在进行的用户输入事件。经过一段时间,系统认为应用已经挂起,向用户显示一个ANR,让用户可以选择关闭。
相同地,如果你的应用花太多的时间在构建详细的内存结构上,又或者在计算游戏的下一个动作上,系统会认为你的应用已经挂起。用上面的技术来保证这些计算是高效的一直都是很重要的,但是即使是最高效的代码运行也是需要花费时间的。
在这两种情况下,解决的办法通常就是创建一个子线程,在这个子线程上做你的大部分工作。这样让你的主线程(驱动用户接口事件循环)保持运行,并让你的代码免于被系统认为已经冻住。因为这样的线程化通常都是在类级别上来完成的,所以你可以认为响应性能问题是一个类问题(与上面描述的方法级别的性能问题)。
这个文档讨论了Android系统是如何决定一个应用没有响应的,并提供了指引来保障你的应用是响应灵敏的。
1)是什么引发了ANR?
在Android系统上,应用的响应灵敏性由Activity Manager和Window Manager system services所监控,当它监测到如下的其中一个条件时,Android就会为特定的应用显示一个ANR:
5秒内对输入事件无响应。
一个BroadCastReceiver在10秒内没有执行完毕。
怎样避免ANR?
考虑到上面对ANR的定义,让我们来研究一下这是为什么会发生以及怎样最好的组织你的应用以避免ANR。
Android应用正常是运行在一个单独的(如main)线程中的,这就意味着在你应用主线程中正在做的需要花很长时间来完成的事情都能够激活ANR对话框。因为你的应用并没有给自己一个机会来处理输入事件或Intent广播。
因此任何运行在主线程中的方法应该做尽可能少的事情。特别地Activitiy在关键生命周期方法中如onCreate()和onResume()应当做尽可能少的设置。潜在地的耗时长的操作(如网络或数据库操作,或高耗费数学计算如改变位图大小)应该在子线程里面完成(或以数据库操作为例,可以通过异步请求)。尽管如此,这并不是说当等待子线程完成的过程中你的主线程必须被阻塞——你不必调用Thread.wait()或Thread.sleep(),恰恰相反,你的主线程应该为子线程提供一个Handler,以便子线程完成时可以提交回给主线程。以这种方式来设计你的应用,将会允许你的主线程一直可以响应输入,以避免由5秒钟的输入事件超时导致的ANR对话。这些做法同样应该被其它任何显示UI的线程所效仿,因为它们属于同样类型的超时。
IntentReciever执行时间的特定限制限制了它们应该做什么:在后台执行的一些琐碎的工作如保存设置或注册通知。至于其它在主线程里被调用的方法,在BroadcastReceiver中,应用应该避免潜在的长耗时操作或计算,而是应该用子线程来完成密集任务(因为BroadcastReceiver的生命周期是短暂的)。对Intent broadcast作出响应,你的应用应该启动一个Service来执行长耗时的动作。同样,你也应该避免从Intent Receiver中启动Activity,因为它会产生一个新的屏,偷走任何用户正在运行的应用的焦点。对Intent broadcast作出的响应,假如你的应用需要向用户显示什么东西,应该用Notification Manager来完成。
增强响应灵敏性
通常,在一个应用中,100到200微秒是一个让用户感觉到阻滞的阈值,因此这里有些小技巧让你用来使你的应用看起来响应更灵敏。
如果你的应用正在后台对用户输入作出响应,显示正在进行的进度(ProgressBar和ProgressDialog对此很有用)。
特别是对于游戏,在子线程中做移动的计算。
如果你的应用有一个耗时的初始化过程,考虑用闪屏或尽可能快地渲染主界面并异步地填充信息。在这两种情况下你都应该表明进度正在进行,以免用户觉得你的应用被冻住了。
为无缝设计:
即使你的应用是快速且响应灵敏的,一些设计仍然能句对用户造成问题——因为与其它应用未计划的交互或者对话,意外的数据丢失,无意识的阻塞等等。为了避免这些问题,有助于理解你的应用运行的环境和可以影响你的应用的系统交互。总之,你应该倔尽全力地开发一个与系统和其它应用无缝交互的应用。
一个常见的无缝问题就是一个应用的后台进程(如service或broadcast receiver)对某事件作出响应而弹出对话框,这看起来仿佛并无大碍,特别是当你在模拟器上单独地构建和测试你的应用时。然而,当你的应用在真正的设备上运行,后台线程显示对话框时,你的应用当时可能没有获得用户焦点。这就会出现你的应用会在活动的应用后面显示对话框,或者从当前应用中获得焦点并显示对话框的情况,而管论当时用户正在做什么(如正在打电话等)。那样的行为可能对你的应用或用户不起作用。
为了避免这些问题,你的应用应该利用适当的系统资源——Notification类,来通知用户。利用通知,你的应用可以通过在状态条上显示一个图标来通知用户事件已经发生,而非获得焦点和打断用户。
另外一个无缝问题的例子就是,Activity由于未能正确实现onPause()及其它生命周期方法而无意中丢失了状态或用户数据。又或者,你的应用要暴露供其它应用使用的数据,你应该通过ContentProvider来实现,而非通过一个全世界都可读的原始文件或数据库。
这些例子的共同特点就是,它们都是关于如何跟系统和其它应用协作得更好,Android系统的设计就是将所有的应用看作是一个松散耦合的组件联邦,不是一堆墨盒代码。这就使你作为一个开发者可以将整个系统视为只是一个更大一点的组件联邦。这样有得于你将应用与其它应用清晰和无缝的集成,所以作为回报,你应该更好的设计你的代码。
这个文档讨论了常见的无缝集成问题和怎样去避免它们。它包含了如下主题:
别丢弃数据
一定要记住Android是一个移动平台。看起来很显然,但是记住这个事实很重要,就是任何Activity(如"正在打进来的电话"应用)在任何时候都有可能弹出来覆盖你的Activity,这会激活onSaveInstanceState()和onPause()方法,并导致你的应用被杀死。如果当其它Activity出现时,用户正好在你的应用上编辑数据,你的应用被杀死的同时那些数据也很可能会丢失。当然了,除非你先保存了进行中的工作。“Android方式”是这么做的:能接收和编辑用户输入的应用需要重写onSaveInstanceState()方法并以恰当的方式保存它们的状态,当用户重新访问应用时,就能重新获得数据。
一个运用这个行为经典的例子就是邮件应用,当用户正在撰写邮件时另一人Activity开始了,应用应该将正在编辑的邮件保存为草稿。
不要暴露原始数据
如果你不想穿着内衣在大街上散步,同样你的数据也不应如此。尽管可能暴露某些应用可以方便其它应用读取,但这通常不是最好的主意。暴露原始数据要求其它的应用能够理解你的数据格式;如果你改变了格式,你将会破坏其它没有同时更新的应用。
“Android方式”就是创建一个ContentProvider通过一个清晰的、深思熟虑的、可维护的API来暴露你的数据给其它应用。使用ContentProvider就像一个Java接口来分离和组件化两段紧密耦合的代码,这就意味着你能够修改你数据的内部格式而不用修改由ContentProvider暴露的接口,这样就不会影响其它应用。
别打断用户
如果能确定一个用户是带有目的性的运行一个应用才是安全的。那就是为什么你除非是直接响应当前活动的用户输入,不然就要避免产生Activity的原因。
那就是说,不要从后台运行的BroadcastReceiver和Service中调用startActivity()。如果这样做将会打断任何正在运行的应用,并使用户恼怒。甚至你的Activity可能成为一个“击键强盗”接收一些用户正在为上一个Activity提供的输入,视乎你的应用所做的,这是这可能是个坏消息。
取代直接从后台直接产生Activity UIs,你应该用NotificationManager来设置通知,这将会出现在状态条上,当用户空闲时可以点击它们来看你的应用向他们显示了什么。
(注意,当你的Activity已经在前台时所有这些都没适用:这时,对于输入的响应,用户期望看到你的下一个Activity。)
有太多事要做?在一个线程里做
如果你的应用需要做一个代价高昂或长耗时的计算,你可能要将它移到一个线程里。这个将会防止显示“Application Not Responding”对话框给用户,最终导致你的应用完全终止。
默认地,在一个Activity中的代码和其所有的View运行在同一个线程上。这与处理UI事件的线程是同一个。例如,当一个键被按下时,一个key-down事件被添加到Activity主线程的队列。事件处理系统需要很快地让这个事件出列并处理这个事件。不然,系统数秒后将会认为应用已经挂起并替用户杀死这个应用。
如果你有长耗时的代码,让它在你的Activity上内联运行将会在使它运行在事件处理线程上,这很大程度上阻塞了了事件处理句柄。这会延缓输入处理并导致ANR对话框。为了避免之,将你的计算移到一个线程中。在为响应灵敏性设计中已经讨论了如何做。
5)不要过载一个单一的Activity屏
任何值得使用的应用都可能会有几个不同的屏幕。当设计你的UI屏幕时,请一定要运用多个Activity对象实例。依赖于你的开发背景,你可能像解释某些类似Java Applet的东西一样来解释一个Activity,Activity是你应用的入口点。然而,那并不是准确:一个Applet的子类是一个Java Applet的单一入口点,而一个Activity应该被看作一个潜在的进入你的应用的多个入口点。在你的”main”Activity和任何其它你可能有的Activity之间的唯一不同就是,那“miain”Activity碰巧是那个唯一在你的AndroidManifest.xml文件中对“android.intent.action.MAIN”动作有兴趣的一个而已。
所以,当设计你的应用时,把你的应用看成一个Activity对象的联邦。从长远来看,这会使得你的代码更具可维护性。
6)扩展系统主题
当提到用户接口的观感时,协调是很重要的。用户为那些与他们所期望的用户接口相反的应用所震动。当设计你的UI时,你应当尽量避免出现太多你自己主题,相反地,用同一个主题。你可以重写或扩展那部分你必须的主题,但是至少你是基于与其他应用相同的UI基础上的。详细可以参阅“应用风格和主题”部分。
7)设计你的UI可以与多屏分辨率一起工作
不同的的基于Android的设备可能会支持不同的分辨率。甚至一些可能支持随时更改分辨率。保证你的布局和图片足够灵活对于在不同设备屏幕上正常显示是非常重要的。
幸运的是,这是很容易办到的。简单讲,你需要做的就是为你的关键分辨率提供不同版本的作品,然后设计你的布局适应各种不同的维度。(例如,避免作用硬编码位置而用相对布局。)如果那样做的话,剩下的系统会处理,你的应用在任何设备上看起来都很棒。
8)假定网络是很慢的
Android设备会有多种网络链接选项。所有的都会提供数据访问,虽然有一些会比另一些更快。其中速度最慢的就是GPRS(GSM网络的非3G数据服务)。即便具备3G能力的设备在非3G网络上也会花很多的时间,所以网速低将会是一个长期存在的事实。
那就是为什么你应该针对最小化的网络访问和带宽编写你的应用。你不能假设网络是快速的,所以你应该一直计划它是慢的。如果你的用户碰巧是在一个快速的网络上,那很好——他们的体验只会提升。你要避免相反的情况:应用有时可用,但有时慢得令人沮丧,得看用户是在哪在什么时间,这样的应用可能不会受欢迎。
别假定触摸屏和键盘
Android可能支持多种外观形状。那就是说一些Android设备将会有完整的“QWERTY"键盘,而其它的可能会有40键、12键或其它键盘设置。同样地,一些设备会有触摸屏,但很多会没有。
当构建你的应用时,一定要记住,不要假定特定的键盘布局——当然了,除非你真的喜欢限制你的应用以到它只能在某些设备上运行。
一定要节省设备电池
如果移动设备经常局限于屋内,那就不是很“移动”。移动设备是电池供电的,而我们如果能让电池每充一次的电量使用得更持久一些,每个人都会更开心——特别是用户。其中的两个用电大户就是处理器和音频,那就是为什么你写的应用应尽量做少的工作的同时尽可能频繁地使用网络的原因。
最小化你的应用使用的处理器时间就归结为书写高效的代码。从音频上最小化功耗,要确保优雅地处理错误条件,并仅获取你需要的东西。例如,如果连接网络失败不要一直重试连接网络,如果失败了一次,很可能是用户没有接收信号。如果你立即重试,那么你所做的一切只是在浪费电池能量。
用户是相当聪明的:如果你的程序是高耗电的,你可以相信他们会发觉的。在那一点上,唯一可以确定的是你的程序将不会保持安装非常久。
1)避免创建对象
对象的创建从来不是免费的。虽然GC使得内存申请代价不再高昂,但是申请总是比不申请来得昂贵。如果你在一个用户接口循环中申请对象,你将会强行执行周期性的GC,在用户体验上出现一些小的“打嗝”,因此除非不得已,你应该避免创建对象实例,下面是一些例子可以帮助理解:
当你在一组输入数据中抽取字符串时,尝试返回源数据的子串,而非创建一个副本。你将会创建一个新的String对象,但是它会和数据共享字符数组char[]。
如果你有一个返回String的方法,而且你知道它的结果将会一直被追加到StringBuffer,改变你的签名和实现,使这个函数里面直接追加,避免创建临时对象。
一个更激进的主意是将多维数组切成与之平行的一维数组:
一个int数组比Integer数组要好,但也有一个公认的事实就是两个平行的int数组要比一个(int,int)对象数组要高效很多。对于其它原始数据类型亦如是。
如果你需要实现一个存储一组对象(Foo,Bar)的容器,请记住两个平等的Foo[]和Bar[]数组通常元比一个定制对象数组要好(当然,对于此有个例外,就是当你设计一个API供其它代码访问时;在那样的情况下,通常最好是为保证API的正确性而牺牲一点速度。但是在你的内部代码,你应该尽可能保持高效)。
通常来说,避免创建临时对象,如果你可以的话。更少的对象创建意味着更小频率的GC,这对用户体验有直接的影响。
2)用Native方法
当处理字符串时,要毫不犹豫地使用诸如String.indexOf()、String.lastIndexOf()之类的专门方法,这些是典型的用C/C++代码实现的方法,它们可以轻易地比实现同样功能的Java循环快10-100倍。
对此建议的一反面是调用一个native方法要比调用一个解析的方法,不要将native方法用于琐碎的计算,如果可以避免的话。
优先使用Virtual而非Interface
假如你有一个HashMap对象,你可以声明它为一个HashMap或一个通用的Map:
Map myMap1 = new HashMap();
HashMap myMap2 = new HashMap();
哪一个更好?
一般的会说你该选择Map,因为它允许你改变其实现,对于通常的编程来说这是对的,但是对于嵌入式系统来说这并不是太妙。通过接口的引用来调用一个方法要比通过一个具体类型的引用调用virtual方法花多2倍的时间。
如果你已经选择了一个HashMap,因为它正好适用你正在做的事情,那通过Map来调用就没有什么价值了。考虑到IDE可以为你重构代码,用Map来调用就没有太大价值了,即使你不知道你代码将去向何方(但是,再一次的,公共的API是又是一个例外:好的API较少考虑性能)。
3)优先选择static而非virtual
如果你不必访问一个对象的字段,使你的方法成为static方法。它可以被更快地调用,因为它不需要一个虚拟方法表间接调用。同时它也是一个好的做法,因为从方法的签名可以看出调用这个方法不会改变对象的状态。
4)避免内部的Getter/Setter
在一些像C++的语言中,通常的做法是用getter(如:i=getCount())代替直接地访问字段(i=mCount),在C++这是一个很好的习惯,因为编译器通常能够内联这个访问,并且你需要限制或debug字段访问,你可以在任何时候增加代码。
在Android,这是一个坏主意。虚拟方法调用代价是昂贵的,实例字段查找代价更高。沿用面一般向对象编程实践在公开接口中提供gettter和setter是合理的,但在一个类中你应该直接访问字段。
Cache字段查找
访问对象字段要比访问本地变量慢得多,如下面这段:
for (int i = 0; i < this.mCount; i++)
dumpItem(this.mItems[i]);
应该写成这样:
int count = this.mCount;
Item[] items = this.mItems;
for (int i = 0; i < count; i++)
dumpItems(items[i]);
(我们用一个显式的"this"来表明这是一个成员变量。)
有一个相似的指引就是,不要在for语句中的第二个从句中调用方法。例如下面这段代码将会在每次迭代中都会执行一次getCount(),这是一个巨大的浪费,你可以将它的值cache为一个int。
for (int i = 0; i < this.getCount(); i++)
dumpItems(this.getItem(i));
通常,如果你将要访问一个实例字段多次,一个好的习惯就是创建一个临时变量。例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
if (isHorizontalScrollBarEnabled()) {
int size = mScrollBar.getSize(false);
if (size <= 0) {
size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams( computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
mScrollBar.draw(canvas);
}
}
这是对成员字段mScrollBar的四次分开查找,通过将mScrollBar缓存到本地变量,四次成员字段查找变成四次本地变量引用,这样更为高效。
同样地,方法参数作为本地变量拥有相同的性能特征。
声明常量为final
考虑在类开头的如下声明:
static int intVal = 42;
static String strVal = "Hello, world!";
编译器产生一个叫<clinit>的类初始化器方法,它在类首次使用时被执行。这个方法将42存到intVal,并为intVal从类文件字符串常量表中抽出一个引用,当这些值在后面被引用到时,它们以字段查找的方式被访问。
我们可以用final关键字改进之:
static final int intVal = 42;
static final String strVal = "Hello, world!";
这个类不再需要一个<clinit>方法,因为常量存到直接由VM处理的类文件静态字段初始化器,代码访问intVal将会直接使用integer值42,而对intVal的访问会用一个相对廉价的“字符串常量”指令来代替一个字段查找。
声明一个方法或类为final并不能直接获得性能上的好处,但它确实能起到某些优化作用。例如,假如编译器知道一个"getter"不能被一个子类重写,它能够内联这个方法调用。
你也可以将本地变量声明为final,然而这并无真正意义上的性能提升。对于要地变量,只有在使代码更清晰(或你不得不,如为了在匿名内部类中使用)时使用final。
小心使用增强的For循环语句
增强的For语句可以用于实现了Iterable接口的Collection,对于这些对象,一个iterator被申请用来进行接口调用hasNext()和next()。对于ArrayList,你最好直接遍历它,但对于其它collections,增强的For循环语句将会等同于显式的迭代用法。
尽管如此,下面的代码展示了增强的For语句的可为人接受的用法:
public class Foo {
int mSplat;
static Foo mArray[] = new Foo[27];
public static void zero() {
int sum = 0;
for (int i = 0; i < mArray.length; i++) {
sum += mArray[i].mSplat;
}
}
public static void one() {
int sum = 0;
Foo[] localArray = mArray;
int len = localArray.length;
for (int i = 0; i < len; i++) {
sum += localArray[i].mSplat;
}
}
public static void two() {
int sum = 0;
for (Foo a: mArray) {
sum += a.mSplat;
}
}
}
zero()在循环中每次迭代获取静态字段两次计算数组长度一次。
one()将所有东西存到本地变量,避免查找。
two()用到了增强的For循环语句,由编译器产生的代码拷贝数组引用和数组长度到本地变量,使之成为一个遍历数组元素的一个很好的选择。它确实在主循环中产生了一个额外的本地载入/存储,使得它比起one()有点慢并且长了4bytes。
总之,增强的For语句对于数组表现良好,但对iterable对象要小心使用,因为有额外的对象创建。
避免Enum类型
Enum非常方便,但不幸的是当考虑到时间和速度时就让人痛苦。例如这个:
public class Foo {
public enum Shrubbery { GROUND, CRAWLING, HANGING }
}
将编译成一个900byte的.class文件,在首次使用是时,类初始化器在代表每个被枚举的值对象上激活<init>方法。每个对象都有其静态字段,并且整个集合就存储在一个数组(一个称为“$values”的静态字段)上,对于仅仅的三个integer来说,那是太多的代码和数据了。
这个:
Shrubbery shrub = Shrubbery.GROUND;
导致了一次静态字段查找。如果“GROUND”是一个static final int编译器将会将它看作是一个常量并内联它。
相反地,当然,是运用enum你可以得到一个更优雅的API和一些编译时的值检查。因此,通常折衷的办法是:为API,你应该千方百计地使用enum,但是当考虑到性能时尝试避免使用它。
利用内部类使用包作用方域
考虑下面的类定义:
public class Foo {
private int mValue;
public void run() {
Inner in = new Inner();
mValue = 27;
in.stuff();
}
private void doStuff(int value) {
System.out.println("Value is " + value);
}
private class Inner {
void stuff() {
Foo.this.doStuff(Foo.this.mValue);
}
}
}
在这里我们要特别指出的是这里定义了一个内部类(Foo$Inner),它可以直接访问外部类的私有方法和私有实例字段,这是合法的,代码的执行的结果是如预期般的“Value is 27”。
问题在于,Foo$Inner是一个完全独立的类,这使得直接访问其私有方法是非法的,为了架起桥梁,编译器会产生如下两个虚拟方法
/*package*/ static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
foo.doStuff(value);
}
当内部类代码需要访问外部类的mValue变量或激活doStuff方法时就会调用这些方法。这就意味着上面的代码清楚表明了你是通过访问器来访问成员字段的,而非直接访问。前面我们讨论过访问器是比直接访问是要慢的,所以这是一个由于某种特定语言方言所导致的隐性性能打击。
我们可以通过声明由内部类访问的字段和方法为具有包作用域而非私有作用域来解决这个问题。这样运行得更快并且移除了额外产生的方法(不幸的是,这也意味着这些字段可以被同包下的其它类所访问,这个是违反了使所有的字段成为私有的标准OO做法的,再一次的,如果你是在设计一个公共的API的话,你可能要慎重地考虑这一优化策略)。
9)避免使用Float类型
在Pentium CPU发布之前,对于游戏作者来说做很多整型计算是很正常的事。有了Pentium之后,浮点计算联合处理器成了内置功能,你的游戏通过交错整型和浮点操作比只有整型计算时运行起来要快得多。在桌面系统上通常的可以自由的使用浮点数。
不幸的是,嵌入式处理器很少具有硬件浮点支持,所以所有的"float"和"double"操作都是在软件上进行。某些基本的浮点操作可能会花费数微秒。
还有,甚至对于整型数,一些芯片支持硬件乘法但缺少硬件除法,在这种情况下,整型除法和取模运算是在软件上执行的——如果你是在设计一个哈希表或做很多数学运算这就是你需要考虑的事情。
为响应灵敏性设计
代码可能通过各种性能测试,但是当用户使用时还是会需要漫长的等待,这些就是那种响应不够灵敏的应用——它们反应迟钝,挂起或冻住周期很长,或者要花很长时间来处理输入。
在Android上,系统通过向用户显示一个称为应用无响应(ANR:Application Not Responding)的对话框来防止在一段时间内响应不够快。用户可以选择让应用继续,但是用户并不会想要每次都来处理这个对话框。因此应把你的应用设计得响应灵敏,使得系统不必显示ANR给用户。
通常地,当不能响应用户输入时系统显示一个ANR。例如,如果一个应用在IO操作(经常是网络访问)上阻塞了,那么主应用线程就会无法处理正在进行的用户输入事件。经过一段时间,系统认为应用已经挂起,向用户显示一个ANR,让用户可以选择关闭。
相同地,如果你的应用花太多的时间在构建详细的内存结构上,又或者在计算游戏的下一个动作上,系统会认为你的应用已经挂起。用上面的技术来保证这些计算是高效的一直都是很重要的,但是即使是最高效的代码运行也是需要花费时间的。
在这两种情况下,解决的办法通常就是创建一个子线程,在这个子线程上做你的大部分工作。这样让你的主线程(驱动用户接口事件循环)保持运行,并让你的代码免于被系统认为已经冻住。因为这样的线程化通常都是在类级别上来完成的,所以你可以认为响应性能问题是一个类问题(与上面描述的方法级别的性能问题)。
这个文档讨论了Android系统是如何决定一个应用没有响应的,并提供了指引来保障你的应用是响应灵敏的。
1)是什么引发了ANR?
在Android系统上,应用的响应灵敏性由Activity Manager和Window Manager system services所监控,当它监测到如下的其中一个条件时,Android就会为特定的应用显示一个ANR:
5秒内对输入事件无响应。
一个BroadCastReceiver在10秒内没有执行完毕。
怎样避免ANR?
考虑到上面对ANR的定义,让我们来研究一下这是为什么会发生以及怎样最好的组织你的应用以避免ANR。
Android应用正常是运行在一个单独的(如main)线程中的,这就意味着在你应用主线程中正在做的需要花很长时间来完成的事情都能够激活ANR对话框。因为你的应用并没有给自己一个机会来处理输入事件或Intent广播。
因此任何运行在主线程中的方法应该做尽可能少的事情。特别地Activitiy在关键生命周期方法中如onCreate()和onResume()应当做尽可能少的设置。潜在地的耗时长的操作(如网络或数据库操作,或高耗费数学计算如改变位图大小)应该在子线程里面完成(或以数据库操作为例,可以通过异步请求)。尽管如此,这并不是说当等待子线程完成的过程中你的主线程必须被阻塞——你不必调用Thread.wait()或Thread.sleep(),恰恰相反,你的主线程应该为子线程提供一个Handler,以便子线程完成时可以提交回给主线程。以这种方式来设计你的应用,将会允许你的主线程一直可以响应输入,以避免由5秒钟的输入事件超时导致的ANR对话。这些做法同样应该被其它任何显示UI的线程所效仿,因为它们属于同样类型的超时。
IntentReciever执行时间的特定限制限制了它们应该做什么:在后台执行的一些琐碎的工作如保存设置或注册通知。至于其它在主线程里被调用的方法,在BroadcastReceiver中,应用应该避免潜在的长耗时操作或计算,而是应该用子线程来完成密集任务(因为BroadcastReceiver的生命周期是短暂的)。对Intent broadcast作出响应,你的应用应该启动一个Service来执行长耗时的动作。同样,你也应该避免从Intent Receiver中启动Activity,因为它会产生一个新的屏,偷走任何用户正在运行的应用的焦点。对Intent broadcast作出的响应,假如你的应用需要向用户显示什么东西,应该用Notification Manager来完成。
增强响应灵敏性
通常,在一个应用中,100到200微秒是一个让用户感觉到阻滞的阈值,因此这里有些小技巧让你用来使你的应用看起来响应更灵敏。
如果你的应用正在后台对用户输入作出响应,显示正在进行的进度(ProgressBar和ProgressDialog对此很有用)。
特别是对于游戏,在子线程中做移动的计算。
如果你的应用有一个耗时的初始化过程,考虑用闪屏或尽可能快地渲染主界面并异步地填充信息。在这两种情况下你都应该表明进度正在进行,以免用户觉得你的应用被冻住了。
为无缝设计:
即使你的应用是快速且响应灵敏的,一些设计仍然能句对用户造成问题——因为与其它应用未计划的交互或者对话,意外的数据丢失,无意识的阻塞等等。为了避免这些问题,有助于理解你的应用运行的环境和可以影响你的应用的系统交互。总之,你应该倔尽全力地开发一个与系统和其它应用无缝交互的应用。
一个常见的无缝问题就是一个应用的后台进程(如service或broadcast receiver)对某事件作出响应而弹出对话框,这看起来仿佛并无大碍,特别是当你在模拟器上单独地构建和测试你的应用时。然而,当你的应用在真正的设备上运行,后台线程显示对话框时,你的应用当时可能没有获得用户焦点。这就会出现你的应用会在活动的应用后面显示对话框,或者从当前应用中获得焦点并显示对话框的情况,而管论当时用户正在做什么(如正在打电话等)。那样的行为可能对你的应用或用户不起作用。
为了避免这些问题,你的应用应该利用适当的系统资源——Notification类,来通知用户。利用通知,你的应用可以通过在状态条上显示一个图标来通知用户事件已经发生,而非获得焦点和打断用户。
另外一个无缝问题的例子就是,Activity由于未能正确实现onPause()及其它生命周期方法而无意中丢失了状态或用户数据。又或者,你的应用要暴露供其它应用使用的数据,你应该通过ContentProvider来实现,而非通过一个全世界都可读的原始文件或数据库。
这些例子的共同特点就是,它们都是关于如何跟系统和其它应用协作得更好,Android系统的设计就是将所有的应用看作是一个松散耦合的组件联邦,不是一堆墨盒代码。这就使你作为一个开发者可以将整个系统视为只是一个更大一点的组件联邦。这样有得于你将应用与其它应用清晰和无缝的集成,所以作为回报,你应该更好的设计你的代码。
这个文档讨论了常见的无缝集成问题和怎样去避免它们。它包含了如下主题:
别丢弃数据
一定要记住Android是一个移动平台。看起来很显然,但是记住这个事实很重要,就是任何Activity(如"正在打进来的电话"应用)在任何时候都有可能弹出来覆盖你的Activity,这会激活onSaveInstanceState()和onPause()方法,并导致你的应用被杀死。如果当其它Activity出现时,用户正好在你的应用上编辑数据,你的应用被杀死的同时那些数据也很可能会丢失。当然了,除非你先保存了进行中的工作。“Android方式”是这么做的:能接收和编辑用户输入的应用需要重写onSaveInstanceState()方法并以恰当的方式保存它们的状态,当用户重新访问应用时,就能重新获得数据。
一个运用这个行为经典的例子就是邮件应用,当用户正在撰写邮件时另一人Activity开始了,应用应该将正在编辑的邮件保存为草稿。
不要暴露原始数据
如果你不想穿着内衣在大街上散步,同样你的数据也不应如此。尽管可能暴露某些应用可以方便其它应用读取,但这通常不是最好的主意。暴露原始数据要求其它的应用能够理解你的数据格式;如果你改变了格式,你将会破坏其它没有同时更新的应用。
“Android方式”就是创建一个ContentProvider通过一个清晰的、深思熟虑的、可维护的API来暴露你的数据给其它应用。使用ContentProvider就像一个Java接口来分离和组件化两段紧密耦合的代码,这就意味着你能够修改你数据的内部格式而不用修改由ContentProvider暴露的接口,这样就不会影响其它应用。
别打断用户
如果能确定一个用户是带有目的性的运行一个应用才是安全的。那就是为什么你除非是直接响应当前活动的用户输入,不然就要避免产生Activity的原因。
那就是说,不要从后台运行的BroadcastReceiver和Service中调用startActivity()。如果这样做将会打断任何正在运行的应用,并使用户恼怒。甚至你的Activity可能成为一个“击键强盗”接收一些用户正在为上一个Activity提供的输入,视乎你的应用所做的,这是这可能是个坏消息。
取代直接从后台直接产生Activity UIs,你应该用NotificationManager来设置通知,这将会出现在状态条上,当用户空闲时可以点击它们来看你的应用向他们显示了什么。
(注意,当你的Activity已经在前台时所有这些都没适用:这时,对于输入的响应,用户期望看到你的下一个Activity。)
有太多事要做?在一个线程里做
如果你的应用需要做一个代价高昂或长耗时的计算,你可能要将它移到一个线程里。这个将会防止显示“Application Not Responding”对话框给用户,最终导致你的应用完全终止。
默认地,在一个Activity中的代码和其所有的View运行在同一个线程上。这与处理UI事件的线程是同一个。例如,当一个键被按下时,一个key-down事件被添加到Activity主线程的队列。事件处理系统需要很快地让这个事件出列并处理这个事件。不然,系统数秒后将会认为应用已经挂起并替用户杀死这个应用。
如果你有长耗时的代码,让它在你的Activity上内联运行将会在使它运行在事件处理线程上,这很大程度上阻塞了了事件处理句柄。这会延缓输入处理并导致ANR对话框。为了避免之,将你的计算移到一个线程中。在为响应灵敏性设计中已经讨论了如何做。
5)不要过载一个单一的Activity屏
任何值得使用的应用都可能会有几个不同的屏幕。当设计你的UI屏幕时,请一定要运用多个Activity对象实例。依赖于你的开发背景,你可能像解释某些类似Java Applet的东西一样来解释一个Activity,Activity是你应用的入口点。然而,那并不是准确:一个Applet的子类是一个Java Applet的单一入口点,而一个Activity应该被看作一个潜在的进入你的应用的多个入口点。在你的”main”Activity和任何其它你可能有的Activity之间的唯一不同就是,那“miain”Activity碰巧是那个唯一在你的AndroidManifest.xml文件中对“android.intent.action.MAIN”动作有兴趣的一个而已。
所以,当设计你的应用时,把你的应用看成一个Activity对象的联邦。从长远来看,这会使得你的代码更具可维护性。
6)扩展系统主题
当提到用户接口的观感时,协调是很重要的。用户为那些与他们所期望的用户接口相反的应用所震动。当设计你的UI时,你应当尽量避免出现太多你自己主题,相反地,用同一个主题。你可以重写或扩展那部分你必须的主题,但是至少你是基于与其他应用相同的UI基础上的。详细可以参阅“应用风格和主题”部分。
7)设计你的UI可以与多屏分辨率一起工作
不同的的基于Android的设备可能会支持不同的分辨率。甚至一些可能支持随时更改分辨率。保证你的布局和图片足够灵活对于在不同设备屏幕上正常显示是非常重要的。
幸运的是,这是很容易办到的。简单讲,你需要做的就是为你的关键分辨率提供不同版本的作品,然后设计你的布局适应各种不同的维度。(例如,避免作用硬编码位置而用相对布局。)如果那样做的话,剩下的系统会处理,你的应用在任何设备上看起来都很棒。
8)假定网络是很慢的
Android设备会有多种网络链接选项。所有的都会提供数据访问,虽然有一些会比另一些更快。其中速度最慢的就是GPRS(GSM网络的非3G数据服务)。即便具备3G能力的设备在非3G网络上也会花很多的时间,所以网速低将会是一个长期存在的事实。
那就是为什么你应该针对最小化的网络访问和带宽编写你的应用。你不能假设网络是快速的,所以你应该一直计划它是慢的。如果你的用户碰巧是在一个快速的网络上,那很好——他们的体验只会提升。你要避免相反的情况:应用有时可用,但有时慢得令人沮丧,得看用户是在哪在什么时间,这样的应用可能不会受欢迎。
别假定触摸屏和键盘
Android可能支持多种外观形状。那就是说一些Android设备将会有完整的“QWERTY"键盘,而其它的可能会有40键、12键或其它键盘设置。同样地,一些设备会有触摸屏,但很多会没有。
当构建你的应用时,一定要记住,不要假定特定的键盘布局——当然了,除非你真的喜欢限制你的应用以到它只能在某些设备上运行。
一定要节省设备电池
如果移动设备经常局限于屋内,那就不是很“移动”。移动设备是电池供电的,而我们如果能让电池每充一次的电量使用得更持久一些,每个人都会更开心——特别是用户。其中的两个用电大户就是处理器和音频,那就是为什么你写的应用应尽量做少的工作的同时尽可能频繁地使用网络的原因。
最小化你的应用使用的处理器时间就归结为书写高效的代码。从音频上最小化功耗,要确保优雅地处理错误条件,并仅获取你需要的东西。例如,如果连接网络失败不要一直重试连接网络,如果失败了一次,很可能是用户没有接收信号。如果你立即重试,那么你所做的一切只是在浪费电池能量。
用户是相当聪明的:如果你的程序是高耗电的,你可以相信他们会发觉的。在那一点上,唯一可以确定的是你的程序将不会保持安装非常久。
评论
3 楼
lfkabc
2011-03-08
做人要厚道,不能自己有收获了也不动声色的这么走了。顶起!
2 楼
zhangjinpeng
2009-09-14
很棒 受益匪浅
1 楼
yzhong_sa
2009-04-27
正是我香了解的好东西。。
相关推荐
风光储直流微电网Simulink仿真模型:光伏发电、风力发电与混合储能系统的协同运作及并网逆变器VSR的研究,风光储直流微电网Simulink仿真模型:MPPT控制、混合储能系统、VSR并网逆变器的设计与实现,风光储、风光储并网直流微电网simulink仿真模型。 系统由光伏发电系统、风力发电系统、混合储能系统(可单独储能系统)、逆变器VSR?大电网构成。 光伏系统采用扰动观察法实现mppt控制,经过boost电路并入母线; 风机采用最佳叶尖速比实现mppt控制,风力发电系统中pmsg采用零d轴控制实现功率输出,通过三相电压型pwm变器整流并入母线; 混合储能由蓄电池和超级电容构成,通过双向DCDC变器并入母线,并采用低通滤波器实现功率分配,超级电容响应高频功率分量,蓄电池响应低频功率分量,有限抑制系统中功率波动,且符合储能的各自特性。 并网逆变器VSR采用PQ控制实现功率入网。 ,风光储; 直流微电网; simulink仿真模型; 光伏发电系统; 最佳叶尖速比控制; MPPT控制; Boost电路; 三相电压型PWM变换器;
以下是针对初学者的 **51单片机入门教程**,内容涵盖基础概念、开发环境搭建、编程实践及常见应用示例,帮助你快速上手。
【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课_pgj
【毕业设计】[零食商贩]-基于vue全家桶+koa2+sequelize+mysql搭建的移动商城应用
电动汽车充电背景下的微电网谐波抑制策略与风力发电系统仿真研究,电动汽车充电微电网的谐波抑制策略与风力发电系统仿真研究,基于电动汽车充电的微电网谐波抑制策略研究,包括电动汽车充电负 载模型,风电模型,光伏发现系统,储能系统,以及谐波处理模块 风力发电系统仿真 ,电动汽车充电负载模型; 风电模型; 光伏发现系统; 储能系统; 谐波处理模块; 风力发电系统仿真,电动汽车充电微电网的谐波抑制策略研究:整合负载模型、风电模型与光伏储能系统
Vscode部署本地Deepseek的continue插件windows版本
内容概要:本文详细介绍了滤波器的两个关键参数——截止频率(F0)和品质因素(Q),并探讨了不同类型的滤波器(包括低通、高通、带通和带阻滤波器)的设计方法及其特性。文章首先明确了F0和Q的基本概念及其在滤波器性能中的作用,接着通过数学推导和图形展示的方式,解释了不同Q值对滤波器频率响应的影响。文中特别指出,通过调整Q值可以控制滤波器的峰谷效果和滚降速度,进而优化系统的滤波性能。此外,还讨论了不同类型滤波器的具体应用场景,如低通滤波器适用于消除高频噪声,高通滤波器用于去除直流分量和低频干扰,而带通滤波器和带阻滤波器分别用于选取特定频段信号和排除不需要的频段。最后,通过对具体案例的解析,帮助读者更好地理解和应用相关理论。 适合人群:电子工程及相关领域的技术人员、研究人员以及高校学生,特别是那些需要深入了解滤波器设计原理的人群。 使用场景及目标:适用于从事模拟电路设计的专业人士,尤其是希望掌握滤波器设计细节和技术的应用场合。目标是让读者能够灵活运用Q值和F0来优化滤波器设计,提升系统的信噪比和选择性,确保信号的纯净性和完整性。
内容概要:本文主要讲述了利用QUARTUSⅡ进行电子设计自动化的具体步骤和实例操作,详细介绍了如何利用EDA技术在QUARTUSⅡ环境中设计并模拟下降沿D触发器的工作过程,重点探讨了系统规格设计、功能描述、设计处理、器件编译和测试四个步骤及相关的设计验证流程,如功能仿真、逻辑综合及时序仿真等内容,并通过具体的操作指南展示了电路设计的实际操作方法。此外还强调了QUARTUSⅡ作为一款集成了多种功能的综合平台的优势及其对于提高工作效率的重要性。 适用人群:电子工程、自动化等相关专业的学生或者工程师,尤其适用于初次接触EDA技术和QuartusⅡ的用户。 使用场景及目标:旨在帮助用户理解和掌握使用QUARTUSⅡ这一先进的EDA工具软件进行从概念设计到最后成品制作整个电路设计过程的方法和技巧。目标是在实际工作中能够熟练运用QUARTUSⅡ完成各类复杂电子系统的高效设计。 其他说明:文中通过具体的案例让读者更直观理解EDA设计理念和技术特点的同时也为进一步探索EDA领域的前沿课题打下了良好基础。此外它还提到了未来可能的发展方向,比如EDA工具的功能增强趋势等。
Simulink建模下的光储系统与IEEE33节点配电网的协同并网运行:光照强度变化下的储能系统优化策略与输出性能分析,Simulink模型下的光伏微网系统:光储协同,实现380v电压等级下的恒定功率并网与平抑波动,Simulink含光伏的IEEE33节点配电网模型 微网,光储系统并网运行 光照强度发生改变时,储能可以有效配合光伏进行恒定功率并网,平抑波动,实现削峰填谷。 总的输出有功为270kw(图23) 无功为0 检验可以并网到电压等级为380v的电网上 逆变侧输出电压电流稳定(图4) ,Simulink; 含光伏; 配电网模型; 微网; 光储系统; 储能配合; 恒定功率并网; 电压等级; 逆变侧输出。,Simulink光伏微网模型:光储协同并网运行,实现功率稳定输出
基于Andres ELeon新法的双馈风机次同步振荡抑制策略:附加阻尼控制(SDC)的实践与应用,双馈风机次同步振荡的抑制策略研究:基于转子侧附加阻尼控制(SDC)的应用与效能分析,双馈风机次同步振荡抑制策略(一) 含 基于转子侧附加阻尼控制(SDC)的双馈风机次同步振荡抑制,不懂就问, 附加阻尼控制 (SDC)被添加到 RSC 内部控制器的q轴输出中。 这种方法是由Andres ELeon在2016年提出的。 该方法由增益、超前滞后补偿器和带通滤波器组成。 采用实测的有功功率作为输入信号。 有关更多信息,你可以阅读 Andres ELeon 的lunwen。 附lunwen ,关键词:双馈风机、次同步振荡、抑制策略;转子侧附加阻尼控制(SDC);RSC内部控制器;Andres ELeon;增益;超前滞后补偿器;带通滤波器;实测有功功率。,双馈风机次同步振荡抑制技术:基于SDC与RSCq轴控制的策略研究
springboot疫情防控期间某村外出务工人员信息管理系统--
高效光伏并网发电系统MATLAB Simulink仿真设计与MPPT技术应用及PI调节闭环控制,光伏并网发电系统MATLAB Simulink仿真设计:涵盖电池、BOOST电路、逆变电路及MPPT技术效率提升,光伏并网发电系统MATLAB Simulink仿真设计。 该仿真包括电池,BOOST升压电路,单相全桥逆变电路,电压电流双闭环控制部分;应用MPPT技术,提高光伏发电的利用效率。 采用PI调节方式进行闭环控制,SPWM调制,采用定步长扰动观测法,对最大功率点进行跟踪,可以很好的提高发电效率和实现并网要求。 ,光伏并网发电系统; MATLAB Simulink仿真设计; 电池; BOOST升压电路; 单相全桥逆变电路; 电压电流双闭环控制; MPPT技术; PI调节方式; SPWM调制; 定步长扰动观测法。,光伏并网发电系统Simulink仿真设计:高效MPPT与PI调节控制策略
PFC 6.0高效循环加载系统:支持半正弦、半余弦及多级变荷载功能,PFC 6.0循环加载代码:支持半正弦、半余弦及多级变荷载的强大功能,PFC6.0循环加载代码,支持半正弦,半余弦函数加载,中间变荷载等。 多级加载 ,PFC6.0; 循环加载代码; 半正弦/半余弦函数加载; 中间变荷载; 多级加载,PFC6.0多级半正弦半余弦循环加载系统
某站1K的校园跑腿小程序 多校园版二手市场校园圈子失物招领 食堂/快递代拿代买跑腿 多校版本,多模块,适合跑腿,外卖,表白,二手,快递等校园服务 需要自己准备好后台的服务器,已认证的小程序,备案的域名!
【Python毕设】根据你提供的课程代码,自动排出可行课表,适用于西工大选课
COMSOL锂枝晶模型:五合一的相场、浓度场与电场模拟研究,涵盖单枝晶定向生长、多枝晶生长及无序生长等多元现象的探索,COMSOL锂枝晶模型深度解析:五合一技术揭示单枝晶至雪花枝晶的生长机制与物理场影响,comsol锂枝晶模型 五合一 单枝晶定向生长、多枝晶定向生长、多枝晶随机生长、无序生长随机形核以及雪花枝晶,包含相场、浓度场和电场三种物理场(雪花枝晶除外),其中单枝晶定向生长另外包含对应的参考文献。 ,comsol锂枝晶模型; 五合一模型; 单枝晶定向生长; 多枝晶定向生长; 多枝晶随机生长; 无序生长随机形核; 雪花枝晶; 相场、浓度场、电场物理场; 参考文献,COMSOL锂枝晶模型:多场景定向生长与相场电场分析
嵌入式大学生 点阵代码
那个有delphi12 tedgebrowser 使用的dll
基于DQN算法的微网储能优化调度与能量管理:深度强化学习的应用与实践,基于DQN算法的微网储能优化调度与能量管理:深度强化学习的应用与实践,基于DQN算法的微网储能运行优化与能量管理 关键词:微网 优化调度 储能优化 深度强化学习 DQN 编程语言:python 参考文献:《Explainable AI Deep Reinforcement Learning Agents for Residential Demand Side Cost Savings in Smart Grids》 内容简介: 受深层强化学习(RL)最新进展的激励,我们开发了一个RL代理来管理家庭中存储设备的操作,旨在最大限度地节省需求侧的成本。 所提出的技术是数据驱动的,并且RL代理从头开始学习如何在可变费率结构下有效地使用能量存储设备,即收缩“黑匣子”的概念,其中代理所学的技术被忽略。 我们解释了RL-agent的学习过程,以及基于存储设备容量的策略。 ,微网; 优化调度; 储能优化; 深度强化学习; DQN; 家庭存储设备; 需求侧成本节省; 智能电网; RL代理; 能量存储设备。,基于DQN算法的微网储
内容概要:该文档为FM17580的原理图设计文件,重点介绍了这款非接触式IC卡读写芯片的电路设计细节。文档详细列出了各个元器件及其连接方式、引脚分配及具体值设定。特别值得注意的是,为了确保性能和可靠性,在PCB布局时强调了GND线需要尽量以最短路径连回FM175xx芯片的TVSS引脚附近,并且靠近电源输入端(TVDD)。同时明确了FM17580只兼容SPI通讯协议,其他如IIC或UART选项则不在支持范围内。此外还提供了关于降低能耗的选择——移除不必要的ADC检测电路,这对于一些特定应用场景非常有用。 适合人群:具备硬件开发经验和RFID/NFC领域基础知识的技术人员或研究人员。 使用场景及目标:适用于需要详细了解FM17580内部结构和技术特性的项目团队;旨在帮助工程师们快速上手搭建实验平台并测试FM17580的功能特性。主要目的是为实际应用开发提供技术支持和参考。 其他说明:文档最后附带了一些附加信息,包括设计师名字、公司名称以及审查流程的相关内容,但具体内容并未公开。此外还提到该文档是针对FM17580评估板(即FM17580Demo)的设计图纸。文中出现多次类似表格可能是不同版本之间的对比或者记录修改历史的部分内容。