了解 Glow 的朋友应该知道,我们主营四款 App,分别是Eve、Glow、Nuture和Baby。作为创业公司,我们的四款 App 都处于高速开发中,平均每个 Android App 由两人负责开发,包括 Android 和 Server 开发,在满足 PM 各种需求的同时,我们的 session crash free 率保持不低于 99.8%,其中两款 App 接近 100%。

本文将对 Glow 当前 Android App 中对现有工具的探索及优化进行讲解,希望对读者有所启发。

整体结构概览

下面是 Glow Android 端的大体结构:

我们有四个 Android App,它们共用同一个 Community 社区,最底层是 Base-Library,存放公用的模块组件,如支付模块,Logging模块等等。

下面,我将依次从以下几个方面进行讲解:

  • 网络层优化
  • 内存优化实践
  • 在 App 和 Library 中集成依赖注入
  • etc.

网络层优化

1. Retrofit2 + OkHttp3 + RxJava

上面这套结构是目前最为流行的网络层架构,可以帮我们写出简洁而稳定的网络请求代码,比起以前复杂的异步回调、主次线程切换等代码更为易用,而且能支持 https 请求。

基本用法如下:

  1. UserApi userApi = retrofit.create(UserApi.class);
  1. @Get("/{id}")
  2. Observable<User> getUser(@Path("id") long id);
  1. userApi.getUser(1)
  2. .subscribeOn(Schedulers.io())
  3. .observeOn(AndroidSchedulers.mainThread())
  4. .subscribe(new Action1<User>() {
  5. @Override
  6. public void call(User user) {
  7. // handle user
  8. }
  9. }, new Action1<Throwable>() {
  10. @Override
  11. public void call(Throwable throwable) {
  12. // handle throwable
  13. }
  14. });

这只是通用做法。下面我们要根据实际情况进行优化。

2. 封装线程切换代码

上面的代码中可以看到,为了执行网络请求,我们会利用RxJava提供的Schedulers工具来方便切换线程。

  1. .subscribeOn(Schedulers.io())
  2. .observeOn(AndroidSchedulers.mainThread())

上面的代码的作用是:让网络请求进入 io线程 执行,并将返回结果转入 UI线程 去进行渲染。

不过,我们 app 有非常多的网络请求,而且除了网络请求,其他的数据库操作 或者 文件读写操作 都需要一样的线程切换。因此,为了代码复用,我们利用 RxJava 提供的 Transformer 来进行封装。

  1. // RxUtil.java
  2. public static <T> Observable.Transformer<T, T> normalSchedulers() {
  3. return new Observable.Transformer<T, T>() {
  4. @Override
  5. public Observable<T> call(Observable<T> source) {
  6. return source.subscribeOn(Schedulers.io())
  7. .observeOn(AndroidSchedulers.mainThread());
  8. }
  9. };
  10. }

然后,我们可以把网络请求代码转化为

  1. userApi.getUser(1)
  2. .compose(RxUtil.normalSchedulers())
  3. .subscribe(...)

这虽然只是很简单的改进,但能让我们的代码更简洁,更不易出错。

3. 封装响应结果 JsonDataResponse

我们 server 的所有返回结果都符合如下格式:

  1. {
  2. 'rc': 0,
  3. 'data': {...},
  4. 'msg': "Successful Call"
  5. }

其中 rc 是自定义的结果标志,server 用来告诉我们该请求的逻辑处理是否成功(此时 rc = 0)。data是这个请求需要的 json 数据。msg一般用来存放错误提示信息。

于是我们创建了一个通用类来封装所有的 Response

  1. public class JsonDataResponse<T> {
  2. @SerializedName("rc")
  3. private int rc;
  4. @SerializedName("msg")
  5. private String msg;
  6. @SerializedName("data")
  7. T data;
  8. public int getRc() { return rc; }
  9. public T getData() { return data; }
  10. }

于是,我们的请求变成如下:

  1. @Get("/{id}")
  2. Observable<JsonDataResponse<User>> getUser(@Path("id") long id);
  1. userApi.getUser(1)
  2. .compose(RxUtil.normalSchedulers())
  3. .subscribe(new Action1<JsonDataResponse<User>>() {
  4. @Override
  5. public void call(JsonDataResponse<User> response) {
  6. if (response.getRc() == 0) {
  7. User user = response.getData();
  8. // handle user
  9. } else {
  10. Toast.makeToast(context, response.getMsg())
  11. }
  12. }
  13. }, new Action1<Throwable>() {
  14. @Override
  15. public void call(Throwable throwable) {
  16. // handle throwable
  17. }
  18. });

