本篇文章项目github地址:MVPCommon本博客原地址:

本篇文章项目github地址:MVPCommon本文章原地址:简书博客

本篇文章项目github地址:MVPCommon

目录结构:

由于对之前的项目做出了更改,所以下面内容在2016.8.10做出了更新

金沙国际官网 1项目效果金沙国际官网 2扫描二维码下载应用

金沙国际官网 1项目效果

金沙国际官网 1项目效果

金沙国际官网 ,1.src:编写java代码目录遵循java规范。

本文章原地址:Anthony的博客

在好友的推荐下,准备开始在简书发表博客。停止在csdn那边更新博客。也会将之前在那边写的不错的博客做一个搬家。在写这个系列文章之前,就希望能从成熟的开发框架中引用和编辑的方式来构建一个android应用端的开发框架。这里的开发框架的意思是,搭建出一个以后的项目都可以应用的library库。

1 前言

这篇文章将从webview的基础,介绍到项目中的真实使用。以及怎么样通过注入js脚本的方式来改变网页内容,从而在本地展示新闻体育列表。制作出一个像模像样的app。严正声明,网页数据来自
虎扑体育,仅作学习用途,请勿在任何商业用途中使用。

项目正在改版,即时通讯功能暂时删除了。

2.gen:android资源标识符,自动添加的;不需要程序员维护,不要轻易删除。

1 前言

当然对于MVP的解说也是使用也是层出不穷,我也网络上也能看到各种版本的解说,之前博客也有文章的更新,里面有MVP的详细说明和项目代码—>Android中的MVP模式,带实例。

本篇文章将参考 google官方android
MVP架构项目的实现,来实现自己的项目。或许看了这篇文章之后,你再去梳理一下google官方架构项目,会让你收获更多。官方的实例肯定具有更好的权威性

推荐关注安卓各种架构相关文章合集github地址:AndroidArchitectureCollection

本博客系列文章目的:

1.1
这里面包含了大多数app都需要的一些操作,以后开发的app都可以使用。1.2
利用这里面的思路可以进行自己公司和团队的android客户端框架的搭建

2 webview基础

WebView是手机中内置了一款高性能 webkit 内核浏览器,在 SDK
中封装的一个组件。没有提供地址栏和导航栏,WebView只是单纯的展示一个网页界面。在开发中经常都会用到。

Android4.4引入了新的一个基于Chromium版本的新版本WebView。该变化提高了WebView的性能,并且和最新的Web浏览器支持最新的HTML5,CSS3样式以及Javascript标准。当在
Android
4.4或者更高的版本上面运行时,任何使用WebView的application会继承使用这些特性。本文章主要描述了一下WebView的新特性。如果你设置targetSdkVersion为19或者更高时,那你就需要特别的注意。这里不做过多讲解,参考**
Migrating to WebView in Android 4.4**

金沙国际官网 5

               android Tool->fixProject  2.project->Build    

2 google官方MVP架构解析

搭建框架的思路:

2.1 利用成熟的开源库,开源jar包作为项目的依赖。2.2
将成熟的开源库引入本地库文件,并作出适当的修改。(有时候为了满足项目需求,必须对开源项目作出一定的自定义修改)2.3
利用以往项目经验对分模块分类别封装出一些父类,完成公共操作,后续开发只需要继承这些父类就可以完成大部分操作,并且可以做出适当修改。2.4
为一些常用操作提供模板代码,可以作为后续开发的参考
进入正题:

2.1 添加webview到应用中

直接将webview添加到layout文件中

<?xml version="1.0" encoding="utf-8"?><WebView xmlns:andro android: android:layout_width="fill_parent" android:layout_height="fill_parent"/>

使用webview加载url使用loadUrl方法:

WebView myWebView =  findViewById(R.id.webview);myWebView.loadUrl("http://www.example.com");

AndroidManifest.xml 中必须添加访问网络权限。

<manifest ... > <uses-permission android:name="android.permission.INTERNET" /> ...</manifest>

当下使用的主要有Piccaso、Fresco、Android-Universal-Image-Loader、Glide、Volley这五个图片加载框架。关于这些图片加载框架的对比,网上可以找到很多文章。这里不做过多赘述。具体请参考5中的参考链接,肯定会对你有帮助。

               lib加BulidPath  如加安卓支付  

