1概念

  • Application:由多个相关的松散的与用户进行交互Activity组成,通常被打包成apk后缀文件中;
  • Activity:就是被用来进行与用户交互和用来与android内部特性交互的组件,是应用程序的核心;
  • ActivityStack:将应用程序中打开的Activity保存在历史栈中;Start Activity时入栈,返回时出栈;
  • AcitivityManager(Service):Activity管理的核心,是一个独立的进程,负责管理Activity的生命周期;
  • Task:任务栈,将一系列相关的Activity组合(可以包含多个APP),完成某个应用程序完整操作;
  • ActivityThread:每一个应用程序所在进程的主线程,循环的消息处理;
  • ApplicationThread: Binder对象,完成ActivityThread与AcitivityManager的通信,属于进程间通信;

2 Activity启动过程

  • ActivityManager和ActivityStack位于同一个进程中,而ApplicationThread和ActivityThread位于另一个进程中。
  • ActivityManager借助ActivityStack是来把所有的Activity按照后进先出的顺序放在一个堆栈中;
  • 每一个APP都有一个ActivityThread来表示主进程,而其中都包含一个ApplicationThread实例。

下面简要介绍一下Activity启动的过程:(对照以上时序图)

在APK文件安装的时候,PackageManager会解析APK中重要的AndroidManifest.xml文件,你在其中注册过的所有Activity和Service等四大组件的信息,也就会在此刻被PM获取到并存储起来。

  1. Step1:无论是通过Launcher来启动Activity,还是通过Activity内部调用startActivity接口来启动新的Activity,都通过Binder进程间通信进入到ActivityManager进程中,并且调用ActivityManager.startActivity接口;
  2. Step2:ActivityManager调用ActivityStack.startActivityMayWait来做准备要启动的Activity的相关信息;
  3. Step3:ActivityStack通知ApplicationThread要进行Activity启动调度了,这里的ApplicationThread代表的是调用ActivityManager.startActivity接口的进程,对于通过点击应用程序图标的情景来说,这个进程就是Launcher了,而对于通过在Activity内部调用startActivity的情景来说,这个进程就是这个Activity所在的进程了;
  4. Step4:ApplicationThread不执行真正的启动操作,它通过调用ActivityManager.activityPaused接口进入到ActivityManager进程中,看看是否需要创建新的进程来启动Activity;
  5. Step5:对于通过点击应用程序图标来启动Activity的情景来说,ActivityManager在这一步中,会调用startProcessLocked来创建一个新的进程,而对于通过在Activity内部调用startActivity来启动新的Activity来说,这一步是不需要执行的,因为新的Activity就在原来的Activity所在的进程中进行启动;
  6. Step6:调用ApplicationThread.scheduleLaunchActivity通知相应的进程ActivityThread执行启动Activity的操作;
  7. Step7:ApplicationThread把这个启动Activity的操作转发给ActivityThread,ActivityThread通过ClassLoader导入相应的Activity类,然后把它启动起来。

3 ActivityView的关系

主要涉及的对象:

  • Window 类是一个抽象类,提供了绘制窗口的一组通用API。
  • PhoneWindow类继承于Window类,是Window类的具体实现,即我们可以通过该类具体去绘制窗口。该类内部包含了一个DecorView对象,并对其进行一定的包装,将它作为根View,并提供一组通用的窗口操作接口。
  • DecorView类是PhoneWindow类的内部类。该类是一个FrameLayout的子类(联系<Merge>标签),并且是PhoneWindow的子类,该类就是对普通的FrameLayout进行功能的扩展,更确切点可以说是修饰(Decor的英文全称是Decoration),比如说添加TitleBar(标题栏),以及TitleBar上的滚动条等 。它是所有应用窗口的根View!
  • WindowManager是Android中一个重要的服务(Service)。WindowManager Service 是全局的,是唯一的。它将用户的操作,翻译成为指令,发送给呈现在界面上的各个Window。
  • ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,我们发现它并不是一个View类型,而是一个Handler,连接的是PhoneWindow跟WindowManagerService。
    • 它的主要作用如下:(WindowManager读取android系统里所有事件通过这个ViewRoot分发到各个activity)

      • A. 向DecorView分发收到的用户发起的event事件,如按键,触屏,轨迹球等事件;
      • B. 与WindowManager Service交互,完成整个Activity的GUI的绘制。