4. 异常处理

上面已经能完成正常的网络请求了,但是,却还没有对错误进行处理。

一次网络请求中,可能发生以下几种错误:

  • 没有网络
  • 网络正常,但 http 请求失败,即 http 状态码不在 [200, 300) 之间,如404500
  • 网络正常,http 请求成功,但是 server 在处理请求时出了问题,使得返回结果的 rc != 0

不同的错误,我们希望给用户不同的提示,并且统计这些错误。

目前我们的网络请求里已经能够处理第三种情况,另外两种都在 throwable 里面,我们可以通过判断 throwable 是 IOException 还是 retrofit2.HttpException 来区分这两种情况。

因此,我们可得到如下异常处理代码:

  1. userApi.getUser(1)
  2. .compose(RxUtil.normalSchedulers())
  3. .subscribe(new Action1<JsonDataResponse<User>>() {
  4. @Override
  5. public void call(JsonDataResponse<User> response) {
  6. if (response.getRc() == 0) {
  7. User user = response.getData();
  8. // handle user
  9. handleUser();
  10. } else {
  11. // such as: customized errorMsg: "cannot find this user".
  12. Toast.makeToast(context, response.getMsg(), Toast.LENGTH_SHORT).show();
  13. }
  14. }
  15. }, new Action1<Throwable>() {
  16. @Override
  17. public void call(Throwable throwable) {
  18. String errorMsg = "";
  19. if (throwable instanceof IOException) {
  20. // io Exception
  21. errorMsg = "Please check your network status";
  22. } else if (throwable instanceof HttpException) {
  23. HttpException httpException = (HttpException) throwable;
  24. // http error.
  25. errorMsg = httpException.response();
  26. } else {
  27. errorMsg = "unknown error";
  28. }
  29. Toast.makeToast(...);
  30. }
  31. });

5. 封装异常处理代码

当然,我们并不想在每一个网络请求里都写上面一大段代码来处理 error,那样太傻了。比如上面 getUser() 请求,我希望只要写 handleUser() 这个方法,至于是网络问题还是 server 自己问题我都不想每次去 handle。

接下来我们来封装上面两个 Action 。我们可以自定义两个 Action:

  1. WebSuccessAction<T extends JsonDataResponse> implements Action1<T>
  1. WebFailureAction implements Action1<Throwable>

其中,WebSuccessAction 用来处理一切正常(网络正常,请求正常,rc=0)后的处理,WebFailureAction 用来统一处理上面三种 error

实现如下:

  1. class WebSuccessAction<T extends JsonDataResponse> implements Action1<T> {
  2. @Override
  3. public void call(T response) {
  4. int rc = response.getRc();
  5. if (rc != 0) {
  6. throw new ResponseCodeError(extendedResponse.getMessage());
  7. }
  8. onSuccess(extendedResponse);
  9. }
  10. public abstract void onSuccess(T extendedResponse);
  11. }
  1. // (rc != 0) Error
  2. class ResponseCodeError extends RuntimeException {
  3. public ResponseCodeError(String detailMessage) {
  4. super(detailMessage);
  5. }
  6. }

在 WebSuccessAction 里,我们把 rc != 0 这种情况转化成 ResponseCodeError 并抛出给 WebFailureAction 去统一处理。

  1. class WebFailAction implements Action1<Throwable> {
  2. @Override
  3. public void call(Throwable throwable) {
  4. String errorMsg = "";
  5. if (throwable instanceof IOException) {
  6. errorMsg = "Please check your network status";
  7. } else if (throwable instanceof HttpException) {
  8. HttpException httpException = (HttpException) throwable;
  9. // such as: "server internal error".
  10. errorMsg = httpException.response();
  11. } else {
  12. errorMsg = "unknown error";
  13. }
  14. Toast.makeToast(...);
  15. }
  16. }

有了上面两个自定义 Action 后,我们就可以把前面 getUser() 请求转化如下:

  1. userApi.getUser(1)
  2. .compose(RxUtil.normalSchedulers())
  3. .subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
  4. @Override
  5. public void onSuccess(JsonDataResponse<User> response) {
  6. handleUser(response.getUser());
  7. }
  8. }, new WebFailAction())

Bingo! 至此我们能够用非常简洁的方式来执行网络操作,而且完全不用担心异常处理。

内存优化实践

