Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:
[html] 
<manifest 
xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.android.launcher"> 
 
<original-package android:name="com.android.launcher2" /> 
... 
<application 
    android:name="com.android.launcher2.LauncherApplication" 
    ... 
    > 
    <activity 
        android:name="com.android.launcher2.Launcher" 
        ... 
        > 
        <intent-filter> 
            <action android:name="android.intent.action.MAIN" /> 
            <category android:name="android.intent.category.HOME" /> 
            <category android:name="android.intent.category.DEFAULT" /> 
            <category android:name="android.intent.category.MONKEY"/> 
        </intent-filter> 
    </activity> 
    ... 
</application> 
</manifest> 
 从中我们可以知道启动过程需要先后初始化LauncherApplication和Launcher的对象。更加简洁的说,启动过程可以分成两步,第一步在
LauncherApplication.onCreate()方法中,第二部在Launcher.onCreate()方法中。
先看第一步,代码片段如下:
[java] 
public void onCreate() { 
        super.onCreate(); 
        // 在创建icon cache之前,我们需要判断屏幕的大小和屏幕的像素密度,以便创建合适大小的icon 
        final int screenSize = getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK; 
        sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE || 
            screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE; 
        sScreenDensity = getResources().getDisplayMetrics().density; 
 
        mIconCache = new IconCache(this); 
        mModel = new LauncherModel(this, mIconCache); 
 
        // 注册广播接收器 
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); 
        ...... 
        registerReceiver(mModel, filter); 
 
 
        //注册ContentObserver,监听LauncherSettings.Favorites.CONTENT_URI数据的变化 
        ContentResolver resolver = getContentResolver(); 
        resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true, 
                mFavoritesObserver); 
    }

LauncherApplication是Application的子类,是整个程序的入口。因此,一些全局信息的初始化和保存工作就放到这里执行。包括屏幕大小,像素密度信息的获取,以及
BroadcastReceiver和ContentObserver的注册都在整个程序的开始就完成。LauncherApplication的工作结束之后,下面就开始初始化Launcher了。Launcher是一个Activity,
而Activity的生命周期中,有几个重要的回调方法,而onCreate()方法是最先被执行的用于进行初始化操作的。那下面就来看看Launcher.onCreate()中具体做了哪些操作:
[java]
protected void onCreate(Bundle savedInstanceState) { 
    ... 
    mModel = app.setLauncher(this); 
    mIconCache = app.getIconCache(); 
    ... 
    mAppWidgetManager = AppWidgetManager.getInstance(this); 
    mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID); 
    mAppWidgetHost.startListening(); 
    ... 
    //检查本地保存的配置是否需要更新 
    checkForLocaleChange(); 
    setContentView(R.layout.launcher); 
    //对UI控件进行初始化和配置 
    setupViews(); 
    //向用户展示指导的页面 
    showFirstRunWorkspaceCling(); 
    registerContentObservers(); 
    ... 
    if (!mRestoring) { 
    //为Launcher加载数据 
        mModel.startLoader(this, true); 
    } 
    ... 
}

可以通过时序图,直观的认识下,onCreate()中主要进行了哪些操作:

可以将Launcher.onCreate()所执行的操作大概分为七步:
1、LauncherAppliaction.setLauncher()。
2、AppWidgetHost.startListening(),对widget事件进行监听
3、checkForLocaleChange(),检查更新本地保存的配置文件
4、setupViews(),配置UI控件
5、showFirstRunWorkspaceCling(),第一次启动时显示的指导画面
6、registerContentObservers(),设置内容监听器
7、LauncherModel.startLoader(),为Launcher加载Workspace和AllApps中的内容
那么,下面就一步一步的顺着执行的过程来看Launcher启动过程中都做了些什么。
Step1:LauncherApplication.setLauncher()
调用LauncherAppliction对象的setLauncher()方法,得到一个LauncherModel对象的引用,setLauncher内容如下:
[java] 
LauncherModel setLauncher(Launcher launcher) { 
        mModel.initialize(launcher); 
        return mModel; 
    } 
在setLauncher中继续执行了mModel对象的initialize方法,在initialize中只有小段代码:
[java] 
public void initialize(Callbacks callbacks) { 
        synchronized (mLock) { 
            mCallbacks = new WeakReference<Callbacks>(callbacks); 
        } 
    } 