Activity创建View的流程如下:

  Activity创建后,系统会调用其attach方法,将其添加到ActivityThread当中,在attach方法中创建了一个PhoneWindow对象实例,要注意PhoneWindow对象创建时并没有创建Decor对象。当我们调用Acitivity的setContentView()时,实际上是调用的PhoneWindow对象的setContentView方法,这时会检查 DecorView是否存在,如果不存在则(才)创建DecorView对象,将其设置为所有窗口的根View,然后把自己的View添加到DecorView中。

  Android中的视图都是通过Window来呈现的,然后通过WindowManager来管理View。

  在建立PhoneWindow的过程中,会得到WindowManager实例,系统会调用WindowManger的addView(decor,l),把DecorView加入到WindowManagerProxy的mViews(保存View的数组)中,Activity会将DecorView注册到WindowManager中。

  WindowManger在保存好这个DecorView对象的同时,也会新创建一个ViewRoot对象用来沟通WindowManager。这样,当用户触碰屏幕或键盘的时候,WindowManager就会通知到;而当控件有一些请求产生,也会经由ViewParent送回到Window Manager中,从而完成整个通信流程。Activity是Android应用程序的载体,允许用户在其上创建一个用户界面,并提供用户处理事件的API,如 onKeyEvent,onTouchEvent等,并维护应用程序的生命周期。Activity也可以理解成Android应用程序的入口,系统服务ActivityManager负责维护Activity的实例对象,并根据运行状态维护其状态信息。