在内存优化方面,Google 官方文档里能找到非常多的学习资料,例如常见的内存泄漏、bitmap官方最佳实践。而且 Android studio 里也集成了很多有效的工具如 Heap ViewerMemory Monitor 和 Hierarchy Viewer 等等。

下面,本文将从其它角度出发,来对内存作进一步优化。

1. 当Activity关闭时,立即取消掉网络请求结果处理。

这一点很容易被忽略掉。大家最常用的做法是在 Activity 执行网络操作,当 Http Response 回来后直接进行UI渲染,却并不会去判断此时 Activity 是否仍然存在,即用户是否已经离开了当时的页面。

那么,有什么方法能够让每个网络请求都自动监听 Activity(Fragment) 的 lifecycle 事件并且当特定 lifecycle 事件发生时,自动中断掉网络请求的继续执行呢?

首先来看下我们的网络请求代码:

  1. userApi.getUser(1)
  2. .compose(RxUtil.normalSchedulers())
  3. .subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
  4. @Override
  5. public void onSuccess(JsonDataResponse<User> response) {
  6. handleUser(response.getUser());
  7. }
  8. }, new WebFailAction())

我们希望达到的是,当 Activity 进入 onStop 时立即停掉网络请求的后续处理。

这里我们参考了 RxLifecycle 的实现方式,之所以没有直接使用 RxLifecycle 是因为它必须我们的 BaseActivity 继承其提供的 RxActivity ,而 RxActivity 并未继承我们需要的 AppCompatActivity。因此本人只能在学习其源码后,自己重新实现一套,并做了一些改动以更符合我们自己的应用场景。

具体实现如下:

  • 首先,我们在 BaseActivity 里,利用 RxJava 提供的 PublishSubject 把所有 lifecycle event 发送出来。
  1. class BaseActivity extends AppCompatActivity {
  2. protected final PublishSubject<ActivityLifeCycleEvent> lifecycleSubject = PublishSubject.create();
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. lifecycleSubject.onNext(ActivityLifeCycleEvent.CREATE);
  7. }
  8. @Override
  9. protected void onDestroy() {
  10. lifecycleSubject.onNext(ActivityLifeCycleEvent.DESTROY);
  11. super.onDestroy();
  12. }
  13. @Override
  14. protected void onStop() {
  15. lifecycleSubject.onNext(ActivityLifeCycleEvent.STOP);
  16. super.onStop();
  17. }
  18. }
  • 然后,在 BaseActivity 里,提供 bindUntilEvent(LifeCycleEvent) 方法
  1. class BaseActivity extends AppCompatActivity {
  2. @NonNull
  3. @Override
  4. public <T> Observable.Transformer<T, T> bindUntilEvent(@NonNull final ActivityLifeCycleEvent event) {
  5. return new Observable.Transformer<T, T>() {
  6. @Override
  7. public Observable<T> call(Observable<T> sourceObservable) {
  8. Observable<ActivityLifeCycleEvent> o =
  9. lifecycleSubject.takeFirst(activityLifeCycleEvent -> {
  10. return activityLifeCycleEvent.equals(event);
  11. });
  12. return sourceObservable.takeUntil(o);
  13. }
  14. };
  15. }
  16. }

这个方法可以用于每一个网络请求 Observable 中,当它监听到特定的 lifecycle event 时,就会自动让网络请求 Observable 终止掉,不会再去监听网络请求结果。

  • 具体使用如下:
  1. userApi.getUser(1)
  2. .compose(bindUntilEvent(ActivityLifeCycleEvent.PAUSE))
  3. .compose(RxUtil.normalSchedulers())
  4. .subscribe(new WebSuccessAction<JsonDataResponse<User>>() {
  5. @Override
  6. public void onSuccess(JsonDataResponse<User> response) {
  7. handleUser(response.getUser());
  8. }
  9. }, new WebFailAction())

利用 .compose(bindUntilEvent(ActivityLifeCycleEvent.STOP)) 来监听 Activity 的 Stop 事件并终止 userApi.getUser(1) 的 subscription,从而防止内存泄漏。

2. 图片优化实践

Android开发者都知道,每个app的可用内存时有限的,一旦内存占用太多或者在主线程突然请求较大内存,很有可能发生 OOM 问题。而其中,图片又是占用内存的大头,因此我们必须采取多种方法来进行优化。

多数情况下我们是从 server 获取一张高清图片下来,然后在内存里进行裁剪成需要的大小来进行显示。这里面存在两个问题,

1:假设我们只需要一张小图,而server取回来的图如果比较大,那就会浪费带宽和内存。

