版权声明:本文出自汪磊的博客,转载请务必注明出处。

一、JobScheduler概述

JobScheduler是安卓5.0版本推出的API,允许开发者在符合某些条件时创建执行在后台的任务。在Android开发中,会存在这些场景:你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI,此时就可以使用JobScheduler了,当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。

JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:避免频繁的唤醒硬件模块,造成不必要的电量消耗以及避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。

JobSchedule适用版本为5.0及以上,目前还未发现兼容库支持。

二、JobScheduler使用

使用JobScheduler首先我们需要创建一个类继承自JobService且必须实现两个方法(JobService继承自Service),分别是onStartJob(JobParameters params)onStopJob(JobParameters params),如下:

 public class TestJobService extends JobService {

     @Override
public boolean onStartJob(JobParameters params) { return false;
} @Override
public boolean onStopJob(JobParameters params) { return false;
}
}

当任务开始时会执行onStartJob(JobParameters params)方法,这是系统用来触发已经被执行的任务。这个方法返回一个boolean值。

如果返回值是false,系统假设这个方法返回时任务已经执行完毕。

如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了开发者的肩上,当任务执行完毕时开发者需要自己调用jobFinished(JobParameters params, boolean needsRescheduled)来通知系统。

当系统接收到一个取消请求时,会调用onStopJob(JobParameters params)方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。onStopJob(JobParameters params)在这种情况下不会被调用。

需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。我们可以在onStartJob中利用Handler发送消息,在Handler中处理相关任务。

jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是告诉系统这个任务如果由于某些原因导致执行失败是否需要重新调度执行,true需要重新调度执行,false不需要。

最后我们需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。

 <service
android:name=".TestJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>

在创建完TestJobService后,我们就该创建JobScheduler对象了,创建JobScheduler对象需要通过getSystemService( Context.JOB_SCHEDULER_SERVICE )来初始化:

 JobScheduler tm =(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

接下来我们使用JobInfo.Builder来构建一个JobInfo对象。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,简单理解为这个任务的ID就可以了,第二个是这个Service组件的类名。

 JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);

mServiceComponent初始化如下:

 mServiceComponent = new ComponentName(this, TestJobService.class);

builder允许你设置很多不同的选项来控制任务的执行,重要方法说明如下:

方法名 说明
setPeriodic(long intervalMillis) 设置任务每隔intervalMillis运行一次
setMinimumLatency(long minLatencyMillis) 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与setPeriodic(long time)方法不兼容,如果这两个方法同时调用了就会引起异常
setOverrideDeadline(long maxExecutionDelayMillis) 这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与setMinimumLatency(long time)一样,这个方法也会与setPeriodic(long time),同时调用这两个方法会引发异常
setPersisted(boolean isPersisted) 这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行
setRequiredNetworkType(int networkType) 这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行
setRequiresCharging(boolean requiresCharging) 这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行
setRequiresDeviceIdle(boolean requiresDeviceIdle) 这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务

需要注意的是setRequiredNetworkType(int networkType), setRequiresCharging(boolean requireCharging) and setRequiresDeviceIdle(boolean requireIdle)者几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中

 if( tm.schedule( builder.build() ) <= 0 ) {
//something wrong
}

schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则返回我们在JobInfo.Builder中定义的标识id。

停止某个任务,可以调用JobScheduler对象的cancel(int jobId)来实现;如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()来实现。

以上就是JobScheduler的介绍,是不是很简单,重要的是理解什么情况下使用JobScheduler。

三、JobScheduler实例举例