由于Launcher实现了Callback接口。在mModel中,将传入的Launcher对象向下转型为Callback赋值给mCallbacks变量。并在LauncherModel中获得了一个Callbacks的软引
用通过这一过程,将Launcher对象作为Callback与mModel进行绑定,当mModel后续进行操作时,Launcher可以通过回调得到结果。
Step2:mAppWidgetHost.startListening()
LauncherAppWidgetHost继承自AppWidgetHost,它的作用就是帮助Launcher管理AppWidget,并且能够捕获长按事件,使得应用可以正常的删除、添加
AppWidget。通过调用mAppWidgetHost.startListening()方法,开启监听。
Step3:checkForLocaleChange()
接下来执行checkForLocaleChange(),方法内容如下:
[java] 
private void checkForLocaleChange() { 
        if (sLocaleConfiguration == null) { 
 
            //从本地存储文件中加载配置信息,包括locale地理位置、mcc移动国家代码 
            //mnc移动网络代码 
            new AsyncTask<Void, Void, LocaleConfiguration>() { 
                @Override 
                protected LocaleConfiguration doInBackground(Void... unused) { 
                    LocaleConfiguration localeConfiguration = new LocaleConfiguration(); 
                    readConfiguration(Launcher.this, localeConfiguration); 
                    return localeConfiguration; 
                } 
 
                @Override 
                protected void onPostExecute(LocaleConfiguration result) { 
                    sLocaleConfiguration = result; 
                    //从本地取出信息后,再次调用 
                    checkForLocaleChange();   
                } 
            }.execute(); 
            return; 
        } 
 
        //得到设备当前的配置信息 
        final Configuration configuration = getResources().getConfiguration(); 
 
        final String previousLocale = sLocaleConfiguration.locale; 
        final String locale = configuration.locale.toString(); 
 
        final int previousMcc = sLocaleConfiguration.mcc; 
        final int mcc = configuration.mcc; 
 
        final int previousMnc = sLocaleConfiguration.mnc; 
        final int mnc = configuration.mnc; 
 
        boolean localeChanged = !locale.equals(previousLocale) || mcc != previousMcc || mnc != previousMnc; 
 
 
        if (localeChanged) { 
            sLocaleConfiguration.locale = locale; 
            sLocaleConfiguration.mcc = mcc; 
            sLocaleConfiguration.mnc = mnc; 
 
            //清空Icon 
            mIconCache.flush(); 
 
            final LocaleConfiguration localeConfiguration = sLocaleConfiguration; 
             
            //将更新后的数据重新写入本地文件保存 
            new Thread("WriteLocaleConfiguration") { 
                @Override 
                public void run() { 
                    writeConfiguration(Launcher.this, localeConfiguration); 
                } 
            }.start(); 
        } 
    } 
在这个方法中,先是检查了本地文件的配置与当前设备的配置是否一致,如果不一致,则更新配置,并且清空IconCache,因为配置的改变可能会改变语言环境,
所以需要清空IconCache中的内容重新加载。
Step4:setupViews()
setupViews()方法调用,在这个方法中简单的对所有的UI控件进行加载和配置:
[java] 
/**
     * Finds all the views we need and configure them properly.
     */ 
    private void setupViews() { 
        final DragController dragController = mDragController; 
        ... 
        // Setup the drag layer 
        mDragLayer.setup(this, dragController); 
 
        // Setup the hotseat 
        mHotseat = (Hotseat) findViewById(R.id.hotseat); 
        if (mHotseat != null) { 
            mHotseat.setup(this); 
        } 
 
        // Setup the workspace 
        mWorkspace.setHapticFeedbackEnabled(false); 
        mWorkspace.setOnLongClickListener(this); 
        mWorkspace.setup(dragController); 
        dragController.addDragListener(mWorkspace); 
 
        // Get the search/delete bar 
        mSearchDropTargetBar = (SearchDropTargetBar) mDragLayer.findViewById(R.id.qsb_bar); 
 
        // Setup AppsCustomize 
        mAppsCustomizeTabHost = (AppsCustomizeTabHost) 
                findViewById(R.id.apps_customize_pane); 
        mAppsCustomizeContent = (AppsCustomizePagedView) 
                mAppsCustomizeTabHost.findViewById(R.id.apps_customize_pane_content); 
        mAppsCustomizeContent.setup(this, dragController); 
 
        // Get the all apps button 
        mAllAppsButton = findViewById(R.id.all_apps_button); 
        if (mAllAppsButton != null) { 
            mAllAppsButton.setOnTouchListener(new View.OnTouchListener() { 
                @Override 
                public boolean onTouch(View v, MotionEvent event) { 
                    if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { 
                        onTouchDownAllAppsButton(v); 
                    } 
                    return false; 
                } 
            }); 
        } 
        // Setup the drag controller (drop targets have to be added in reverse order in priority) 
        dragController.setDragScoller(mWorkspace); 
        dragController.setScrollView(mDragLayer); 
        dragController.setMoveTarget(mWorkspace); 
        dragController.addDropTarget(mWorkspace); 
        if (mSearchDropTargetBar != null) { 
            mSearchDropTargetBar.setup(this, dragController); 
        } 
    } 