4 Activity编程

  Activity之间通过Intent进行通信,android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity。Activity的加载模式(launchMode受启动Activity的Intent对象中设置的Flag和manifest文件中Activity的元素的特性值交互控制。

launchMode

  • Standard:标准模式,调用一次startActivity()就产生一个实例
  • singleTop:Task单例模式,即若已有一个实例在栈顶,则(才)不产生新的实例,转而调用onNewIntent()。
  • singleTask:Task内单例模式,栈内可以有其他Activity实例
  • singleInstance:全局单单例模式,独占Task

大致生命周期:onCreate >>onStart>> onResume>>onPause>> onStop>> onDestroy

SingleTask时有 onStop>> (onNewIntent>>) onRestart>>onStart>>onResume

横竖屏切换:onSaveInstance->onPause->onStop->onDestory->onCreate->onStart->onRestoreInstance->onResume

launchModesingleTask的时候,通过Intent启到一个Activity,如果系统已经存在一个实例,系统就会将请求发送到这个实例上,但这个时候,系统就不会再调用通常情况下我们处理请求数据的onCreate方法,而是调用onNewIntent方法,类似onCreate用法,需要更新intent:调用setIntent(intent)

打开Activity

1、startActivity( ) :仅仅是跳转到目标页面,若是想跳回当前页面,则必须再使用一次startActivity( )。

2、startActivityForResult( ) :可以一次性完成这项任务,当程序执行到这段代码的时候,假若从A跳转到下一个B,而当这个B调用了finish()方法以后,程序会自动跳转回A,并调用A中的onActivityResult( )方法。

A: 调用   startActivityForResult(intent, RequestCode);    //此处的RequestCode的值要大于0;

并重写  onActivityResult(int requestCode, int resultCode, Intent data)

B:  setResult(RESULT_OK, intent);   // RESULT_OK为resultCode

finish();

  • 将一个activity设置成窗口模式:  将activity的属性android:theme="@style/Theme.Dialog"
  • 退出Activity
  1. 退出单一Activity:finish()
  2. 退出多Activity:

    1) 记录打开的Activity:自定义一个单例模式的Activity栈来管理所有Activity,在需要退出时,逐个关闭即可。
    2) 发送特定广播:在需要结束应用时,发送一个特定的广播,每个Activity收到广播后,关闭即可。
    3) 递归退出:都使用startActivityForResult,然后自己加标志,在onActivityResult中处理,递归关闭。
    4) 抛异常强制退出:通过抛异常,使程序Force Close。问题是如何使程序结束而不弹出Force Close窗口
    5) 通用方法:Dalvik VM的本地方法
        android.os.Process.killProcess(android.os.Process.myPid()) //获取PID
        System.exit(0); //常规Java、c#的标准退出法,返回值为0代表正常退出

  另:在Intent中加入标志Intent.FLAG_ACTIVITY_CLEAR_TOP,这打开时将会清除该进程空间的所有Activity。

  • 横竖屏切换时Activity的生命周期:

    1) 不设置Activity的configChanges 属性时,切屏会重新调用各个生命周期(见上),切横屏会执行一次,竖屏两次。

    2) 设置Activity的android:configChanges=”orientation”时,横竖屏切换都只执行一次

    3) 设置Activity的android:configChanges=”orientation|keyboardHidden”时,屏幕切换不会重新调用个各生命周期,只会执行:onConfigurationChange()

  • onCreate方法中 Bundle savedInstanceSate这个参数作用:

  在实际应用中,当一个Activity结束前,如果需要保存状态,就在onsaveInstanceState()中,将状态数据以key-value的形式放入到savedInstanceState()中。这样,当一个Activity被创建时,就能从onCreate的参数savedInsanceState中获得状态数据。

  onsaveInstanceState ()触发条件(五个):按下HOME键时;长按HOME键选择运行其他程序时;关闭屏幕显示时;启动一个新的Activity时;屏幕方向切换时。(当系统“未经你许可”时销毁了你的activity时)

  与之配套的有onRestoreInstanceState()方法,但不一定成对出现。这个方法被调用的前提是activity“确实”被系统销毁了。它的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。

5Activity Fragment

  • 传递参数:

    1) Fragment中通过getActivity()获得Activity对象,调用Activity中的公有方法((RootActivity)getActivity()).fun();

    2) Activity实现一个接口,Fragment在onAttach()方法中,将该Activity转化为该接口,在需要调用的时候回调

    3) Activity在切换Fragment的时候,通过setArguments向Fragment传递参数,Fragment通过getArguments()取值。

  • 通过FragmentManager管理:我们创建Fragment和销毁Fragment的时候,可以通过栈的方式:

    1) FragmentTransaction的add()方法,添加一个Fragment;

    2) FragmentTransaction的popBackStack()弹出

public class MainActivity extends ActionBarActivity implements FragmentCallBack{
private Button btn;
private MyFragment1 fragment1;
private MyFragment2 fragment2;
private FragmentManager fragmentManager;
private Fragment currentFragment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
fragment1 = new MyFragment1();
Bundle data = new Bundle();
data.putString("TEXT", "这是Activiy通过Bundle传递过来的值");
fragment1.setArguments(data);//通过Bundle向Fragment中传递值,在onCreateView中getArguments()
fragmentTransaction.add(R.id.rl_container, fragment1);//将fragment1设置到Activity的布局rl_container上
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commitAllowingStateLoss();
currentFragment = fragment1;
btn = (Button)findViewById(R.id.btn);
btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(currentFragment instanceof MyFragment1){
if(null == fragment2) //可以避免切换的时候重复创建
fragment2 = new MyFragment2();
……//设值,同fragment1
       fragmentTransaction.replace(R.id.fl, fragment2); // 替代fl的控件
       fragmentTransaction.commit();
currentFragment = fragment2;
}else{ //当前是fragment2,因此,只需要将fragment2出栈即可变成fragment1
fragmentManager.popBackStack();
currentFragment = fragment1;
}
}
});
}
@Override
public void callbackFun1(Bundle arg) {…… } }