本Demo是安卓官方提供的一个例子,我们先看TestJobService类;

 public class TestJobService extends JobService {
private static final String TAG = "SyncService"; @Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service created");
} @Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service destroyed");
} @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Messenger callback = intent.getParcelableExtra("messenger");
Message m = Message.obtain();
m.what = MainActivity.MSG_SERVICE_OBJ;
m.obj = this;
try {
callback.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Error passing service object back to activity.");
}
return START_NOT_STICKY;
} @Override
public boolean onStartJob(JobParameters params) {
// We don't do any real 'work' in this sample app. All we'll
// do is track which jobs have landed on our service, and
// update the UI accordingly.
jobParamsMap.add(params);
if (mActivity != null) {
mActivity.onReceivedStartJob(params);
}
Log.i(TAG, "on start job: " + params.getJobId());
return true;
} @Override
public boolean onStopJob(JobParameters params) {
// Stop tracking these job parameters, as we've 'finished' executing.
jobParamsMap.remove(params);
if (mActivity != null) {
mActivity.onReceivedStopJob();
}
Log.i(TAG, "on stop job: " + params.getJobId());
return true;
} MainActivity mActivity;
private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>(); public void setUiCallback(MainActivity activity) {
mActivity = activity;
} /** Send job to the JobScheduler. */
public void scheduleJob(JobInfo t) {
Log.d(TAG, "Scheduling job");
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
if(tm.schedule(t)<=0){
Log.i(TAG, "something wrong");
}
} /**
* Not currently used, but as an exercise you can hook this
* up to a button in the UI to finish a job that has landed
* in onStartJob().
*/
public boolean callJobFinished() {
JobParameters params = jobParamsMap.poll();
if (params == null) {
return false;
} else {
jobFinished(params, false);
return true;
}
}
}

上面提到过JobService其实也是Service,同样具有相应生命周期方法。TestJobService先不做详细讲解,但是要看到这个类中封装了一些JobScheduler方法供外界调用。

接下来我们结合MainActivity类一起看,MainActivity源码如下:

 public class MainActivity extends Activity {

     private static final String TAG = "MainActivity";

     public static final int MSG_UNCOLOUR_START = 0;
public static final int MSG_UNCOLOUR_STOP = 1;
public static final int MSG_SERVICE_OBJ = 2;
// UI fields.
int defaultColor;
int startJobColor;
int stopJobColor; private TextView mShowStartView;
private TextView mShowStopView;
private TextView mParamsTextView;
private EditText mDelayEditText;
private EditText mDeadlineEditText;
private RadioButton mWiFiConnectivityRadioButton;
private RadioButton mAnyConnectivityRadioButton;
private CheckBox mRequiresChargingCheckBox;
private CheckBox mRequiresIdleCheckbox;
ComponentName mServiceComponent;
TestJobService mTestService;
//
private static int kJobId = 0; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_main);
Resources res = getResources();
defaultColor = res.getColor(R.color.none_received);
startJobColor = res.getColor(R.color.start_received);
stopJobColor = res.getColor(R.color.stop_received);
// Set up UI.
mShowStartView = (TextView) findViewById(R.id.onstart_textview);
mShowStopView = (TextView) findViewById(R.id.onstop_textview);
mParamsTextView = (TextView) findViewById(R.id.task_params);
mDelayEditText = (EditText) findViewById(R.id.delay_time);
mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
mServiceComponent = new ComponentName(this, TestJobService.class);
// Start service and provide it a way to communicate with us.
Intent startServiceIntent = new Intent(this, TestJobService.class);
startServiceIntent.putExtra("messenger", new Messenger(mHandler));
startService(startServiceIntent);
} Handler mHandler = new Handler(/* default looper */) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UNCOLOUR_START:
mShowStartView.setBackgroundColor(defaultColor);
break;
case MSG_UNCOLOUR_STOP:
mShowStopView.setBackgroundColor(defaultColor);
break;
case MSG_SERVICE_OBJ:
mTestService = (TestJobService) msg.obj;
mTestService.setUiCallback(MainActivity.this);
}
}
}; private boolean ensureTestService() {
if (mTestService == null) {
Toast.makeText(MainActivity.this, "Service null, never got callback?",
Toast.LENGTH_SHORT).show();
return false;
}
return true;
} /**
* UI onclick listener to schedule a job. What this job is is defined in
* TestJobService#scheduleJob().
*/
@SuppressLint("NewApi")
public void scheduleJob(View v) {
if (!ensureTestService()) {
return;
}
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent); String delay = mDelayEditText.getText().toString();
if (delay != null && !TextUtils.isEmpty(delay)) {
builder.setMinimumLatency(Long.valueOf(delay) * 1000);
}
String deadline = mDeadlineEditText.getText().toString();
if (deadline != null && !TextUtils.isEmpty(deadline)) {
builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
if (requiresUnmetered) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
} else if (requiresAnyConnectivity) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
mTestService.scheduleJob(builder.build());
}
/**
* cancel All jobs
* @param v
*/
@SuppressLint("NewApi")
public void cancelAllJobs(View v) {
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.cancelAll();
} /**
* UI onclick listener to call jobFinished() in our service.
*/
public void finishJob(View v) {
if (!ensureTestService()) {
return;
}
mTestService.callJobFinished();
mParamsTextView.setText("");
} /**
* Receives callback from the service when a job has landed
* on the app. Colours the UI and post a message to
* uncolour it after a second.
*/
@SuppressLint("NewApi")
public void onReceivedStartJob(JobParameters params) {
mShowStartView.setBackgroundColor(startJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START);
mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second.
mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras());
} /**
* Receives callback from the service when a job that
* previously landed on the app must stop executing.
* Colours the UI and post a message to uncolour it after a
* second.
*/
public void onReceivedStopJob() {
mShowStopView.setBackgroundColor(stopJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP);
mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second.
mParamsTextView.setText("");
} }

