# 第一天教程 # U-I-L 的使用需要手动下载一个 jar 包 [U-I-L 项目地址](https://github.com/nostra13/Android-Universal-Image-Loader) 其次我们在 ``build.gradle`` 导入相对应的包: compile 'com.android.support:support-v4:23.4.0' compile 'com.android.support:appcompat-v7:23.4.0' // 友盟导入(注:友盟的使用不在教程范围内) compile files('libs/utdid4all-1.0.4.jar') compile files('libs/umeng-analytics-v6.0.1.jar') // UIL 导入 compile files('libs/universal-image-loader-1.9.5.jar') // 内存泄漏检测工具 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1' // or 1.4-beta1 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1' // 官方 material design 控件库 compile 'com.android.support:design:23.4.0' // OkHttp compile 'com.squareup.okhttp3:okhttp:3.4.1' // Gson compile 'com.google.code.gson:gson:2.7' // 轮播器 compile 'com.bigkoo:convenientbanner:2.0.5' 完成以上工作就可以对我们的项目进行实现了,首先对 ``AndroidManiFfest.xml`` 写入相对应的权限 对于 U-I-L 框架,我们需要在 Application 中初始化它的配置,这样方便我们在全局中都可以直接使用: public class GankOrApplication extends Application { public static Context mContext; @Override public void onCreate() { super.onCreate(); initImageLoader(getApplicationContext()); LeakCanary.install(this); mContext = getApplicationContext(); } private void initImageLoader(Context context) { ImageLoaderConfiguration configuration = ImageLoaderConfiguration.createDefault(context); ImageLoader.getInstance().init(configuration); } } U-I-L 有一个默认构建配置的方法,基本上可以满足我们的软件需求,当然 U-I-L 也是支持高度定制自己的配置,详情请[见此](https://github.com/nostra13/Android-Universal-Image-Loader/wiki/Configuration) 软件使用中,我们需要对所接收到的图片进行缓存,同样的 U-I-L 已经帮我们封装的很好,我们可以创建一个自己的工具类,对需要缓存并显示的图片进行简易的封装: public class ImageUtil { private static ImageUtil instance = null; private ImageLoader mImageLoader; private ImageUtil() { mImageLoader = ImageLoader.getInstance(); } public static synchronized ImageUtil getInstance() { if (instance == null) { instance = new ImageUtil(); } return instance; } // 显示图片 public void displayImage(String url, ImageView view) { DisplayImageOptions options = new DisplayImageOptions.Builder() .cacheInMemory(true) .cacheOnDisk(true) .build(); mImageLoader.displayImage(url, view, options); } // 添加加载时显示图片,可有效解决快速滑动 recyclerView 异步加载图片错位 public void displayImageOnLoading(String url, ImageView view) { DisplayImageOptions options = new DisplayImageOptions.Builder() .cacheInMemory(true) .cacheOnDisk(true) .showImageOnLoading(R.drawable.download_defualt) .showImageForEmptyUri(R.drawable.download_defualt) .build(); mImageLoader.displayImage(url, view, options); } // 取出缓存图片路径 public String getAbsolutePath(String url) { return mImageLoader.getDiskCache().get(url).getAbsolutePath(); } // 判断是否有缓存 public boolean isExist(String url) { return mImageLoader.getDiskCache().get(url).exists(); } // 保存图片 public void saveImage(Activity activity, String name, String url) { // 6.0 检查权限 if (Build.VERSION.SDK_INT >= 23) { int write = activity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); int read = activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE); if (write != PackageManager.PERMISSION_GRANTED || read != PackageManager .PERMISSION_GRANTED) { activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest .permission.READ_EXTERNAL_STORAGE}, 300); } } File saveFile = new File(Environment.getExternalStorageDirectory(), "GankOr"); if (!saveFile.exists()) { saveFile.mkdirs(); } if (copyImage(saveFile.getAbsolutePath() + "//" + name + ".jpg", url)) { LazyUtil.showToast(String.format(activity.getString(R.string.picture_save_on), saveFile .getAbsolutePath())); } else { LazyUtil.showToast("保存失败"); } } // 保存图片 public boolean copyImage(String newPath, String url) { String oldPath = ImageUtil.getInstance().getAbsolutePath(url); InputStream inStream = null; FileOutputStream fs = null; try { int bytesum = 0; int byteread = 0; File oldfile = new File(oldPath); if (oldfile.exists()) { //文件存在时 inStream = new FileInputStream(oldPath); //读入原文件 fs = new FileOutputStream(newPath); byte[] buffer = new byte[1024]; while ((byteread = inStream.read(buffer)) != -1) { bytesum += byteread; //字节数 文件大小 fs.write(buffer, 0, byteread); } } return true; } catch (Exception e) { e.printStackTrace(); } finally { LazyUtil.close(inStream); LazyUtil.close(fs); } return false; } } 简单地构建一个单例模式来获取 ImageLoader 对象,当然,嫌麻烦的可以直接将 ``display()`` 方法设置为 ``static``。在这里新手会很好奇,为什么还要做一遍封装,而不是拿来直接使用 ``ImageLoader.displayImage()`` 诸如此类的呢?现在假设一个场景,假设我当前没有做二次封装,直接使用 ImageLoader,当临时我们需要换掉这个库,例如使用 [picasso](https://github.com/square/picasso) 或者 [glide](https://github.com/bumptech/glide) 等第三方库来替换我们的 ImageLoader,那么我们就要在项目里面,找到所有使用过 ImageLoader 的地方,然后更改参数,对象名等等。但是我做了二次封装之后就不会这样麻烦,我只需要把当前的 ImageUtil 里面的实现改成 [picasso](https://github.com/square/picasso) 或者 [glide](https://github.com/bumptech/glide) 即可,甚至连参数都并不会有很大改动。这就是封装的特性:是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。这样说很复杂,简而言之,所有需要调用 ImageUtil 的地方,只需要传入相应的参数,但是具体内部的实现是对外不公布的。 这样我们就可以在相对应需要使用 ImageLoder 的地方调用如下代码: ImageUtil.getInstance.displayImage(String url, ImageView view); 同理,我们对 OkHttp 也进行简单地封装: public class OkUtil { private static OkUtil instance = null; private Call mCall; private OkHttpClient mOkHttpClient; private Handler mHandler; private Gson mGson; private OkUtil() { mOkHttpClient = new OkHttpClient(); mHandler = new Handler(Looper.getMainLooper()); mGson = new Gson(); } public static synchronized OkUtil getInstance() { if (instance == null) { instance = new OkUtil(); } return instance; } // 获取知乎 Gson public void okHttpZhihuGson(String url, final ResultCallback callback) { final Request request = new Request.Builder() .url(API.ZHIHU_BASIC_URL + url) .build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { callback.onError(call, e); } @Override public void onResponse(final Call call, Response response) throws IOException { final String string = response.body().string(); final Object o = mGson.fromJson(string, callback.mType); mHandler.post(new Runnable() { @Override public void run() { callback.onResponse(o, string); } }); } }); } // 获取知乎 JsonObject public void okHttpZhihuJObject(String url, final String object, final JObjectCallback callback) { final Request request = new Request.Builder() .url(API.ZHIHU_BASIC_URL + url) .build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { callback.onFailure(call, e); } @Override public void onResponse(final Call call, Response response) throws IOException { try { JSONObject jsonObject = new JSONObject(response.body().string()); final String string = jsonObject.getString(object); mHandler.post(new Runnable() { @Override public void run() { callback.onResponse(call, string); } }); } catch (JSONException e) { e.printStackTrace(); } } }); } // 获取 Gank Gson public void okHttpGankGson(String url, final ResultCallback callback) { final Request request = new Request.Builder() .url(API.GANK_BASIC_URL + url) .build(); mCall = mOkHttpClient.newCall(request); mCall.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { callback.onError(call, e); } @Override public void onResponse(final Call call, Response response) throws IOException { if (response.isSuccessful()) { final String string = response.body().string(); final Object o = mGson.fromJson(string, callback.mType); mHandler.post(new Runnable() { @Override public void run() { callback.onResponse(o, string); } }); } } }); } // 取消全部网络请求 public void cancelAll(OkUtil instance) { if (instance != null && mCall != null && !mCall.isCanceled()) { instance.mCall.cancel(); } } public interface JObjectCallback { void onFailure(Call call, IOException e); void onResponse(Call call, String jObjectUrl); } public static abstract class ResultCallback { Type mType; public ResultCallback() { mType = getSuperclassTypeParameter(getClass()); } static Type getSuperclassTypeParameter(Class subclass) { Type superclass = subclass.getGenericSuperclass(); if (superclass instanceof Class) { throw new RuntimeException("Missing type parameter."); } ParameterizedType parameterized = (ParameterizedType) superclass; return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); } public abstract void onError(Call call, Exception e); public abstract void onResponse(T response, String json); } } 关于 okhttp 的学习,以及它的封装,鸿洋的这篇[博客](http://blog.csdn.net/lmj623565791/article/details/47911083)还是相当不错的。我也是简单地参考了一部分,对其中的一部分进行了截取,经过简易地封装后,假设我们需要对知乎的 json 进行解析,那么可以这样使用: // 获取知乎最新消息 OkUtil.getInstance().okHttpZhihuGson(API.ZHIHU_NEWS_LATEST, new OkUtil .ResultCallback() { @Override public void onError(Call call, Exception e) { e.printStackTrace(); } @Override public void onResponse(ZhihuDailyNews response) { ZhihuDailyNews news = response; // 知乎头条消息 mTopStories = news.getTopStories(); } }); } 第一个参数传入相应的 url,第二部分传入一个 ``ResultCallback<实体类>`` 参数就可以回调了 最后,我们再构建一个 ``NetUtil``,检查当前网络是否连接,代码如下: public class NetUtil { public static boolean isNetConnect(@NonNull Context context) { ConnectivityManager service = (ConnectivityManager) context.getApplicationContext().getSystemService(Context .CONNECTIVITY_SERVICE); NetworkInfo info = service.getActiveNetworkInfo(); return info != null && info.isAvailable(); } } 第三个封装的是缓存类 [ASimpleCache](https://github.com/yangfuhai/ASimpleCache),首先我们要下载 ASimpleCache 到我们的项目下,接下来封装代码如下: /** * 基于 ASimpleCache 的二次封装 * Created by joker on 2016/8/14. */ public class CacheUtil { private static CacheUtil instance = null; private ASimpleCache mCache; private CacheUtil(Context context) { mCache = ASimpleCache.get(context); } public static synchronized CacheUtil getInstance(Context context) { if (instance == null) { instance = new CacheUtil(context); } return instance; } // ASimpleCache 中的源码 public String json2String(String json) { BufferedReader in = null; try { in = new BufferedReader(new StringReader(json)); String readString = ""; String currentLine; while ((currentLine = in.readLine()) != null) { readString += currentLine; } return readString; } catch (IOException e) { e.printStackTrace(); return null; } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } } public boolean isCacheEmpty(String key) { return TextUtils.isEmpty(mCache.getAsString(key)); } public boolean isNewResponse(String key, String newJson) { return (!TextUtils.isEmpty(mCache.getAsString(key)) && !mCache.getAsString(key).equals(json2String(newJson))) || TextUtils.isEmpty(mCache .getAsString(key)); } public String getAsString(String key) { return mCache.getAsString(key); } public void put(String key, String value) { mCache.put(key, value); } } 这是国内一位开发者所构建的第三方库,其实说是库,不如说就是一个文件,它轻量到只有一个 java 文件构成,对于这种小型应用,也是能够基本上满足应用需求了。 然后就是我们的 API 了: public class API { // Gank API public static final String GANK_BASIC_URL = "http://gank.io/api/data/"; public static final String GANK_WELFARE = "福利/15/"; public static final String GANK_VIDEO = "休息视频/15/"; public static final int GANK_FIRST_PAGE = 1; // 知乎 API public static final String ZHIHU_BASIC_URL = "http://news-at.zhihu.com/api/"; public static final String ZHIHU_START = "4/start-image/1080*1776"; public static final String ZHIHU_LATEST = "latest"; public static final String ZHIHU_BEFORE = "before/"; public static final String ZHIHU_NEWS_FOUR = "4/news/"; public static final String ZHIHU_NEWS_TWO = "2/news/"; public static final String ZHIHU_HOT_NEWS = "3/news/hot"; } 对于我们常用的几个方法,我也进行了建议封装,取名为 LazyUtil 了,更为大型的项目中,应该严格秉持着单一原则,即一个类只干一件事,充分解耦,LazyUtil 代码如下: public class LazyUtil { private static Toast toast; // toast 优化显示 public static void showToast(String content) { if (toast == null) { toast = Toast.makeText(GankOrApplication.mContext, content, Toast.LENGTH_SHORT); } else { toast.setText(content); } toast.show(); } // Closeable close() 方法封装,增强代码可读性 public static void close(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException e) { e.printStackTrace(); } } public static void log(String log) { Log.e("TAG", log); } public static void log(String tag, String log) { Log.e(tag, log); } } 接下来我们构建基类 Acitivity,这是一个良好的习惯,在该基类中我们主要用于重写一些共有的逻辑,好处是显而易见的对于一些Activity的共有逻辑我们不必要在每个Activity中都重新写一遍,只需要在基类Activity中写一遍就好了。同样地,关于基类 Activity 的学习,我这里也有一篇[文章](http://mp.weixin.qq.com/s?__biz=MzAxMTI4MTkwNQ==&mid=2650820702&idx=1&sn=f58abdeeb6453d73be2031e5ba736add&scene=23&srcid=0808aE9QWenHbRTqj5rzuOnb#rd)推荐 基类 Activity 代码如下: public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LazyUtil.log(getClass().getName(), " onCreate"); setActivityState(this); initView(savedInstanceState); initData(); } public void setActivityState(Activity activity) { // 设置 APP 只能竖屏显示 activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } protected void initData() { } protected abstract void initView(Bundle savedInstanceState); public boolean isNetConnect() { return NetUtil.isNetConnect(this); } @Override protected void onStop() { super.onStop(); LazyUtil.log(getClass().getName(), " onStop"); } @Override protected void onStart() { super.onStart(); LazyUtil.log(getClass().getName(), " onStart"); } @Override protected void onDestroy() { super.onDestroy(); LazyUtil.log(getClass().getName(), " onDestroy"); } @Override protected void onResume() { super.onResume(); LazyUtil.log(getClass().getName(), " onResume"); } @Override protected void onRestart() { super.onRestart(); LazyUtil.log(getClass().getName(), " onRestart"); } @Override protected void onPause() { super.onPause(); LazyUtil.log(getClass().getName(), " onPause"); } } 我在这里对 Activity 的生命周期进行了覆写,打印相对应的日志,对于后期排解 bug 有很大的帮助。 同样地,我们也构建一个基类 Fragment,代码如下: public class BaseFragment extends Fragment { protected BaseActivity mActivity; public BaseFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mActivity = (BaseActivity) getActivity(); return super.onCreateView(inflater, container, savedInstanceState); } } 我们只是在 onCreateView 方法中获取到了当前与 fragment 相关的 activity,到这里,基类 activity 和 fragment 的封装就已经完成了。对于后期的 activity 和 fragment 基本上继承自基类就可以了。