数据存储和恢复:

  和Activity类似,可以用Bundle对象保存Fragment的状态,当重建activity时,可以用于恢复Fragment的状态。存储是利用onSaveInstanceState()回调函数,恢复时是在onCreate,onCreateView或onActivityCreated里。

  • Back Stack:

  Activity停止时,时存在一个由系统维护的backsatck中;但是当Fragment停止(被remove)时,需要程序员显示调用addToBackStack(),并且Fragment是保存在一个由宿主activity掌管的backstack中。

  拥有Fragment的Activity的生命周期直接影响了其中的Fragment的生命周期,这样,针对Activity的每一个生命周期的回调都会有一个类似的针对Fragment的回调。例如,当Activity收到onPause()回调时,在Activity中每个Fragment都会收到onPause()回调。但是,Fragment有几个额外的生命周期回调方法,用来处理跟Activity的交互,以便执行诸如创建和销毁Fragment的UI的动作。

这些额外的回调方法如下:

  • onAttach()       当Fragment已经跟Activity关联上的时候调用。Activity会作为该方法的参数来传递。
  • onCreateView():   创建跟Fragment关联的视图层时调用
  • onActivityCreated(): 当onCreate()方法执行完之后调用
  • onDestroyView():     当关联的视图层正在被删除时调用
  • onDetach():           当从Activity中解除Fragment的关联时调用

  如下图中说明的那样,Fragment的生命周期流收到持有这些Fragment的Activity的影响,在这个图中,你能看到每个连续的Activity状态决定了Fragment的那个回调方法可以被调用。

  例如,当Activity已经收到了onCreate()的回调之后,在Activity中的Fragment就不会再接收onActivityCreated()以上的回调了。一旦Activity到达了被恢复的状态,你就可以自由的给这个Activity添加和删除Fragment了,只有Activity在恢复态时,Fragment的生命周期才能独立的改变。但是,当Activity离开恢复态时,Fragment会再次被推进Activity的生命周期中。

6ViewPager+Fragment实现滑动标签页

1)主布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<include layout="@layout/activity_main_top_tab" />
<android.support.v4.view.ViewPager
android:id="@+id/id_page_vp"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
</android.support.v4.view.ViewPager>
</LinearLayout>

2)加载代码注意:需要引入android-support-v4.jar(正常情况系统会自动引入)