2:如果直接在主线程去为图片请求大块空间,很容易由于系统难于快速分配而 OOM;

比较理想的情况是:需要显示多大的图片,就向server请求多大的图片,既节省用户带宽流量,更减少内存的占用,减小 OOM 的机率。

为了实现 server 端的图片Resize,我们采用了 Thumbor 来提供图片 Resize 的功能。android端只需要提供一个原图片 URL 和需要的 size 信息,就可以得到一张 Resize 好的图片资源文件。具体server端实现这里就不细讲了,感兴趣的读者可以阅读官方文档。

这里介绍下我们在 Android 端的实现,以 Picasso 为栗子。

  • 首先要引入 Square 提供的 pollexor 工具,它可以让我们更简便的创建 thumbor 的规范 URI,参考如下:
  1. thumbor.buildImage("http://example.com/image.png")
  2. .resize(48, 48)
  3. .toUrl()
  • 然后,利用 Picasso 提供的 requestTransformer 来实时获取当前需要显示的图片的真实尺寸,同时设置图片格式为 WebP,这种格式的图片可以保持图片质量的同时具有更小的体积:
  1. Picasso picasso = new Picasso.Builder(context).requestTransformer(new Picasso.RequestTransformer() {
  2. @Override
  3. public Request transformRequest(Request request) {
  4. String modifiedUrl = URLEncoder.encode(originUrl);
  5. ThumborUrlBuilder thumborUrlBuilder = thumbor.buildImage(modifiedUrl);
  6. String url = thumborUrlBuilder.resize(request.targetWidth, request.targetHeight)
  7. .filter(ThumborUrlBuilder.format(ThumborUrlBuilder.ImageFormat.WEBP))
  8. .toUrl();
  9. Timber.i("SponsorAd Image Resize url to " + url);
  10. return request.buildUpon().setUri(Uri.parse(url)).build();
  11. }
  12. }).build();
  • 利用修改后的 picasso 对象来请求图片
  1. picasso.load(originUrl).fit().centerCrop().into(imageView);

利用上面这种方法,我们可以为不同的 ImageView 计算显示需要的真实尺寸,然后去请求一张尺寸匹配的图片下来,节约带宽,减小内存开销。

当然,在应用这种方法的时候,不要忘记考虑服务器的负载情况,毕竟这种方案意味着每张图片会被生成各种尺寸的小图缓存起来,而且Android设备分辨率不同,即使是同一个 ImageView,真实的宽高 Pixel 值也会不同,从而生成不同的小图。

在App和Library中集成依赖注入

依赖注入框架 Dagger 我们很早就开始用了,从早期的 Dagger1 到现在的 Dagger2。虽然 Dagger 本身较为陡峭的学习曲线使得不少人止步,不过一旦用过,根本停不下来。

如果只是在 App 里使用 Dagger 相对比较简单,不过,我们还需要在 Community 和 Base-Android 两个公用 Library 里也集成 Dagger,这就需要费点功夫了。

下面我来逐步讲解下我们是如何将 Dagger 同时集成进 App 和 Library 中。

1. 在App里集成Dagger

首先需要在 GlowApplication 里生成一个全局的 AppComponent

  1. @Singleton
  2. @Component(modules = AppModule.class)
  3. public interface AppComponent {
  4. void inject(MainActivity mainActivity);
  5. }

创建 AppModule

  1. @Module
  2. public class AppModule {
  3. private final LexieApplication lexieApplication;
  4. public AppModule(LexieApplication lexieApplication) {
  5. this.lexieApplication = lexieApplication;
  6. }
  7. @Provides Context applicationContext() {
  8. return lexieApplication;
  9. }
  10. // mock tool object
  11. @Provides Tool provideTool() {
  12. return new Tool();
  13. }
  14. }

集成进 Application

  1. class GlowApplication extends Application {
  2. private AppComponent appComponent;
  3. @Override
  4. public void onCreate() {
  5. appComponent = DaggerAppComponent.builder()
  6. .appModule(new AppModule(this))
  7. .build();
  8. }
  9. public static AppComponent getAppComponent() {
  10. return appComponent;
  11. }
  12. }

在 MainActivity中使用inject 一个 tool 对象

  1. class MainActivity extends Activity {
  2. @Inject Tool tool;
  3. @Override
  4. public void onCreate() {
  5. GlowApplication.getAppComponent().inject(this);
  6. }
  7. }

2. 在 Library 中集成 Dagger

(下面以公用Library:Community为例子)