1 项目目录

打开github,展开项目目录,会发现项目结构的组织方式是按照功能进行分模块的,当然根据个人情况,也可以按照ui,model,view,presenter这种情况进行划分组织目录。

金沙国际官网 6google官方MVP架构目录视图2
具体实现流程

我们将关注度放到具体的一个taskdetail模块当中来解析实现MVP的流程。

金沙国际官网 7taskdetail模块2.1
TaskDetailContract
可以看到这里是通过一个协议类XXXContract来对View和Presenter的接口进行继承。这样做的好处也就是,我们可以将基础的View层的操作放在BaseView里面,对基础的Presenter层的操作放在BasePresenter里面。减少后续代码的重复。一个协议类也将View和Presenter管理起来,方便操作。金沙国际官网 82.2
BaseView
那么来看看BaseView,主要是有一个setPresenter的操作,MVP中Presenter和View层是需要交互的,这里通过setPresenter操作,我们也就可以获得相应的Presenter的实例在View层直接mPresenter.xxx()进行交互了。我们可以在下面的代码中看到官方示例代码是通过在TaskDetailPresenter的构造函数中调用mTaskDetailView.setPresenter完成这一步操作的。

所以在我们自己的代码中,我们也可以将加载的loading,以及加载错误页面,加载失败页面等操作放在BaseView里面,这是每个View都会有的:

金沙国际官网 9

2.3
BasePresenter
BasePresenter中只有一个start方法,表示“开始”,我们可以在这里进行数据加载初始化等。

金沙国际官网 102.3
TaskDetailActivity
可以看到这里这里一个初始化了fragment的activity,主要操作当让是new了一个XXXPresenter。activity在项目中是一个全局的控制者,负责创建view以及presenter实例,并将二者联系起来,金沙国际官网 11

2.4 TaskDetailFragmentFragment是MVP中View的实现类,它不与Model
层进行交互,只和presenter的实例进行交互。

金沙国际官网 122.5
TaskDetailPresenter
Presenter的真正实现类,在这里进行model层和view层的交互。金沙国际官网 13

通过上面的分析,在来梳理一下整个步骤:1
官方MVP实例,通过协议类XXXContract来对View和Presenter的接口进行内部继承。是对BaseView和BasePresenter的进一步封装,所以我们实现的View和Presenter也只需要继承XXXContract中的对应内部接口就行。

2
activity的作用主要是创建View(这里是相应的fragment),以及创建presenter,并把view传递给presenter(完成presenter对view实例关联操作)

3
在presenter的实现类的构造函数中,通过view的setPresenter,让view获得了presenter实例。这样view中就可以对Presenter中的方法进行操作了。(完成view对presenter实例关联操作)

4
在presenter的实现类中,可以对Model数据进行操作。实例中,数据的获取、存储、数据状态变化都是model层的任务,presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。

项目目录结构:

金沙国际官网 14项目整体目录结构可以看出整个工程分为四个部分。没看懂上面第一张图片的意思?那么换一种方式可能更好理解,请继续往下看。android
studio的目录结构中,我们需要添加项目依赖,依赖开源库或者依赖我们自己写的库。具体的依赖关系是如下面两张图片:3.1
library依赖各种开源jar包,开源库,可自定义修改的开源库等:
金沙国际官网 15library依赖各种开源jar包,开源库等可以看到我们直接将所有的复杂的开源库jar包,以及可自定义修改的开源库都放到了library的依赖中,分别对应上图的1,3,
2。自己的项目(可以是我们以后编辑的任何的项目)都可以使用library了。如3.2
app依赖框架library
金沙国际官网 16app依赖框架library这样以后我们依赖的什么v4,v7还是网络上的无论各种网络请求,还是图片加载,都放在library中。我们也只需要整理好library。在后面的项目开发中将会省心很多。

2.2 支持javascript

如果访问的页面中有 Javascript,则 WebView 初始化的时候必须设置支持
Javascript。(WebSetting中提供了很多有用的api, 点击链接查看)

WebView myWebView =  findViewById(R.id.webview);WebSettings webSettings = myWebView.getSettings();webSettings.setJavaScriptEnabled;