public class MainActivity extends FragmentActivity {
private ViewPager mPager;
private ArrayList<Fragment> fragmentList;
private ImageView image;
private TextView view1, view2, view3;
private int currIndex; // 当前页卡编号
private int bmpW; // 横线图片宽度
private int offset; // 图片移动的偏移量
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InitTextView();
InitImage();
InitViewPager();
}
public void InitTextView(){
view1 = (TextView)findViewById(R.id.tv_guid1);
view2 = (TextView)findViewById(R.id.tv_guid2);
view3 = (TextView)findViewById(R.id.tv_guid3);
view1.setOnClickListener(new txListener(0));
view2.setOnClickListener(new txListener(1));
view3.setOnClickListener(new txListener(2));
}
public class txListener implements View.OnClickListener{
private int index=0;
public txListener(int i) { index =i; }
@Override
public void onClick(View v) { mPager.setCurrentItem(index); }
}
public void InitImage(){
image = (ImageView)findViewById(R.id.cursor);
bmpW = BitmapFactory.decodeResource(getResources(), R.drawable.cursor).getWidth();
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenW = dm.widthPixels;
offset = (screenW/3 - bmpW)/2;
//imgageview设置平移,使下划线平移到初始位置(平移一个offset)
Matrix matrix = new Matrix();
matrix.postTranslate(offset, 0);
image.setImageMatrix(matrix);
} public void InitViewPager(){
mPager = (ViewPager)findViewById(R.id. id_page_vp);
fragmentList = new ArrayList<Fragment>();
Fragment btFragment= new ButtonFragment();
Fragment secondFragment = TestFragment.newInstance("this is second fragment");
Fragment thirdFragment = TestFragment.newInstance("this is third fragment");
fragmentList.add(btFragment);
fragmentList.add(secondFragment);
fragmentList.add(thirdFragment);
//给ViewPager设置适配器
mPager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragmentList));
mPager.setCurrentItem(0);//设置当前显示标签页为第一页
mPager.setOnPageChangeListener(new MyOnPageChangeListener());//页面变化时的监听器
}
public class MyOnPageChangeListener implements OnPageChangeListener{
private int one = offset *2 +bmpW;//两个相邻页面的偏移量
@Override
public void onPageScrolled(int arg0, float arg1, int arg2) { }
@Override
public void onPageScrollStateChanged(int arg0) { }
@Override
public void onPageSelected(int arg0) {
Animation animation = new TranslateAnimation(currIndex*one,arg0*one,0,0);//平移动画
currIndex = arg0;
animation.setFillAfter(true); // 动画终止时停留在最后一帧,不然会回到没有执行前的状态
animation.setDuration(200); // 动画持续时间0.2秒
image.startAnimation(animation);// 是用ImageView来显示动画的
int i = currIndex + 1;
Toast.makeText(MainActivity.this, "您选择了第"+i+"个页卡", Toast.LENGTH_SHORT).show();
}
}
}
  • ViewPager应该和Fragment一起使用时,此时ViewPager的适配器是FragmentPagerAdapter,当你实现一个FragmentPagerAdapter,你必须至少覆盖以下方法:

    • getCount()
    • getItem()
  • 如果ViewPager没有和Fragment一起,ViewPager的适配器是PagerAdapter,它是基类提供适配器来填充页面ViewPager内部,当你实现一个PagerAdapter,你必须至少覆盖以下方法:
    • instantiateItem(ViewGroup, int)
    • destroyItem(ViewGroup, int, Object)
    • getCount()
    • isViewFromObject(View, Object)

3Adapter

public class MyFragmentPagerAdapter extends FragmentPagerAdapter{
ArrayList<Fragment> list;
public MyFragmentPagerAdapter(FragmentManager fm,ArrayList<Fragment> list) {
super(fm);
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Fragment getItem(int arg0) {
return list.get(arg0);
}
}

  ViewPager默认会缓存三页数据,即:Viewpager每加载一个Fragment,都会预先加载此Fragment左侧或右侧的Fragment。而如果每个fragment都需要去加载数据,或从本地加载,或从网络加载,那么在这个activity刚创建的时候就变成需要初始化大量资源,浪费用户流量不止,还造成卡顿。那么,能不能做到当切换到这个fragment的时候,它才去初始化呢?答案就在Fragment里的setUserVisibleHint这个方法里。  

  该方法用于告诉系统,这个Fragment的UI是否是可见的。所以我们只需要继承Fragment并重写该方法,即可实现在fragment可见时才进行数据加载操作,即Fragment的懒加载

/** 基类Fragment  */
public abstract class BaseFragment extends Fragment {
protected View mRootView;
public Context mContext;
protected boolean isVisible;
private boolean isPrepared;
private boolean isFirst = true;
public BaseFragment() { /* Required empty public constructor*/ }
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
lazyLoad();
} else {
isVisible = false;
onInvisible();
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getActivity();
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mRootView == null) {
mRootView = initView();
}
return mRootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
isPrepared = true;
lazyLoad();
}
protected void lazyLoad() {
  //① isPrepared参数在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 initData()方法不会报空指针异常。
  //② isVisible参数在fragment可见时通过系统回调setUserVisibileHint方法设置为true,不可见时为false,这是fragment实现懒加载的关键。
  //③ isFirst确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,initData在该Fragment的整个生命周期只调用一次,第一次调用initData()方法后马上执行 isFirst = false。
if (!isPrepared || !isVisible || !isFirst)
return;
initData();
isFirst = false;
}
protected void onInvisible() { /* do sth*/ }
public abstract View initView();
public abstract void initData();
}

  为了可复用,这里我新建了个BaseFragment,在basefragment,我增加了三个方法:一个是onVisiable,即fragment被设置为可见时调用,一个是onInvisible,即fragment被设置为不可见时调用。另外再写了一个lazyLoad的抽象方法,该方法在onVisible里面调用。  