逆向思维下,先设想应用场景:即 Dagger 已经集成好了,那么我们应该可以按如下方式在 CommunityActivity 里 inject 一个 tool 对象。

  1. class CommunityActivity extends Activity {
  2. @Inject Tool tool;
  3. @Override
  4. public void onCreate() {
  5. GlowApplication.getAppComponent().inject(this);
  6. }
  7. }

关键在于: GlowApplication.getAppComponent().inject(this); 这一句。

那么问题来了:

对于一个 Library 而言,它是无法拿到 GlowApplication 对象的,因为作为一个被别人调用的 Library,它甚至不知道这个上层 class 的存在

为了解决这个问题,我们在community里定义一个公用接口作为中间桥梁,让GlowApplication实现这个公共接口即可。

  1. // 在Community定义接口CommunityComponentProvider
  2. public interface CommunityComponentProvider {
  3. AppComponent getAppComponent();
  4. }
  1. // 每个app的Application类都实现这个接口来提供AppComponent
  2. class GlowApplication implements CommunityComponentProvider {
  3. AppComponent getAppComponent() {
  4. return appComponent;
  5. }
  6. }

然后 CommunityActivity就可以实现如下:

  1. class CommunityActivity extends Activity {
  2. @Inject Tool tool;
  3. @Override
  4. public void onCreate() {
  5. Context applicationContext = getApplicationContext();
  6. CommunityComponentProvider provider = (CommunityComponentProvider) applicationContext;
  7. provider.getAppComponent().inject(this);
  8. }
  9. }

3. 从 AppComponent 抽离 CommunityComponent

  1. provider.getAppComponent().inject(this);

这一句里我们已经实现前半句 provider.getAppComponent() 了,但后半句的实现呢?

正常情况下,我们要把

  1. void inject(CommunityActivity communityActivity);

放入 AppComponent 中,如下:

  1. @Singleton
  2. @Component(modules = AppModule.class)
  3. public interface AppComponent {
  4. void inject(MainActivity mainActivity);
  5. // 加在这里
  6. void inject(CommunityActivity communityActivity);
  7. }

其实这样我们就已经几乎完成了整个 Library 和 App 的依赖注入了。

但细心的朋友应该发现里面存在一个小问题,那就是

  1. void inject(CommunityActivity communityActivity);

这句代码如果放入了 App 里的 AppComponent 里,那就意味着我们也需要在另外三个 App里的 AppComponent 都加上一句相同的代码?这样可以吗?

理论上当然是可行的。但是,从单一职责的角度来考虑,AppComponent 只需要负责 App层的 inject 就行,我们不应该把属于 Community 的 inject 放到App 里,这样的代码太ugly,而且更重要的是,随着 Community 越来越多 Activity 需要 inject ,每个 inject 都要在各个 App 里重复加,这太烦了,也太笨了。

因此,我们采用了一个简洁有效的方法来改进。

在 Community 里创建一个 CommunityComponent,所有属于 Community 的inject 直接写在 CommunityComponent 里,不需要 App 再去关心。与此同时,为了保持前面 provider.getAppComponent() 仍然有效,我们让 AppComponent 继承 CommunityComponent

实现代码如下:

  1. class AppComponent extends CommunityComponent {...}

在 Community 里

  1. class CommunityComponent {
  2. void inject(CommunityActivity communityActivity);
  3. }www.90168.org
  1. class CommunityActivity extends Activity {
  2. @Inject Tool tool;
  3. @Override
  4. public void onCreate() {
  5. Context applicationContext = getApplicationContext();
  6. CommunityComponentProvider provider = (CommunityComponentProvider) applicationContext;
  7. provider.getAppComponent().inject(this);
  8. }
  9. }

Bingo! 至此我们已经能够优雅简洁地在 App 和 Library 里同时应用依赖注入了。

小结

由于篇幅有限,本文暂时先从网络层、内存优化和依赖注入方面进行讲解,之后会再考虑从 Logging模块、数据同步模块、Deep Linking模块、多Library的Gradle发布管理、持续集成和崩溃监测模块等进行讲解。

谢谢!