由于UI组件较多,setupViews中所进行的操作也比较繁琐,先通过时序图来简单的理一下吧:


这里一共包括5个UI组件和一个DragController,那就一步一步地看都进行了哪些操作吧。
1、DragLayer
首先我们简单的认识下Draglayer。DragLayer继承自FrameLayout,是整个Launcher的根容器。当快捷图标或者AppWidget被拖拽时,事件的处理就在DragLayer进
行操作的,DragLayer.setup()方法的内容如下:
[java]
public void setup(Launcher launcher, DragController controller) { 
        mLauncher = launcher; 
        mDragController = controller; 
    } 
只是简单的做了赋值操作,使DragLayer持有Launcher和DragController对象的引用。DragController可以帮助其实现拖拽操作。
2、Hotseat
Hotseat也是FrameLayout的直接子类,代表主屏幕下方的dock栏,可以放置4个快捷图标和一个进入AllApps的按钮。代码如下:
[java] 
public void setup(Launcher launcher) { 
        mLauncher = launcher; 
        setOnKeyListener(new HotseatIconKeyEventListener()); 
    } 
方法调用之后,Hotseat持有Launcher对象的引用,并且用HotseatIconKeyEvenListener对自身的按键进行监听,进入HotseatIconKeyEvenListener可以看到:

