Robotium原则的实施源代码分析
从前面的章节《Robotium源代码分析之Instrumentation进阶》中我们了解到了Robotium所基于的Instrumentation的一些进阶基础。比方它注入事件的原理等,但Robotium作为一个測试框架。其功能远不止于仅仅是方便我们注入事件,其应该还包括其它高级的功能,參照我们前面其它框架如MonkeyRunner,UiAutomator和Appium的源代码分析,我们知道一个移动平台自己主动化測试框架的基本功能除了事件注入外起码还应该有控件获取的功能。
所以,这篇文章我们主要是环绕Robotium的这几个功能做阐述。
Robotium作为一个自己主动化測试框架。做一个自己主动化測试库。其所要走的事情就是要封装好获取控件和操作控件的各种方法,而由于Robotium编写的脚本又是和目标測试应用执行在同一进程中的(參考《Robotium源代码分析之Instrumentation进阶》第一节),所以就会让事情更简单了。比方获取一个TextView的文本,我们就能够直接在測试脚本这个子线程调用目标控件的个对应方法来获得文本,假设要设置控件的文本属性的话,我们也仅仅是须要提供一个runnable让主线程UiThread去调用控件的设置Text属性的方法就完毕了。
当然,假设你是想要模拟用户键盘输入来设置文本。那么就须要操作事件的另外一个方式,去注入事件发送键盘事件来完毕了。也就是说。你的脚本既能够运用作为UiThread子线程的优势来直接操控控件属性,也能够通过注入事件的方式来模拟用户层面对控件的操作。
1.通过注入事件操作控件
Method |
Description |
Comment |
Key Events |
||
sendKeySync |
发送一个键盘事件。注意同一时间仅仅有一个action,或者是按下,或者是弹起,全部以下其它key相关的事件注入都是以这种方法为基础的 |
|
sendKeyDownUpSync |
基于sendKeySync发送一个按键的按下和弹起两个事件 |
|
sendCharacterSync |
发送键盘上的一个字符。完整的过程包含一个按下和弹起事件 |
|
sendStringSync |
往应用发送一串字符串 |
|
Tackball Event |
||
sendTrackballEventSync |
发送轨迹球事件。 个人没实用过,应该是像黑莓的那种轨迹球吧 |
|
Pointer Event |
||
sendPointerSync |
发送点击事件 |
/* */ public void clickOnText(String text)
/* */ {
/* 1095 */ this.clicker.clickOnText(text, false, 1, true, 0);
/* */ }
直接跳转到Clicker类的clickOnText方法,各个參数的意义也一目了然:
/* */ public void clickOnText(String regex, boolean longClick, int match, boolean scroll, int time)
/* */ {
/* 427 */ TextView textToClick = this.waiter.waitForText(regex, match, Timeout.getSmallTimeout(), scroll, true, false);
/* */
/* 429 */ if (textToClick != null) {
/* 430 */ clickOnScreen(textToClick, longClick, time);
/* */
/* */
/* */
/* */ }
/* 435 */ else if (match > 1) {
/* 436 */ Assert.fail(match + " matches of text string: '" + regex + "' are not found!");
/* */ }
/* */ else
/* */ {
/* 440 */ ArrayList<TextView> allTextViews = RobotiumUtils.removeInvisibleViews(this.viewFetcher.getCurrentViews(TextView.class, true));
/* 441 */ allTextViews.addAll(this.webUtils.getTextViewsFromWebView());
/* */
/* 443 */ for (TextView textView : allTextViews) {
/* 444 */ Log.d("Robotium", "'" + regex + "' not found. Have found: '" + textView.getText() + "'");
/* */ }
/* 446 */ allTextViews = null;
/* 447 */ Assert.fail("Text string: '" + regex + "' is not found!");
/* */ }
/* */ }
第一步当然是先获得控件了。具体怎么获得往后章节会具体描写叙述。如今重点看430行clickOnScreen方法,注意參数longCilck代表用户想注入的点击方法是长按还是短按:
/* */ public void clickOnScreen(View view, boolean longClick, int time)
/* */ {
/* 182 */ if (view == null) {
/* 183 */ Assert.fail("View is null and can therefore not be clicked!");
/* */ }
/* 185 */ float[] xyToClick = getClickCoordinates(view);
/* 186 */ float x = xyToClick[0];
/* 187 */ float y = xyToClick[1];
/* */
/* 189 */ if ((x == 0.0F) || (y == 0.0F)) {
/* 190 */ this.sleeper.sleepMini();
/* */ try {
/* 192 */ view = this.viewFetcher.getIdenticalView(view);
/* */ }
/* */ catch (Exception ignored) {}
/* 195 */ if (view != null) {
/* 196 */ xyToClick = getClickCoordinates(view);
/* 197 */ x = xyToClick[0];
/* 198 */ y = xyToClick[1];
/* */ }
/* */ }
/* */
/* 202 */ if (longClick) {
/* 203 */ clickLongOnScreen(x, y, time, view);
/* */ } else {
/* 205 */ clickOnScreen(x, y, view);
/* */ }
/* */ }
先依据控件获得控件点击坐标:事实上就是控件的中心点的绝对坐标值了,该转换在getClickCoordinates方法进行。没有什么特别的地方,就不跳进去分析了。
然后依据是否是长按考虑调用clickLongOnScreen或者clickOnScreen方法。我们这里挑clicOnScreen往下展开:
/* */ public void clickOnScreen(float x, float y, View view)
/* */ {
/* 77 */ boolean successfull = false;
/* 78 */ int retry = 0;
/* 79 */ SecurityException ex = null;
/* */
/* 81 */ while ((!successfull) && (retry < 10)) {
/* 82 */ long downTime = SystemClock.uptimeMillis();
/* 83 */ long eventTime = SystemClock.uptimeMillis();
/* 84 */ MotionEvent event = MotionEvent.obtain(downTime, eventTime, 0, x, y, 0);
/* */
/* 86 */ MotionEvent event2 = MotionEvent.obtain(downTime, eventTime, 1, x, y, 0);
/* */ try
/* */ {
/* 89 */ this.inst.sendPointerSync(event);
/* 90 */ this.inst.sendPointerSync(event2);
/* 91 */ successfull = true;
/* */ } catch (SecurityException e) {
/* 93 */ ex = e;
/* 94 */ this.dialogUtils.hideSoftKeyboard(null, false, true);
/* 95 */ this.sleeper.sleep(200);
/* 96 */ retry++;
/* 97 */ View identicalView = this.viewFetcher.getIdenticalView(view);
/* 98 */ if (identicalView != null) {
/* 99 */ float[] xyToClick = getClickCoordinates(identicalView);
/* 100 */ x = xyToClick[0];
/* 101 */ y = xyToClick[1];
/* */ }
/* */ }
/* */ }
/* 105 */ if (!successfull) {
/* 106 */ Assert.fail("Click at (" + x + ", " + y + ") can not be completed! (" + (ex != null ? ex.getClass().getName() + ": " + ex.getMessage() : "null") + ")");
/* */ }
/* */ }
所做的事情就是依据点击坐标组建两个分别按下和弹起的事件,然后在89和90行分别调用Instrumentation的sendPointerSync方法触发InputManager注入这两个按下和弹起的事件就完了。
有了曾经文章的基础,这些代码分析起来就非常流畅了,不然到了这里还要去跟大家解析各种事件注入的情况就会显得非常冗长乏味了。
2. 通过runOnMainSync操作控件
/* */ public void enterText(EditText editText, String text)
/* */ {
/* 1748 */ editText = (EditText)this.waiter.waitForView(editText, Timeout.getSmallTimeout());
/* 1749 */ this.textEnterer.setEditText(editText, text);
/* */ }
首先还是如上获得控件,然后调用TextEnterer类的setEditText方法:
/* */ public void setEditText(final EditText editText, final String text)
/* */ {
/* 45 */ if (editText != null) {
/* 46 */ final String previousText = editText.getText().toString();
/* */
/* 48 */ this.inst.runOnMainSync(new Runnable()
/* */ {
/* */ public void run()
/* */ {
/* 52 */ editText.setInputType(0);
/* 53 */ editText.performClick();
/* 54 */ TextEnterer.this.dialogUtils.hideSoftKeyboard(editText, false, false);
/* 55 */ if (text.equals("")) {
/* 56 */ editText.setText(text);
/* */ } else {
/* 58 */ editText.setText(previousText + text);
/* 59 */ editText.setCursorVisible(false);
/* */ }
/* */ }
/* */ });
/* */ }
/* */ }
毫无意外的这种方法在48行開始调用的就是runOnMainSync的方法来给主线程运行,所做的事情大致例如以下:
- 设置控件的输入类型,比方假设是password输入框的话就不要显示输入过的字符,以*号取代等
- 模拟点击设置焦点到目标输入控件
- 由于是直接对控件设置,所以不须要通过键盘驱动,所以也就没有必要显示键盘出来了
- 直接设置控件的Text
3.通过runOnUiThread操作控件
/* */ public void setProgressBar(ProgressBar progressBar, int progress)
/* */ {
/* 1691 */ progressBar = (ProgressBar)this.waiter.waitForView(progressBar, Timeout.getSmallTimeout());
/* 1692 */ this.setter.setProgressBar(progressBar, progress);
/* */
直接跳入到Setter类的setProgressBar方法:
/* */ public void setProgressBar(final ProgressBar progressBar, final int progress)
/* */ {
/* 101 */ if (progressBar != null)
/* */ {
/* 103 */ this.activityUtils.getCurrentActivity(false).runOnUiThread(new Runnable()
/* */ {
/* */ public void run()
/* */ {
/* */ try {
/* 108 */ progressBar.setProgress(progress);
/* */ }
/* */ catch (Exception ignored) {}
/* */ }
/* */ });
/* */ }
/* */ }
103行能够看到是使用了runOnUiThread的方法在主线程直接提交改动控件属性的消息然后放到UiThread MainLooper来排队改动进度条属性的。
- 通过Instrumentation调用InputManager注入事件
- 通过runOnMainSync在主线程同步直接改动控件属性
- 通过runOnUiThread在主线程异步改动控件属性
4. Robotium跨应用
5. 通过WindowManager获取控件
本来打算像往常一样依照自己的逻辑又一次分析Robotium获取控件的原理的,但发现网上已有先驱撰文《Robotium
5.0.1 源代码解析之控件搜索》做了对应的分析了。且近来身体欠佳,牙痛,头痛,发烧干嘛来袭的,所以就干脆直接引用了,本人认为写的还ok。大家阅读应该不会存在问题的了。这里就先谢过作者了。
事实上获取一个控件的方法无非是先获取得ui界面上得根控件。然后从根控件開始搜索以下指定的控件。在UiAutomator中我们用AccessibilityNodeInfo来封装一个view,而在Robotium中我们还是使用view和ViewGroup本身而已。注意这里View是描写叙述一个控件的最小单位,而ViewGroup是view的容器,比方最上层的DecorView就是包括了界面全部控件的容器。所以获得这个容器就能获得全部的子控件。
再次说明。以下分析是摘录自网上的,本人仅仅做了排版调整。
众所周知,Robotium是基于Android的单元測试框架Instrumentation。而robotium对于Instrumentation封装的比較强的地方便是控件搜索。这部分的源代码主要位于ViewFetcher.java中。
5.1 mViews的获取
要先搜索控件,必须先得到Activity的rootView。在Android中。对于一般的Activity或其对话框,其rootView叫做DecorView。事实上就是Activity和Dialog外面的那层框(关于Activity或dialog的层次能够用HierarchyViewer来查看)。
尽管通过Activity类的getWindow().getDecorView能够获取到Activity自身的DecorView,可是无法获取到对话框的,因此Robotium中界面控件是从WindowManagerGlobal(或WindowManagerImpl)中的mViews获取到的。当然mViews中不但包括DecorView。还包括同进程内的全部界面的根节(如悬浮框的根节点)。
mView的值的获取过程主要例如以下:
1) 确定mViews所在类:android 4.2之前,获取类为android.view.WindowManagerImpl,4.2及之后,获取类为WindowManagerGlobal
String windowManagerClassName;
if (android.os.Build.VERSION.SDK_INT >= 17) {
windowManagerClassName = "android.view.WindowManagerGlobal";
} else {
windowManagerClassName = "android.view.WindowManagerImpl";
}
windowManager = Class.forName(windowManagerClassName) <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
2). 获得类的实例:此类是个单例类。有直接的静态变量能够获取到事实上例, 4.2及之后的版本号其变量名为sDefaultWindowManager,3.2至4.1,其变量名为sWindowManager,3.2之前,其变量名为mWindowManager。
/**
* Sets the window manager string.
*/
private void setWindowManagerString(){ if (android.os.Build.VERSION.SDK_INT >= 17) {
windowManagerString = "sDefaultWindowManager";
} else if(android.os.Build.VERSION.SDK_INT >= 13) {
windowManagerString = "sWindowManager";
} else {
windowManagerString = "mWindowManager";
}
}
3). 获取mViews变量的值了,从4.4開始类型变为ArrayList<View>,之前为View[]
viewsField = windowManager.getDeclaredField("mViews");
instanceField = windowManager.getDeclaredField(windowManagerString);
viewsField.setAccessible(true);
instanceField.setAccessible(true);
Object instance = instanceField.get(null);
View[] result;
if (android.os.Build.VERSION.SDK_INT >= 19) {
result = ((ArrayList<View>) viewsField.get(instance)).toArray(new View[0]);
} else {
result = (View[]) viewsField.get(instance);
}
5.2 mViews的过滤
mViews中会包括三种类型的View:
1) 当前显示的以及没有显示的Activity的DecorView
2) 当前对话框的DecorView
3) 悬浮框View等其它不属于DecorView的独立View
在搜索控件时,显然须要在最上层界面中搜索。所以搜索范围为:
最上层的Activity/Dialog + 悬浮框
对于悬浮框,robotium中的处理是找出mViews中不属于DecorView类的View,并将其全部子控件引入。
private final View[] getNonDecorViews(View[] views) {
View[] decorViews = null; if(views != null) {
decorViews = new View[views.length]; int i = 0;
View view; for (int j = 0; j < views.length; j++) {
view = views[j];
if (view != null && !(view.getClass().getName()
.equals("com.android.internal.policy.impl.PhoneWindow$DecorView"))) {
decorViews[i] = view;
i++;
}
}
}
return decorViews;
}
对于Activity/Dialog的筛选,Robotium採取对照DrawingTime的方法选出最后绘制的DecorView,其即为最上层Activity/Dialog的DecorView:
/**
* Returns the most recent view container
*
* @param views the views to check
* @return the most recent view container
*/ private final View getRecentContainer(View[] views) {
View container = null;
long drawingTime = 0;
View view; for(int i = 0; i < views.length; i++){
view = views[i];
if (view != null && view.isShown() && view.hasWindowFocus() && view.getDrawingTime() > drawingTime) {
container = view;
drawingTime = view.getDrawingTime();
}
}
return container;
}
5.3 控件过滤&控件列表生成
得到悬浮框的根节点和最上层的DecorView后。robotium会将全部View统一加入到一个ArrayList中生成控件列表。加入方法本身非常easy,就是一个简单的递归。但须要注意的是此处有一个onlySufficientlyVisible的推断。
onlySufficientlyVisible是ViewFetcher中最常见的一个变量。其表示是否过滤掉显示不全然的控件,即onlySufficientlyVisible为true时表示仅仅在显示全然的控件中搜索目标,为false时表示在全部控件中搜索目标。
详细代码为以下的addChildren函数:
private void addChildren(ArrayList<View> views, ViewGroup viewGroup, boolean onlySufficientlyVisible) {
if(viewGroup != null){
for (int i = 0; i < viewGroup.getChildCount(); i++) {
final View child = viewGroup.getChildAt(i); if(onlySufficientlyVisible && isViewSufficientlyShown(child))
views.add(child); else if(!onlySufficientlyVisible)
views.add(child); if (child instanceof ViewGroup) {
addChildren(views, (ViewGroup) child, onlySufficientlyVisible);
}
}
}
}
从上面的代码能够看出,当onlySufficientlyVisible为true时,robotium会对控件的可见不可见进行检查。只是这里的可见不可见不是指Visible或Invisible(Robotium过滤Invisible控件的方法是RobotiumUtils.removeInvisibleViews,原理是利用view.isShown()方法),而是指因为界面滚动而导致的没有显示或显示不全然。继续看Robotium对SufficientlyVisible是怎么推断的:
public final boolean isViewSufficientlyShown(View view){
final int[] xyView = new int[2];
final int[] xyParent = new int[2]; if(view == null)
return false; final float viewHeight = view.getHeight();
final View parent = getScrollOrListParent(view);
view.getLocationOnScreen(xyView); if(parent == null){
xyParent[1] = 0;
}
else{
parent.getLocationOnScreen(xyParent);
} if(xyView[1] + (viewHeight/2.0f) > getScrollListWindowHeight(view))
return false; else if(xyView[1] + (viewHeight/2.0f) < xyParent[1])
return false; return true;
}
getScrollListWindowHeight函数用于获取控件所属的ListView或ScrollView最以下边界的Y坐标。因此
xyView[1] + (viewHeight/2.0f) > getScrollListWindowHeight(view)
(xyView[1] + (viewHeight/2.0f) < xyParent[1]
则表示控件有超过一半的面积被隐藏在了父控件的上方,这两种情况都被Robotium推断为不满足SufficientlyVisible的(只是好像没有推断横向的?)。
依据onlySufficientlyVisible过滤掉对应控件后。robotium便完毕了控件列表的生成工作。之后的搜索就可直接在列表中进行查找了。
有的时候要搜索指定类型的控件。能够依照类型对控件列表进行再一次的过滤。ViewFetcher中的代码例如以下:
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, View parent) {
ArrayList<T> filteredViews = new ArrayList<T>();
List<View> allViews = getViews(parent, true);
for(View view : allViews){
if (view != null && classToFilterBy.isAssignableFrom(view.getClass())) {
filteredViews.add(classToFilterBy.cast(view));
}
}
allViews = null;
return filteredViews;
}
能够看到,robotium直接利用了Class. isAssignableFrom进行类型的匹配。
5. 4.文本搜索
获得了控件列表。能够開始搜索指定的目标控件了。先从我们最经常使用的文本搜索開始。看看robotium的搜索流程。搜索过程的代码主要位于Searcher.java中,主要功能在两个searchFor函数中实现,通过嵌套完毕目标的搜索。
第一层
<strong> public <T extends TextView> T searchFor(final Class<T> viewClass, final String regex, int expectedMinimumNumberOfMatches, final long timeout, final boolean scroll, final boolean onlyVisible) {
//修正非法的expectedMinimumNumberOfMatches
if(expectedMinimumNumberOfMatches < 1) {
expectedMinimumNumberOfMatches = 1;
} //定义一个Callable给下层searchFor使用,能够直接获取到符合条件的控件列表
final Callable<Collection<T>> viewFetcherCallback = new Callable<Collection<T>>() {
@SuppressWarnings("unchecked")
public Collection<T> call() throws Exception {
sleeper.sleep();
//从当前的Android View中获取到符合viewClass的控件列表
ArrayList<T> viewsToReturn = viewFetcher.getCurrentViews(viewClass); if(onlyVisible){
//过滤掉Invisible的控件
viewsToReturn = RobotiumUtils.removeInvisibleViews(viewsToReturn);
} //robotium支持在webView中查找网页控件,因此若目标控件是TextView或是TextView的子类,
//会把网页中的文本框也加到控件列表中。
if(viewClass.isAssignableFrom(TextView.class)) {
viewsToReturn.addAll((Collection<? extends T>) webUtils.getTextViewsFromWebView());
}
return viewsToReturn;
}
}; try {
//调用下层searchFor继续搜索
return searchFor(viewFetcherCallback, regex, expectedMinimumNumberOfMatches, timeout, scroll);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
</strong>
这个函数的主要功能有二,一是对非法的expectedMinimumNumberOfMatches进行修正。二是为下一层searchFor提供一个Callable,里面定义好了控件列表的获取过程。
1) expectedMinimumNumberOfMatches:这个參数表示搜索目标最小发现数目,当一个界面中有多个控件满足搜索条件。通过此參数能够指定想要获取的是第几个。
2) Callable<Collection<T>> viewFetcherCallback:定义了控件列表(即搜索范围)的获取过程。首先利用前面提到的viewFetcher.getCurrentViews(viewClass)获取一个初步的列表。再通过RobotiumUtils.removeInvisibleViews(viewsToReturn)过滤掉不可见控件;最后因为Robotium支持webView内部搜索(Robotium的名字貌似也是来源于Selenium),所以当搜索目标是一个TextView时,Robotium还会调用webUtils.getTextViewsFromWebView()把网页中的文本框增加到搜索范围中。
第二层
<strong> public <T extends TextView> T searchFor(Callable<Collection<T>> viewFetcherCallback, String regex, int expectedMinimumNumberOfMatches, long timeout, boolean scroll) throws Exception {
final long endTime = SystemClock.uptimeMillis() + timeout;
Collection<T> views; while (true) { final boolean timedOut = timeout > 0 && SystemClock.uptimeMillis() > endTime; if(timedOut){
logMatchesFound(regex);
return null;
} //获取符合条件的控件列表
views = viewFetcherCallback.call(); for(T view : views){
if (RobotiumUtils.getNumberOfMatches(regex, view, uniqueTextViews) == expectedMinimumNumberOfMatches) {
uniqueTextViews.clear();
return view;
}
}
if(scroll && !scroller.scrollDown()){
logMatchesFound(regex);
return null;
}
if(!scroll){
logMatchesFound(regex);
return null;
}
}
}</strong>
这一层的主要功能就是循环在控件列表中找到含有指定文本的控件,直至超时或发现了 expectedMinimumNumberOfMatches数目的目标控件,这个过程中须要注意的有四点:
1) uniqueTextViews:为了防止找到的控件存在反复。此处用了一个uniqueTextViews集合来存储搜索到的结果。
2) 文本的匹配:直接利用了Pattern进行正则匹配,但比对的内容不仅仅包含view.getText(),还包含 view.getError()以及view.getHint()
3) 自己主动滚动:当开启了scroll选项。而且在当前的界面没有找到足够的目标时。Robotium会自己主动滚动界面 (只是好像仅仅会向下?):
if(scroll && !scroller.scrollDown()
4) 滚动时robotium仅仅会滚动drawingTime最大的控件(通过ViewFetcher.getFreshestView())。所以一个界面中有两个可滚动控件时,robotium仅仅会滚动当中一个。
6.引用
5.0.1 源代码解析之控件搜索》,这里同一时候对作者表达本人感谢之情,省了我去又一次分析的时间!
作者 |
自主博客 |
微信 |
CSDN |
天地会珠海分舵 |
服务号:TechGoGoGo 扫描码: |
http://blog.csdn.net/zhubaitian |
版权声明:本文博客原创文章,博客,未经同意,不得转载。
Robotium原则的实施源代码分析的更多相关文章
- spark(1.1) mllib 源代码分析
在spark mllib 1.1加入版本stat包,其中包括一些统计数据有关的功能.本文分析中卡方检验和实施的主要原则: 一个.根本 在stat包实现Pierxunka方检验,它包括以下类别 (1)适 ...
- Media Player Classic - HC 源代码分析 5:关于对话框 (CAboutDlg)
===================================================== Media Player Classic - HC 源代码分析系列文章列表: Media P ...
- Linux内核源代码分析方法
Linux内核源代码分析方法 一.内核源代码之我见 Linux内核代码的庞大令不少人"望而生畏",也正由于如此,使得人们对Linux的了解仅处于泛泛的层次.假设想透析Linux ...
- Monkey源代码分析番外篇之Android注入事件的三种方法比較
原文:http://www.pocketmagic.net/2012/04/injecting-events-programatically-on-android/#.VEoIoIuUcaV 往下分析 ...
- HBase源代码分析之MemStore的flush发起时机、推断条件等详情
前面的几篇文章.我们具体介绍了HBase中HRegion上MemStore的flsuh流程,以及HRegionServer上MemStore的flush处理流程.那么,flush究竟是在什么情况下触发 ...
- Java设计模式-代理模式之动态代理(附源代码分析)
Java设计模式-代理模式之动态代理(附源代码分析) 动态代理概念及类图 上一篇中介绍了静态代理,动态代理跟静态代理一个最大的差别就是:动态代理是在执行时刻动态的创建出代理类及其对象. 上篇中的静态代 ...
- Monkey源代码分析之事件注入
本系列的上一篇文章<Monkey源代码分析之事件源>中我们描写叙述了monkey是怎么从事件源取得命令.然后将命令转换成事件放到事件队列里面的.可是到如今位置我们还没有了解monkey里面 ...
- MonkeyRunner源代码分析之启动
在工作中由于要追求完毕目标的效率,所以很多其它是强调实战.注重招式.关注怎么去用各种框架来实现目的.可是假设一味仅仅是注重招式.缺少对原理这个内功的了解,相信自己非常难对各种框架有更深入的理解. 从几 ...
- android-plugmgr源代码分析
android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...
随机推荐
- 程序猿的量化交易之路(29)--Cointrader之Tick实体(16)
转载需注明出处:http://blog.csdn.net/minimicall,http://cloudtrade.top Tick:什么是Tick,在交易平台中很常见,事实上就 单笔交易时某仅仅证券 ...
- Java学习笔记——File类之文件管理和读写操作、下载图片
Java学习笔记——File类之文件管理和读写操作.下载图片 File类的总结: 1.文件和文件夹的创建 2.文件的读取 3.文件的写入 4.文件的复制(字符流.字节流.处理流) 5.以图片地址下载图 ...
- SuSE(SLES)安装配置syslog-ng日志server,可整合splunk
Update History 2014年04月25日 - 撰写初稿 引言 在自己主动化部署AutoYast.自己主动化监控BMC Patrol双方面形成雏形后.日志的收集.管理.分析也顺势成为我们须要 ...
- Java Swing界面编程(28)---复选框:JCheckBox
程序能够通过JRadioButton实现单选button的功能,那么要实现复选框的功能,则必须使用JCheckBox完毕. package com.beyole.util; import java.a ...
- Cocos2d-x 3.1.1 学习日志8--2分钟让你知道cocos2d-x3.1.1 文本类别
实际上文本经常使用的三个,LabelTTF,LabelBMF和LabelAtlas.而他们使用非常相似.所以,你会只举一反三,非常快就能够掌握了. <span style="font- ...
- 动态分析maillog日志,把恶意链接直接用防火墙禁止
近期用 postfix + dovecot 搭建了一个邮件server, 被人当做垃圾邮件转发器,经过配置postfix 的黑白名单, postfix 提示成功的 REJECT 了垃圾邮件, 只是还是 ...
- 齐博软件(地方门户系统) 文件加密破解工具
原文:齐博软件(地方门户系统) 文件加密破解工具 本程序为针对"齐博软件地方门户系统5.0官方原版"的破解工具,一个垃圾系统居然弄出这么恶心的加密方式,有个鸟用!以后见一个破一个! ...
- 分享毕业学生“ERP实施project联赛”总结,是肺腑之言——知识是人的价值的体现,每门课程是有意义的学校纪律
丁.这是我刚刚完成的实习报告,特别是给你一个.阿信,让你知道的真实想法研究生管,我希望你相信在教育管帮助.---雷管1102 刘弈福 以上是刚刚收到(20140427)生邮件,贻富不是我带的毕业设计学 ...
- poj3278(bfs)
题目链接:http://poj.org/problem?id=3278 分析:广搜,每次三种情况枚举一下,太水不多说了. #include <cstdio> #include <cs ...
- PostgreSQL服务端监听设置及client连接方法
背景介绍: PostgreSQL服务端执行在RedHat Linux上,IP为:192.168.230.128 client安装在Windows XP上, IP为:192.168.230.1 配置方法 ...