Glow Android 优化实践的更多相关文章

  1. Android最佳性能实践(三)——高性能编码优化

    在前两篇文章当中,我们主要学习了Android内存方面的相关知识,包括如何合理地使用内存,以及当发生内存泄露时如何定位出问题的原因.那么关于内存的知识就讨论到这里,今天开始我们将学习一些性能编码优化的 ...

  2. Android最佳性能实践(四)——布局优化技巧

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43376527 在前面几篇文章其中.我们学习了怎样通过合理管理内存,以及高性能编码技 ...

  3. 爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

    本文由爱奇艺技术团队原创分享,原题<爱奇艺Android客户端启动优化与分析>. 1.引言 互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Andro ...

  4. 腾讯技术分享:Android版手机QQ的缓存监控与优化实践

    本文内容整理自公众号腾讯Bugly,感谢原作者的分享. 1.问题背景 对于Android应用来说,内存向来是比较重要的性能指标.内存占用过高,会影响应用的流畅度,甚至引发OOM,非常影响用户体验.因此 ...

  5. 直播推流端弱网优化策略 | 直播 SDK 性能优化实践

    弱网优化的场景 网络直播行业经过一年多的快速发展,衍生出了各种各样的玩法.最早的网络直播是主播坐在 PC 前,安装好专业的直播设备(如摄像头和麦克风),然后才能开始直播.后来随着手机性能的提升和直播技 ...

  6. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    在上期<直播推流端弱网优化策略 >中,我们介绍了直播推流端是如何优化的.本期,将介绍手游直播中录屏的实现方式. 直播经过一年左右的快速发展,衍生出越来越丰富的业务形式,也覆盖越来越广的应用 ...

  7. Android最佳性能实践(二)——分析内存的使用情况

    由于Android是为移动设备开发的操作系统,我们在开发应用程序的时候应当始终把内存问题充分考虑在内.虽然Android系统拥有垃圾自动回收机制,但这并不意味着我们就可以完全忽略何时去分配或释放内存. ...

  8. 百度APP移动端网络深度优化实践分享(二):网络连接优化篇

    本文由百度技术团队“蔡锐”原创发表于“百度App技术”公众号,原题为<百度App网络深度优化系列<二>连接优化>,感谢原作者的无私分享. 一.前言 在<百度APP移动端网 ...

  9. 百度APP移动端网络深度优化实践分享(一):DNS优化篇

    本文由百度技术团队“蔡锐”原创发表于“百度App技术”公众号,原题为<百度App网络深度优化系列<一>DNS优化>,感谢原作者的无私分享. 一.前言 网络优化是客户端几大技术方 ...

随机推荐

  1. mongoose学习笔记1--基础知识1

    今天我们将学习Mongoose,什么是Mongoose呢,它于MongoDB又是什么关系呢,它可以用来做什么呢? MongoDB是一个开源的NoSQL数据库,相比MySQL那样的关系型数据库,它更显得 ...

  2. 解决ERROR 2003 (HY000): Can't connect to MySQL server on

    方案一: .打开cmd; .输入命令:net stop +MySQL的服务名,停止MySQL服务,如果未启动MySQL服务则可跳过该步骤: .输入命令:mysqld --remove卸载MySQL服务 ...

  3. 图像特征提取之LBP特征

    LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子:它具有旋转不变性和灰度不变性等显著的优点.它是首先由T. Ojala, M.Pietik?inen ...

  4. python基础——匿名函数

    python基础——匿名函数 当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便.  在Python中,对匿名函数提供了有限支持.还是以map()函数为例,计算f(x)=x2时 ...

  5. 类模板的static成员

    下列代码可以通过编译吗?如何修改使其通过编译? template <class T> struct sum {   static void foo(T op1 , T op2){    c ...

  6. NYOJ题目57 6174问题

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAscAAAJLCAIAAACE5qzaAAAgAElEQVR4nO3dMXKrutvH8XcT6bOQ1C ...

  7. 借助LinkedHashMap实现基于LRU算法缓存

    一.LRU算法介绍 LRU(Least Recently Used)最近最少使用算法,是用在操作系统中的页面置换算法,因为内存空间是有限的,不可能把所有东西都放进来,所以就必须要有所取舍,我们应该把什 ...

  8. JDK、Jmeter、Android环境变量配置

    JDK环境变量 1.在系统变量里点击新建,变量名填写JAVA_HOME,变量值填写JDK的安装路径,在这里就填写"D:\Program Files\Java\jdk1.6.0_26" ...

  9. UVA 10405最长公共子序列

    裸最长公共子序列,直接贴代码 #include<cstdio> #include<iostream> #include<algorithm> #include< ...

  10. Pyqt QListWidget 展示系统环境变量

    今天学习了下Pyqt的 QListWidget 控件 我们先看下这个图片 这张图片就是典型的listWidget效果,我们今天就仿这样布局新建个ListWidget 在网上找了个关于QListWidg ...