对于这个Demo我只想说一个点,MainActivity在启动的时候onCreate()方法中47-49行启动了TestJobService,并且传递了一个new Messenger(mHandler)匿名对象,接下来我们看下TestJobService中onStartCommand方法,此方法上来就获取Messenger对象,并且调用send方法发送一个消息(实际调用的就是Handler的send方法,可自行点进源码查看),此消息obj为TestJobService类实例对象。回到MainActivity中查看mHandler,what为MSG_SERVICE_OBJ的时候:将msg的obj赋值给MainActivity中mTestService变量,然后调用TestJobService中setUiCallback方法,这样MainActivity与TestJobService类就互相持有彼此实例对象了,也就可以调用其内部的方法互相通信了,比如TestJobService中onStartJob方法调用MainActivity中的onReceivedStartJob方法,onReceivedStartJob方法中其实就是发送了Handler消息,最后在Handler中执行相关业务逻辑。好了Demo就说这一点,其余请自行细细查看,很简单,最后附上Demo布局文件:

 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/onstart_textview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#999999"
android:gravity="center"
android:text="@string/onstarttask"/>
<TextView
android:id="@+id/onstop_textview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/none_received"
android:gravity="center"
android:text="@string/onstoptask"/>
</LinearLayout>
<Button
android:id="@+id/finished_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:layout_marginBottom="5dp"
android:onClick="finishJob"
android:text="@string/finish_job_button_text"/> <TextView
android:id="@+id/task_params"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/defaultparamtext"
android:gravity="center"
android:textSize="20dp"
android:padding="15dp"
android:layout_marginBottom="10dp" /> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/constraints"
android:layout_margin="15dp"
android:textSize="18dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/connectivity"
android:layout_marginRight="10dp"/>
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/checkbox_any"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/any"
android:checked="false" />
<RadioButton android:id="@+id/checkbox_unmetered"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/unmetered"
android:checked="false" />
</RadioGroup> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/timing"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:textSize="17dp"
android:text="@string/delay"/>
<EditText
android:id="@+id/delay_time"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deadline"
android:textSize="17dp"/>
<EditText
android:id="@+id/deadline_time"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/charging_caption"
android:layout_marginRight="15dp"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkbox_charging"
android:text="@string/charging_text"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/idle_caption"
android:layout_marginRight="15dp"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkbox_idle"
android:text="@string/idle_mode_text"/>
</LinearLayout> </LinearLayout>
<Button
android:id="@+id/schedule_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:onClick="scheduleJob"
android:text="@string/schedule_job_button_text"/>
<Button
android:id="@+id/cancel_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:onClick="cancelAllJobs"
android:text="@string/cancel_all_jobs_button_text"/>
</LinearLayout>
</ScrollView>

好了,本篇到此结束,希望通过本篇介绍你能正确使用JobScheduler这个强大的工具,有些任务放在适合的时机来做更加适宜。