通过创建JavaScript的接口可以让JavaScript和安卓客户端进行交互。比如说JavaScript可以调用本地的安卓代码展示一个Dialog而不是使用JavaScript的alter()方法。通过addJavascriptInterface()方法创建接口。示例:定义一个“接口”类

public class WebAppInterface { Context mContext; /** Instantiate the interface and set the context */ WebAppInterface(Context c) { mContext = c; } /** Show a toast from the web page */ @JavascriptInterface public void showToast(String toast) { Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show(); }}

在addJavascriptInterface方法中创建接口

WebView webView =  findViewById(R.id.webview);webView.addJavascriptInterface(new WebAppInterface, "Android");

在HTML的JavaScript中调用WebView显示toast消息,

<input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" /><script type="text/javascript"> function showAndroidToast { Android.showToast; }</script>

注意:1
addJavascriptInterface方法中要绑定的Java对象及方法运行在另外的线程中,而不是运行在构造他的线程中。2
使用addJavascriptInterface将会使javaScript可以操控安卓本地代码。因此有可能导致安全性问题,请确保所有HTML代码都是你所知道的。3
如果targetSdkVersion是17或者更高(android 4.2以上),必须使用
@JavascriptInterface 注解,才能让网页访问到本地代码。

这个段落的答案,摘抄自Stormzhang的文章 如何正确使用开源项目?

3.assets

3 实战应用:

说了这么多,通过一个一个最为简单的spalsh页面的搭建,来完整的使用MVP吧。

3.1
BaseView
我在这里没有添加setPresenter方法,而是将loading,以及加载错误,网络加载错误等页面都放在了这里面。

/** * Created by Anthony on 2016/5/3. * Class Note: * interface for MVP View in all of the project */public interface BaseView { void showMessage(String msg); void close(); void showProgress(String msg); void showProgress(String msg, int progress); void hideProgress(); void showErrorMessage(String msg,String content);}

3.2
BasePresenter
在我的项目中,我采用的是在BasePresenter中获取View的实例,也就是通过下面的attachView方法完成了View和Presenter的关联性。

public interface BasePresenter<T extends BaseView> { void attachView; void detachView();}

这个方法会在相应的Presenter的实现类里面得到实现。而我们需要通过在相应的View类里面通过mPresenter.attachView进行初始化关联,这样就可以在Presenter的实例中使用View的实例。那么上面这个mPresenter实例我是怎么在View的实现类中得到的呢?
答案是使用Dagger2的Inject获取。可以参考我的文章Google官方MVP+Dagger2架构详解
进行Dagger2的学习。

@InjectSplashPresenter mPresenter;

3.3
SplashContract
同样的我们也将采用契约类来完成Presenter和View接口的展现。这里Presenter主要是完成加载数据(在splash页面中加载数据是应用普遍应当考虑的一种方式。)

/** * Created by Anthony on 2016/5/31. * Class Note: * contract class for splash view & presenter */public interface SplashContract { interface Presenter extends BasePresenter<View> { void initData(); } interface View extends BaseView { void toMainActivity(); }}

3.4
SplashContract.View的实现SplashActivity
官方示例代码采用的方式是fragment作为View的实现,这里灵活变通,采用Activity作为MVP中View的实现。这里继承自AbsBaseActivity其中完成了一些初始化操作,将会在另外的文章中进行讲解。这里我们就采用Dagger2进行了SplashPresenter的实例的获取。也实现了toMainActivity方法,用于跳转到主页面。

/** * Created by Anthony on 2016/5/31. * Class Note: * this class is simple but indicates how to use MVP in your project * * implements of splash view */public class SplashActivity extends AbsBaseActivity implements SplashContract.View { @Inject SplashPresenter mPresenter; @Override public void toMainActivity() { Intent intent = new Intent(this, MainActivity.class); startActivity; } @Override protected void initViewsAndEvents() { mPresenter.attachView; mPresenter.initData(); } @Override protected int getContentViewID() { return R.layout.activity_splash; } @Override protected void injectDagger(ActivityComponent activityComponent) { activityComponent.inject; }}

3.5 SplashContract.Presenter
的实现SplashPresenter
这里对Presenter的实现类是SplashPresenter,我们需要实现的方法自然有三个attachView,detachView,以及initData三个方法。