【附】

1. Activity怎么获取Service中的信息?
  1) 使用Intent的putExtras传参数过去;
  2) 使用SharePreference;
  3) 使用全局变量Application;
  4) AIDL;
  5) 如果是在同一个进程,可以通过Handler发送message;
  6) 广播发送,效率低点;
  7) bind service,获得service对象,访问其公共方法。
2. Activity和Service如何通过Handler通信?
  1) 在Activity中将Handler声明为Static,直接在Service中通过类方法调用:XActivity.myStaticHandler.sendMessage(…);
  2) 在service中定义一个Activity类本身的静态对象,在Activity中定义一个Public方法返回handler对象。
  3) Service通过onBind方法返回一个Messager对象给Activity
3. ClassLoader:
  将制定的class类对象加载到内存中;一个运行的APP至少有两个Classloader。
  系统启动时创建的BootClassLoader和应用启动时创建的PathClassLoader
4. Android string.xml 通配符 %$用法
  <string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
  在这个例子中,这个格式化的字符串有2个参数
  属性值举例说明
    %n$ms:输出字符串,n代表是第几个参数,设置m的值可以在输出之前放置空格
    %n$md:输出整数,设置m的值可以在输出之前放置空格,也可以设为0m,在输出之前放置m个0
    %n$mf:输出浮点数,n代表是第几个参数,设置m的值可以控制小数位数,如m=2.2时,输出为00.00
  在程序中按照下面的方法来根据参数来格式化字符串:
    Resources res = getResources();
    String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);

最新文章

  1. 升讯威ADO.NET增强组件(源码):送给喜欢原生ADO.NET的你
  2. HTML 5 胜出:XHTML2 宣告夭折
  3. 2013 duilib入门简明教程 -- 事件处理和消息响应 (17)
  4. PL/pgSQL函数带output参数例子
  5. javaScript 对json数据按key值排序
  6. 用于主题检测的临时日志(594fb726-af0b-400d-b647-8b1d1b477d72 - 3bfe001a-32de-4114-a6b4-4005b770f6d7)
  7. VS2010 常见错误总结
  8. SGU223 - Little Kings(状态压缩DP)
  9. CSS链接、光标、DHTML、缩放
  10. gluster 安装配置基本指南
  11. 检查mysql数据库是否存在坏表脚本
  12. ansible服务及剧本编写
  13. MySQL松散索引扫描与紧凑索引扫描
  14. PCB泪滴设计
  15. 【RL-TCPnet网络教程】第18章 BSD Sockets基础知识
  16. python之元组
  17. MTLAB: 稀疏矩阵的表示-sparse
  18. Python Queue(队列)
  19. ubuntu ------ 网络 ifconfig 不显示IP地址
  20. vc MFC 通过IDispatch调用默认成员函数

热门文章

  1. Corba、protocol buffer、SOA的区别 (转)
  2. 学习打造自己的DEBUG_NEW
  3. mysql小误区关于set global sql_slave_skip_counter=N命令
  4. 8.springMVC中的RESTful架构风格
  5. 关于“ora-01483:DATE或NUMBER赋值变量的长度无效”的问题
  6. spring集成freemaker 制作短信模板
  7. 在线学习体验大PK 云智慧发布在线教育网站性能监测报告
  8. java核心知识点学习----重点学习线程池ThreadPool
  9. LINK : fatal error LNK1104: 无法打开文件“gtestd.lib”
  10. protocol buffers的使用示例[z]