[java] 
class HotseatIconKeyEventListener implements View.OnKeyListener { 
    public boolean onKey(View v, int keyCode, KeyEvent event) { 
        final Configuration configuration = v.getResources().getConfiguration(); 
        return FocusHelper.handleHotseatButtonKeyEvent(v, keyCode, event, configuration.orientation); 
    } 

调用方法handleHotseatButtonKeyEvent()来处理相应的事件:
[java] 
static boolean handleHotseatButtonKeyEvent(View v, int keyCode, KeyEvent e, int orientation) { 
        ... 
        switch (keyCode) { 
            case KeyEvent.KEYCODE_DPAD_LEFT: 
                ... 
                break; 
            case KeyEvent.KEYCODE_DPAD_RIGHT: 
                ... 
                break; 
            case KeyEvent.KEYCODE_DPAD_UP: 
                ... 
                break; 
            case KeyEvent.KEYCODE_DPAD_DOWN: 
                ... 
                break; 
            default: break; 
        } 
        return wasHandled; 
    } 
handleHotseatButtonKeyEvent()方法中根据当前的方向,对KeyEvent.KEYCODE_DPAD_LEFT、KeyEvent.KEYCODE_DPAD_RIGHT、
KeyEvent.KEYCODE_DPAD_UP、KeyEvent.KEYCODE_DPAD_DOWN即可能存在(如果手机有实体按键)的导航按钮上、下、左、右进行响应。这样Hotseat
的初始化工作就完成了。
3、Workspace的初始化
先调用setHapticFeedbackEnabled(false),使其在触摸的时候没有触感反馈。接着设置长按事件的监听setOnLongClickListener(this),Launcher实现了
OnLongClickListener接口,看看Launcher中是如何进行响应的:
[java]
public boolean onLongClick(View v) { 
        ... 
        if (!(v instanceof CellLayout)) { 
            v = (View) v.getParent().getParent(); 
        } 
        ... 
        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag(); 
        .. 
        // The hotseat touch handling does not go through Workspace, and we always allow long press 
        // on hotseat items. 
        final View itemUnderLongClick = longClickCellInfo.cell; 
        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress(); 
        if (allowLongPress && !mDragController.isDragging()) { 
            if (itemUnderLongClick == null) { 
                // 在空的空间上长按时 
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, 
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 
                startWallpaper(); 
            } else { 
                ... 
            } 
        } 
        return true; 
    } 
这里我们只关心与Workspace的长按事件相关的内容,当Workspace发生长按事件时,产生触感反馈,同时调用startWallpaper进行壁纸的设置:
[java] 
private void startWallpaper() { 
        showWorkspace(true); 
        final Intent pickWallpaper = new Intent(Intent.ACTION_SET_WALLPAPER); 
        Intent chooser = Intent.createChooser(pickWallpaper, 
                getText(R.string.chooser_wallpaper)); 
        startActivityForResult(chooser, REQUEST_PICK_WALLPAPER); 
    } 
showWorkspace(true)的作用是不管当前的Launcher处于什么状态,都跳转到显示Workspace的状态,并且带有动画过渡。而后面几段代码的作用就是弹出
Dialog,包含了所有能够响应ACTOIN_SET_WALLPAPER的action的Activity。然后我们就可以选择一个来设置比壁纸了。接着就是调用Workspace.setup():
[java]
void setup(DragController dragController) { 
        mSpringLoadedDragController = new SpringLoadedDragController(mLauncher); 
        mDragController = dragController; 
 
        // hardware layers on children are enabled on startup, but should be disabled until 
        // needed 
        updateChildrenLayersEnabled(); 
        setWallpaperDimension(); 
    } 
代码中先创建了一个SpringLoadedDragController的对象,这个类的作用控制当Launcher处于State.APPS_CUSTOMIZE_SPRING_LOADED状态时,即处于缩小状
态时,提供控制Launcher进行滑动、放置item的操作。接着Workspace的成员变量mDragController获取了DragController对象的引用。随后,调用
updateChildrenLayersEnabled (),注释中的意思是当子view在创建的时候会开启硬件层,其它时候关闭。其中调用了内部的API这里就不过多追究了。最后,调用
setWallpaperDimension()设置Wallpaper的尺寸。下面还有一步操作调用dragController.addDragListener(mWorkspace)方法,Workspace实现了DragListener:
[java]
interface DragListener { 
       void onDragStart(DragSource source, Object info, int dragAction); 
       void onDragEnd(); 
   } 
这样mWorkspace就能够响应拖拽事件了,具体响应内容将在后面的文章中进行分析。
这样mWorkspace的初始化就算完成了,主要完成了两件事情:1、设置了对长按事件的处理,2、对拖拽事件的处理
4、AppsCustomizeTabHost、AppsCustomizePagedView
AppsCustomizePagedView是内嵌在AppsCustomizeTabHost中的组件,在当点击AllApp按钮是,会跳转到AppsCustomizeTabHost中,而在
AppsCustomizePagedView装载Icon。初始化时调用AppCustomizedPagedView.setup()方法:
[java] 
public void setup(Launcher launcher, DragController dragController) { 
        mLauncher = launcher; 
        mDragController = dragController; 
    } 
获取Launcher与DragController对象的引用。
5、DragController
DragController类主要的工作就是处理拖拽事件,对其进行初始化时分别调用了四个方法dragController.setDragScoller(mWorkspace);dragController.setScrollView(mDragLayer);
dragController.setMoveTarget(mWorkspace);dragController.addDropTarget(mWorkspace);那分别看看这四个方法中具体都做了什么:
[java] 
public void setDragScoller(DragScroller scroller) { 
        mDragScroller = scroller; 
    } 
首先我想吐槽下,方法名应该时在敲代码的时候拼错了,正常情况应该是setDragScroller()~~~~~。Workspace实现了DragScroller接口,代表了Workspace
可以进行滑动操作。通过此方法获取到了DragScroller对象。接着又调用了DragController.setScrollView()
[java]
/**
 * Set which view scrolls for touch events near the edge of the screen.
 */ 
public void setScrollView(View v) { 
    mScrollView = v; 
}

从提供的代码注释理解,这个方法设置了当屏幕的边缘触摸滑动时,所滚动的View。(目前还不清楚具体所指的对象)
[java] 
/**
 * Sets the view that should handle move events.
 */ 
void setMoveTarget(View view) { 
    mMoveTarget = view; 
}     
设置应该处理移动事件的View,传入的对象是Workspace。
[java] 
/**
 * Add a DropTarget to the list of potential places to receive drop events.
 */ 
public void addDropTarget(DropTarget target) { 
    mDropTargets.add(target); 

将Workspace对象作为DropTarget对象添加到mDropTargets中。其中DropTarget接口的定义了一个能够接收拖曳对象的类。当桌面的item被拖拽后,需要找到下一
个容纳它的容器,而这个容器就一个DropTarget。
6、SearchDropTargetBar
SearchDropTargetBar管理着搜索框和删除框的转换,正常情况下它是一个searchBar,当图标被拖拽时,它就变成了deleteDropTargetBar,将图标拖放到上面松手就可以将其从Workspace中删除。
[java] 
public void setup(Launcher launcher, DragController dragController) { 
        dragController.addDragListener(this); 
        dragController.addDragListener(mInfoDropTarget); 
        dragController.addDragListener(mDeleteDropTarget); 
        dragController.addDropTarget(mInfoDropTarget); 
        dragController.addDropTarget(mDeleteDropTarget); 
        mInfoDropTarget.setLauncher(launcher); 
        mDeleteDropTarget.setLauncher(launcher); 
    } 
setup中执行的内容比较繁杂,这里不作详细的分析。
这样setupViews()执行完毕。继续回到onCreate()方法中分析。
Step5:showFirstRunWorkspaceCling()

showFirstRunWorkspaceCling()方法调用,在应用第一次被启动的时候,此方法会被调用,用于向用户展示一个指导界面。以后都不会再出现。
Step6:registerContentObservers()

registerContentObservers()注册对指定URI所指定的数据的监听,及时对数据变化做出反应。
Step7:LauncherModel.startLoader()

在应用启动的时候需要加载数据,LauncherModel.startLoader()就完成了这个任务。加载过程的基本流程如下:

通过上面的时序图,对加载的流程基本有了认识。调用LauncherModel.startLoader()开始加载内容,内容加载完之后,通过LauncherModel.Callbacks接口定义的回
调方法,将数据返回给需要的对象。而Launcher实现了这个接口,数据将回传给Launcher。了解了基本过程之后,开始进入加载过程。
[java] 
public void startLoader(Context context, boolean isLaunching) { 
        synchronized (mLock) { 
            ...... 
            // Don't bother to start the thread if we know it's not going to do anything 
            if (mCallbacks != null && mCallbacks.get() != null) { 
                ...... 
                mLoaderTask = new LoaderTask(context, isLaunching); 
                sWorkerThread.setPriority(Thread.NORM_PRIORITY); 
                sWorker.post(mLoaderTask); 
            } 
        } 
    } 
方法中,创建了一个实现了Runnable接口的LoaderTask类的对象mLoaderTask,mWork是一个Handler,调用mWork.post()将mLoaderTask添加到消息队列中。最
后mLoaderTask中的run方法就会得到执行:
[java] 
public void run() { 
            ...... 
            keep_running: { 
                ...... 
                if (loadWorkspaceFirst) { 
                    ...... 
                    loadAndBindWorkspace(); 
                } else { 
                    ...... 
                } 
 
                if (mStopped) { 
                    break keep_running; 
                } 
 
                ...... 
                waitForIdle(); 
 
                // second step 
                if (loadWorkspaceFirst) { 
                    ...... 
                    loadAndBindAllApps(); 
                } else { 
                    ...... 
                } 
                ...... 
            } 
            ...... 
        } 
如果是初次启动,则loadWorkspaceFirst=true,loadAndBindWorkspace被调用,此时Workspace中的内容项将被加载并且绑定显示到Workspace中。当
Workspace中的内容加载之后,调用waitForIdle方法,以等待加载结束。确认完成之后紧接着loadAndBindAllApps()方法执行,在这个方法中将加载AllApps页面的
内容。这样加载过程就分成了两个部分:1、loadAndBindWorkspace()加载Workspace内容。2、loadAndBindAllApps()加载AllApps中的内容。这部分内容本文暂
不作深入的分析。
随着startLoader()的过程执行完毕,Launcher的初始化过程就基本上结束了。启动过程是很繁琐的,因为所有应用中需要使用到的组件都可能在启动的时候
进行配置,等到从具体的功能入手的时候,就能够更加清楚启动过程所做的操作的意义。
作者:chenshaoyang0011

【转】Android 4.0 Launcher2源码分析——启动过程分析的更多相关文章

  1. 【Android】Android 4.0 Launcher2源码分析——启动过程分析

    Android的应用程序的入口定义在AndroidManifest.xml文件中可以找出:[html] <manifest xmlns:android="http://schemas. ...

  2. Android 4.0 Launcher2源码分析——主布局文件(转)

    本文来自http://blog.csdn.net/chenshaoyang0011 Android系统的一大特色是它拥有的桌面通知系统,不同于IOS的桌面管理,Android有一个桌面系统用于管理和展 ...

  3. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  4. jQuery 2.0.3 源码分析Sizzle引擎解析原理

    jQuery 2.0.3 源码分析Sizzle引擎 - 解析原理 声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 先来回答博友的提问: 如何解析 div > p + ...

  5. 【转】Ubuntu 14.04.3上配置并成功编译Android 6.0 r1源码

    http://www.linuxidc.com/Linux/2016-01/127292.htm 终于成功把Android 6.0 r1源码的源码编译.先上图,这是在Ubuntu中运行的Android ...

  6. Spark2.1.0之源码分析——事件总线

    阅读提示:阅读本文前,最好先阅读<Spark2.1.0之源码分析——事件总线>.<Spark2.1.0事件总线分析——ListenerBus的继承体系>及<Spark2. ...

  7. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  8. Symfony2源码分析——启动过程2

    文章地址:http://www.hcoding.com/?p=46 上一篇分析Symfony2框架源码,探究Symfony2如何完成一个请求的前半部分,前半部分可以理解为Symfony2框架为处理请求 ...

  9. quartz2.x源码分析——启动过程

    title: quartz2.x源码分析--启动过程 date: 2017-04-13 14:59:01 categories: quartz tags: [quartz, 源码分析] --- 先简单 ...

随机推荐

  1. 【学习笔记】--- 老男孩学Python,day12 函数名的应用,闭包,迭代器

    1, 函数名的应用,第一类对象 函数名可以像变量一样进行使用 1.赋值 def func(): print("你吃了么?") # print(func) # a = func # ...

  2. 原生webview 日期格式转时间戳兼容问题

    需要根据后端返回的日期格式返回相应时间戳 后端返回的数据格式: let dateStr = 2019-04-19T10:39:10.000+0000;   直接new Date(dateStr ).g ...

  3. JSP 插入到数据库的数据出现 “SQLServerException: 将截断字符串或二进制数据” 错误解决方案

    最近在编写一个小型基于的jsp系统开发.掌握数据库一直感觉还不错.但是今天就出现了一个问题困扰我大半天.后来本来准备睡觉,但是觉得今天不解决这个问题恐怕晚上是“彻夜难眠啊”!!于是打开电脑,又开始捣腾 ...

  4. opencv3.2.0实现连续图片合成avi视频

    ##名称:利用videowriter实现多张连续图片合成avi视频 ##平台:QT5.7.1+OpenCV3.2.0 ##日期:2017年12月10日 /**************新建QT控制台程序 ...

  5. Spring Boot—13事务支持

    pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  6. javascript的继承实现

    javascript虽然是一门面向对象的语言,但是它的继承机制从一开始设计的时候就不同于传统的其他面向对象语言,是基于原型的继承机制,但是在这种机制下,继承依然有一些不同的实现方式. 方法一:类式继承 ...

  7. 构建微服务:快速搭建Spring Boot项目

    Spring Boot简介: Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员 ...

  8. linux 文件搜索命令find、-name、-iname、通配符*?、-size、-user、-group、-amin、-cmin、-mmin、-a、-o、-exec/-ok、-inum

    尽可能规划好目录结构,少用文件搜索操作,因为它会占用大量的系统资源 find /bin/find find [搜索范围] [匹配条件] find /etc -name initfind /etc -n ...

  9. JQuery this和$(this)的区别

    this其实是一个Html 元素. $this 只是个变量名,加$是为说明其是个jquery对象. 而$(this)是个转换,将this表示的dom对象转为jquery对象,这样就可以使用jquery ...

  10. zabbix系列之六——安装后配置二Items

    https://www.zabbix.com/documentation/3.4/manual/config/items/itemtypes/snmp 1Items 1.1creating items ...