/** * Created by Anthony on 2016/5/31. * Class Note: * presenter for splash view */public class SplashPresenter implements SplashContract.Presenter { private static final short SPLASH_SHOW_SECONDS = 1; private long mShowMainTime; private SplashContract.View mView; private Context mContext; private Subscription mSubscription; @Inject ToastUtils mToastUtil; @Inject DataManager mDataManager; private MyApplication mApplication; @Inject public SplashPresenter(@ActivityContext Context context, MyApplication application) { mContext = context; this.mApplication = application; } @Override public void initData() { mShowMainTime = System.currentTimeMillis() + SPLASH_SHOW_SECONDS * 2000; //load channel list data ,then save to database mSubscription = mDataManager.loadChannelList(Constants.FIRST_MENU_URL) .doOnNext(mDataManager.saveChannelListToDb) .subscribeOn(Schedulers.io .observeOn(AndroidSchedulers.mainThread .subscribe(new HttpSubscriber<List<Channel>>() { @Override public void onNext(List<Channel> channels) {// mApplication.channels = channels;//load to global instance showView(); } @Override public void onError(Throwable e) { super.onError; } /** * menu url to load channels * * @return */// private String getFirstMenuUrl() {// return "raw://news_menu"; //local data fot testing// } private void showView() { AsyncTask<String, String, String> showMainTask = new AsyncTask<String, String, String>() { @Override protected String doInBackground(String[] params) { if (System.currentTimeMillis() < mShowMainTime) { try { long sleepTime = mShowMainTime - System.currentTimeMillis(); if (sleepTime > 0) { Thread.sleep(mShowMainTime - System.currentTimeMillis; } } catch (InterruptedException e) { e.printStackTrace(); } } return null; } @Override protected void onPostExecute { mView.toMainActivity(); mView.close(); } }; showMainTask.execute(); } @Override public void attachView(SplashContract.View view) { mView = view; } @Override public void detachView() { mView = null; if (mSubscription != null && !mSubscription.isUnsubscribed { mSubscription.unsubscribe(); } }}

这里需要关注的点:我们在attackView中完成了view的实例获取mView,以及在需要解除绑定的时候使用detachView方法。这样我们就可以在当前Presenter的全局中使用View的实例了。

 @Override public void attachView(SplashContract.View view) { mView = view; } @Override public void detachView() { mView = null; if (mSubscription != null && !mSubscription.isUnsubscribed { mSubscription.unsubscribe(); } }

注意点:1
细心的你可能会发现,那么上面的所有BaseView的方法在哪里实现呢?答案是所有Activity都需要继承的AbsBaseActivity中

金沙国际官网 17这样我们就可以让所有activity继承AbsBaseActivity,他们都是实现了BaseView的所有方法的状态。这样就提供了统一的界面化处理而且减去了许多重复的代码。2
上面无论是官方的代码还是我们自己的代码,由于使用MVP模式,都会降低模块之间的耦合性。所以这时候能够正确的关联View,Presenter和Model层之间的数据显得尤为的重要。3
对于Model层的数据,我们可以看到我是在initData方法中调用DataManger的loadChannelList进行数据的加载的。关于Model层这方面的内容,已经在文章浅析MVP中model层设计
进行了分析,不再赘述。

如何编辑library里面的框架内容:

4.1 对于开源库,我们可以通过library下面的gradle文件进行修改,以
compile ‘XXX.xxx.xxx’ 这种形势,开源库里面的内容不可更改,

金沙国际官网 18编辑maven开源库当然我们也可以在android
studio中直接进行引入,也可以按照下图的步骤123进行添加:金沙国际官网 19maven库添加步骤4.2
对于开源jar包,
就直接放在library-libs文件夹下了。同样支持gradle文件和project
structure中编辑的形式。金沙国际官网 20第三方jar包引入4.3
对于可自定义修改的开源库。
这里直接上github下载最新版本的各种开源库,添加到ThirdPart
文件夹中,同样支持上面的两种编辑方式。金沙国际官网 21可编辑的开源库

2.3 webview页面处理

当用户点击webview中的网页链接的时候,安卓系统默认会启动一个新的应用专门成立url的跳转。如果希望点击链接继续在当前webview中响应,而不是新开Android的系统browser中响应该链接,必须覆盖
WebView的WebViewClient对象. 并重写shouldOverrideUrlLoading方法。

WebView myWebView =  findViewById(R.id.webview);myWebView.setWebViewClient.(new WebViewClient();private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (Uri.parse.getHost().equals("www.example.com")) { // This is my web site, so do not override; let my WebView load the page return false; } // Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse; startActivity; return true; }}

计算机史上有个万能的解决方案就是,如果原有层面解决不了问题,那么就请再加一层!

原生文件 音乐  视频  图片。

4 参考资料:

Android官方MVP架构示例项目解析AndroidArchitectureCollection

android studio注意事项:

5.1
使用本地jar没能编译的问题
在AS中使用本地jar非常简单,只需要将jar文件放到libs目录下,然后点下工具栏中的Sync
Project with Gradle Files图标即可。刚放到libs目录下,尚未编译

金沙国际官网 22尚未编译的jar包按下工具栏中的Sync
Project with Gradle
Files图标,同步项目金沙国际官网 23Sync
Project with Gradle
Files编译好之后如下图所示:金沙国际官网 24已经编译的jar包有没有正确编译的标志就是有没有出现三角箭头5.2
怎么让一个module成为library
module的问题
从github上面复制的项目过来可能不会有左边那个带有三个柱形图的小符号,而是普通文件夹的形式。所以需要做出两个配置,看下面两张图片金沙国际官网 25柱形图标代表library(在setting.gradle
中进行如图的配置)金沙国际官网 26library的gradle代码(在每个开源库的library中的gradle文件中,进行配置)apply
plugin参数的值是com.android.application,就说明这是个app。如果apply
plugin参数的值是com.android.library,就说明这是个library。5.3
关于maven库,jar包,开源包,aar的区别。
请参考这篇文章,点击这里好了,项目的目录结构就介绍到这里,在这里再返回去看一下第一张图片,是不是瞬间明朗很多了。

2.4 webview的页面导航处理

当webview的url进行跳转的时候,自动回记录进入历史界面,可以使用goBack()方法和goForward()方法,进行返回和前进。webview的canGoBack方法将会判断历史记录中是否还有页面,如果还有上一级页面,将会返回true。

@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) { // Check if the key event was the Back button and if there's history if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack { myWebView.goBack(); return true; } // If it wasn't the Back key or there's no web page history, bubble up to the default // system behavior (probably exit the activity) return super.onKeyDown(keyCode, event);}

对于开源项目,我们知道有些库设计的确实很棒,使用者调用起来非常方便,一行代码直接搞定,拿图片加载库
Picasso 举个例子:

4.bin

3 webview在项目中使用

如果还需要了解更多,推荐4
参考链接中的深入讲解Webview。这里接着介绍WebView在我项目中的使用。

Picasso.with.load.into(imageView);

包含临时编译生成apk的应用程序。

3.1 需求是什么?

当然在我项目中,最开始想做的一个体育新闻资讯,但是苦于自己没有服务器,所以一开始都是模拟的数据。网上的开源api也是将所有新闻数据糅合在一起的,怎么分开呢?比如nba,英超,欧冠,各种不同的新闻数据在哪里获取呢?作为一个体育爱好者,经常会去的虎扑体育,然后找到了手机版的
虎扑体育。这不就是我想要的体育新闻数据嘛,OO哈哈~。但是我不想要这个头部和底部呢
,接下来分析怎么处理,很简单的实现。

金沙国际官网 27金沙国际官网 28

使用起来是不是特简单?你也许问我,都封装的这么好了还用得着再封装一层么?那你错了,哪怕他已经很完美了,我都会这么做:

5.libs

3.2 效果怎么样?

看看运行效果,还是有模有样的

金沙国际官网 29

public class ImageLoader { public static void with(Context context, String imageUrl, ImageView imageView) { Picasso.with.load.into(imageView); }}

第三方类库的jar包,尽量不要太多

3.3 怎么做?

主要就是进行了新闻网页的展示,并且去掉了头部和底部。

public class WebViewFragment extends AbsTitleFragment { ...... R.layout.fragment_web_view; ...... @Override protected void initViewsAndEvents(View rootView) { mWebView.setVisibility(View.INVISIBLE); toggleShowLoading(true, "loading"); setWebViewOption; if (getFragmentUrl() != null) { mWebView.loadUrl(getFragmentUrl; } } private void setWebViewOption(WebView webview) { ...... //设置是否支持运行JavaScript,仅在需要时打开 webview.getSettings().setJavaScriptEnabled; ...... //设置WebViewClient webview.setWebViewClient(new MyWebViewClient; webview.setWebChromeClient(new MyWebChromeClient; } private class MyWebViewClient extends WebViewClient { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {// view.loadUrl; Intent intent = new Intent(mContext, WebViewActivity.class); intent.putExtra(WebViewActivity.WEB_VIEW_URL, url); startActivity; return true; } ...... @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); toggleShowLoading(false, ""); if (view.getVisibility() == View.INVISIBLE) { view.setVisibility(View.VISIBLE); } } ...... } private class MyWebChromeClient extends WebChromeClient { ...... @Override public void onProgressChanged(WebView view, int newProgress) { if (newProgress > 25) { injectJS; } super.onProgressChanged(view, newProgress); } } private void injectJS(WebView webview) { webview.loadUrl("javascript:(function() " + "{ " + "document.getElementsByClassName('m-top-bar')[0].style.display='none'; " + "document.getElementsByClassName('m-footer')[0].style.display='none';" + "document.getElementsByClassName[0].style.display='none';" + "}); }}

可以看到WebView的初始化在setWebViewOption方法中进行。其中需要关注的有下面三行代码

金沙国际官网 30

上面在WebSetting方法中WebViewClient的初始化,这里主要是点击新闻进行页面跳转的处理。

金沙国际官网 31提供WebViewClient并重写shouldOverrideUrlLoading方法,并返回truewebview的返回键必须做处理,如果当前webview支持返回(不是点进来的第一个webview,而是再次跳转的),则返回。如果不支持返回(第一个webview界面),则finish当前的页面。金沙国际官网 32

初始化WebView的时候也进行了WebChromeClient的初始化。其中主要是在当进度超过百分之25的时候注入了一段javaScript代码。通过javascript代码去掉了当前网页的头部和底部。

金沙国际官网 33

这样我所有项目调用的方式直接就是 ImageLoader.with() ,这样做的好处是:

6.res  :图像、布局、按钮

3.4 为什么这么做?

上面就是通过对网页信息注入js的形式,隐藏了相应的属性。如图,去掉标题栏,在网页中通过F12打开开发者工具,发现顶部栏的class的名字是m-top-bar,所以通过注入脚本的时候直接调用了这个class并且将display置为none。对这方面不了解的可以学习一下Html5相关的知识。

金沙国际官网 34

至于为什么是在进度条进行25之后进行注入,这里主要是在界面加载完成后再进行注入js就会有一定的卡顿闪屏现象。可以自己实践一下。

本篇文章项目github地址:MVPCommon

注意:由于项目构建的需要,关于webview在本项目中利用js注入实现网页列表的效果将在本项目中移除。(2016.7.22)

入口统一,所有图片加载都在这一个地方管理,一目了然,即使有什么改动我也只需要改这一个类就可以了。

drawable-xxx:分辨率

4 参考链接

Web Apps->1 Supporting Different Screens from Web Apps->2
Building Web Apps in WebView
->3 Migrating to WebView in Android
4.4
->4 Debugging Web Apps->5 Best Practices for Web Apps

深入讲解WebView-上深入讲解WebView-下

随着你们业务的需求,发现 Picasso
这个图片加载库已经满足不了你们了,你们需要换成 Fresco
,如果你没有封装一层的话,想要替换这个库那你要崩溃了,要把所有调用
Picasso
的地方都改一遍,而如果你中间封装了一层,那真的非常轻松,三天两头的换一次也没问题。

layout:布局

这就是所谓的外部表现一致,内部灵活处理原则。

meau:组件

这里提供我平常开发用到的两个需求:

values:国际化

3.1
图片封装,提供统一入口。
封装成熟的图片框架,也就解决了这些问题:(内存缓存,磁盘缓存,网络加载的结合,利用采样率对图片进行一定的压缩,高效加载bitmap,图片的加载策略,缓存策略,图片错位
,优化列表的卡顿)

7.AndroidManifest.xml  类似java 的主方法!

3.2 提供wifi下加载图片开关,非wifi下显示占位图片。

包名

4.1 整体目录

APK的版本

在我的mvp搭建的项目中,imageloader放在和activity,fragment同级的widget下面。当然后续也会不断的添加widget,比如这里的loading,netstatus,progress(Material
进度条),swipeback等。

APK 图片文字

金沙国际官网 35整体目录结构

相关授权

4.2 ImageUtil类

8.android 的四大组件

作为整个ImageLoader的公共访问入口,以后使用的方式,将会是

Activities

ImageLoader imageLoader =new ImageLoader.Builder().url("img url").imgView.build();ImageLoaderUtil.getInstance().loadImage(context,imageLoader);

Server

这种形式。可以看到ImageUtil提供的是单例模式,进行了封装(后期引入Dagger2
之后直接会修改使用ImageLoaderUtil实例的方式)。全局应该只提供一个ImageLoader的实例,因为图片加载中又有线程池,缓存系统和网络请求等,很消耗资源,所以不可能让它构造多个实例。

Intent

package edu.com.base.ui.widget.imageloader;import android.content.Context;/** * Created by Anthony on 2016/3/3. * Class Note: * use this class to load image,single instance */public class ImageLoaderUtil { public static final int PIC_LARGE = 0; public static final int PIC_MEDIUM = 1; public static final int PIC_SMALL = 2; public static final int LOAD_STRATEGY_NORMAL = 0; public static final int LOAD_STRATEGY_ONLY_WIFI = 1; private static ImageLoaderUtil mInstance; private BaseImageLoaderStrategy mStrategy; private ImageLoaderUtil(){ mStrategy =new GlideImageLoaderStrategy(); }//single instance public static ImageLoaderUtil getInstance(){ if(mInstance ==null){ synchronized (ImageLoaderUtil.class){ if(mInstance == null){ mInstance = new ImageLoaderUtil(); return mInstance; } } } return mInstance; } public void loadImage(Context context,ImageLoader img){ mStrategy.loadImage(context,img); } public void setLoadImgStrategy(BaseImageLoaderStrategy strategy){ mStrategy =strategy; }}

bordcast

4.3 BaseImageLoaderProvider类

可以看到我们ImageUtil中是采用这个类的loadImage方法去加载图片的。这里是一个接口。由具体的子类(GlideImageLoaderProvider)去实现loadImage方法。(这里我也添加了PicassoImageLoaderStrategy方法,但其中的loadImage方法是空的实现)。

package edu.com.base.ui.widget.imageloader;import android.content.Context;/** * Created by Anthony on 2016/3/3. * Class Note: * abstract class/interface defined to load image * (Strategy Pattern used here) */public interface BaseImageLoaderStrategy { void loadImage(Context ctx, ImageLoader img);}

4.4 GlideImageLoaderProvider类

是BaseImageLoaderProvider的实现类,完成具体的加载图片操作。这里面会有wifi下加载图片的判断。具体判断将放在util工具类中进行实现。这里也是利用图片加载库Glide进行实现。后期如果工程项目决定使用其他的图片加载框架,当然可以采用其他类继承BaseImageLoaderProvider。

package edu.com.mvplibrary.ui.widget.imageloader;import android.content.Context;import com.bumptech.glide.Glide;import com.bumptech.glide.Priority;import com.bumptech.glide.load.data.DataFetcher;import com.bumptech.glide.load.engine.DiskCacheStrategy;import com.bumptech.glide.load.model.stream.StreamModelLoader;import java.io.IOException;import java.io.InputStream;import edu.com.mvplibrary.AbsApplication;import edu.com.mvplibrary.util.AppUtils;import edu.com.mvplibrary.util.SettingUtils;/** * Created by Anthony on 2016/3/3. * Class Note: * provide way to load image */public class GlideImageLoaderProvider implements BaseImageLoaderProvider { @Override public void loadImage(Context ctx, ImageLoader img) { boolean flag= SettingUtils.getOnlyWifiLoadImg; //如果不是在wifi下加载图片,直接加载 if{ loadNormal; return; } int strategy =img.getStrategy(); if(strategy == ImageLoaderUtil.LOAD_STRATEGY_ONLY_WIFI){ int netType = AppUtils.getNetWorkType(AbsApplication.app; //如果是在wifi下才加载图片,并且当前网络是wifi,直接加载 if(netType == AppUtils.NETWORKTYPE_WIFI) { loadNormal; } else { //如果是在wifi下才加载图片,并且当前网络不是wifi,加载缓存 loadCache; } }else{ //如果不是在wifi下才加载图片 loadNormal; } } /** * load image with Glide */ private void loadNormal(Context ctx, ImageLoader img) { Glide.with.load(img.getUrl.placeholder(img.getPlaceHolder.into(img.getImgView; } /** *load cache image with Glide */ private void loadCache(Context ctx, ImageLoader img) { Glide.with.using(new StreamModelLoader<String>() { @Override public DataFetcher<InputStream> getResourceFetcher(final String model, int i, int i1) { return new DataFetcher<InputStream>() { @Override public InputStream loadData(Priority priority) throws Exception { throw new IOException(); } @Override public void cleanup() { } @Override public String getId() { return model; } @Override public void cancel() { } }; } }).load(img.getUrl.placeholder(img.getPlaceHolder.diskCacheStrategy(DiskCacheStrategy.ALL).into(img.getImgView; }}

4.5 ImageLoader类

在ImageUtil的load方法中进行图片加载,第一个参数是Context,那么第二个参数呢?正是这里的ImageLoader,采用Builder建造者模式。Builder模式可以将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以构建不同的对象。

why builder
pattern?
因为在图片加载中,会处理到的数据必定有图片的url,必定有ImageView的实例,可能有加载策略(是否wifi下加载),可能有图片加载类型,也会有图片加载没有成功时候的占位符。那么这么多数据操作,所以用到了Builder模式,一步一步的创建一个复杂对象的创建者模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构建流程。比如这里的ImageLoader。

package edu.com.base.ui.widget.imageloader;import android.widget.ImageView;import edu.com.mvplibrary.R;/** * Created by Anthony on 2016/3/3. * Class Note: * encapsulation of ImageView,Build Pattern used */public class ImageLoader { private int type; //类型  private String url; //需要解析的url private int placeHolder; //当没有成功加载的时候显示的图片 private ImageView imgView; //ImageView的实例 private int wifiStrategy;//加载策略,是否在wifi下才加载 private ImageLoader(Builder builder) { this.type = builder.type; this.url = builder.url; this.placeHolder = builder.placeHolder; this.imgView = builder.imgView; this.wifiStrategy = builder.wifiStrategy; } public int getType() { return type; } public String getUrl() { return url; } public int getPlaceHolder() { return placeHolder; } public ImageView getImgView() { return imgView; } public int getWifiStrategy() { return wifiStrategy; } public static class Builder { private int type; private String url; private int placeHolder; private ImageView imgView; private int wifiStrategy; public Builder() { this.type = ImageLoaderUtil.PIC_SMALL; this.url = ""; this.placeHolder = R.drawable.default_pic_big; this.imgView = null; this.wifiStrategy = ImageLoaderUtil.LOAD_STRATEGY_NORMAL; } public Builder type { this.type = type; return this; } public Builder url(String url) { this.url = url; return this; } public Builder placeHolder(int placeHolder) { this.placeHolder = placeHolder; return this; } public Builder imgView(ImageView imgView) { this.imgView = imgView; return this; } public Builder strategy(int strategy) { this.wifiStrategy = strategy; return this; } public ImageLoader build() { return new ImageLoader; } }}

4.6 策略模式的使用上面的图片加载用到了策略模式。

策略模式是指定义了一系列的算法,并将每一个算法封装起来(比如上面的Picasso和Glide),而且他们还可以互相替换。策略模式让他的算法独立于使用它的客户而独立变化。

一起看看整个图片加载封装的类图。

金沙国际官网 36

这里真是利用同样是图片加载,我们可以将不同的加载方式抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现方式,这样我们的客户端,也就是我们利用ImageLoaderUtil类加载图片的时候,就可以实现不同的加载策略。我们也可以通过

public void setLoadImgStrategy(BaseImageLoaderStrategy strategy){ mStrategy =strategy;}

来传入不同的加载策略,实现了策略的动态替换。也就提高了后期的可扩展性和可维护性。

5 参考链接

Android 几个图片缓存原理、特性对比

Introduction to Glide, Image Loader Library for Android, recommended by
Google

FaceBook推出的Android图片加载库-Fresco

StackOverflow–>Picasso v/s Imageloader v/s Fresco vs Glide

本篇文章项目github地址:MVPCommon

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图