安卓电量优化之JobScheduler使用介绍的更多相关文章

  1. 安卓电量优化之AlarmManager使用全部解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.AlarmManager概述 AlarmManager是安卓系统中一种系统级别的提示服务,可以在我们设定时间或者周期性的执行一个intent,这 ...

  2. 安卓电量优化之WakeLock锁机制全面解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.WakeLock概述 wakelock是一种锁的机制,只要有应用拿着这个锁,CPU就无法进入休眠状态,一直处于工作状态.比如,手机屏幕在屏幕关闭 ...

  3. Android性能优化系列之电量优化

    电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情,随着Android开的性能要求越来越高,电量的优化,也显得格外重要,一个耗电的应用,用户肯定会毫不犹豫的进行卸载,所 ...

  4. Android性能优化-电量优化

    前言 电量优化,这个名词在传统PC时代,我们基本很少听见.然而到了诺基亚时代,我们也同样很少关注.直到了移动互联的智能机时代.电量优化才被慢慢的重视起来.可能的原因如下: 移动设备,不能一直使用电源供 ...

  5. 安卓性能优化之清除Handler的Message和Runnable

    安卓性能优化之清除Handler的Message和Runnable Handler是由系统所提供的一种异步消息处理的常用方式,一般情况下不会发生内存泄露. 但既然是调优,当在A_Activity中使用 ...

  6. ReSharper的功能真的很强大主要是针对代码规范和优化,园子里介绍的也不少,如果你没有安装,那我只能表示你们会相见恨晚

    二.ReSHarper 代码规范.单元测试.... ReSharper的功能真的很强大,主要是针对代码规范和优化,园子里介绍的也不少,如果你没有安装,那我只能表示你们会相见恨晚! 1.像命名不规范,f ...

  7. 安卓手机优化 ROOT自启动管理 + 电量管理

    一.KingRoot + 净化大师 KingRoot 主要完成ROOT 和 自启动软件的管理 这里禁止的自启动管理 对于有些软件是不太管用 比如美团 手机百度等 净化大师 主要完成 高电量的软件优化 ...

  8. mysql性能优化学习笔记-参数介绍及优化建议

    MySQL服务器参数介绍 mysql参数介绍(客户端中执行),尽量只修改session级别的参数. 全局参数(新连接的session才会生效,原有已经连接的session不生效) set global ...

  9. loadrunner脚本优化-ParameterList参数类型介绍

    脚本优化-Parameter List参数类型介绍 by:授客 QQ:1033553122 篇幅问题,这里采用网盘下载的方式和大家分享: 百度网盘分享: 链接: http://pan.baidu.co ...

随机推荐

  1. 多版本VisualStudio导致的.net版本问题

      写在前面:本博文是在我现有知识状态下写的, 我现在是小白, 有错误欢迎指正. 以后假如接触到更合理的见解, 我一定会修正这篇博文的. 本文原是在我本地笔记中待着的, 写于2016/05/17. 下 ...

  2. 怎样将word文件转化为Latex文件:word-to-latex-2.56具体解释

    首先推荐大家读一读这篇博文:http://blog.csdn.net/ibingow/article/details/8613556 --------------------------------- ...

  3. 【zabbix系列】安装与加入host

    測试环境 Ubuntu 14.04.1 LTS [服务端安装] 关于安装官方提供了非常具体的安装方法,包含各平台的源代码及包安装.关于其它版本号Linux请參考 https://www.zabbix. ...

  4. hdu 4409 Family Name List(LCA&amp;有坑点)

    Family Name List Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  5. x86内存映射

    Contents 1 "Low" memory (< 1 MiB) 1.1 Overview 1.2 BIOS Data Area (BDA) 1.3 Extended BI ...

  6. XML文档读取-DOM4j

    JAXP是sun公司官方提供的java解析工具包,但很多其他企业和机构也都开发了自己的xml解析工具,甚至比JAXP更加优秀,比如DOM4J. Dom4j是一个简单.灵活的开放源代码的库.Dom4j是 ...

  7. CentOS7系统卸载自带的OpenJDK并安装SUNJDK

    安装说明 系统环境: CentOS 7 安装方式: rmp安装 软件: jdk-8u111-linux-x64.rpm 下载地址: Oracle JDK 官网下载地址 卸载CentOS默认安装的Ope ...

  8. Asp.Net Web API(四)

    HttpResponseException-----HTTP响应异常 如果Web API控制器抛出一个未捕捉的异常,会发生什么呢?在默认情况下,大多数异常都会转换为一个带有状态码500的内部服务器错误 ...

  9. linux使用freetds 连接连远程服务器sqlservser2012

    1.下载:freetds-patched.tar.gz  http://www.freetds.org/software.html http://www.freetds.org/userguide/c ...

  10. iOS 单利模式实现/优缺点

    感谢此文章提供摘要: http://www.cnblogs.com/lyanet/archive/2013/01/11/2856468.html 优缺点:http://blog.csdn.net/ta ...