Flesh

Project Url: Kerr1Gan/Flesh
Introduction: Android 上福利满满的 app,宅男神器
More: Author   ReportBugs   
Tags:

果肉一款由 Kotlin 实现的 app,数据源mzitu,MD 风格的界面。

如果你是一位想学习一下 Kotlin 的同学,那么绝对不要错过 Flesh。如 Kotlin 所说它与 Java 完美兼容,所以这里有 Kotlin 调用 Java,同时也有 Java 调用 Kotlin。数据是从网站上爬取的所以这里也有爬虫骚操作。果肉将会不定期更新,增加更多福利。

先上福利。Release1.1

fuli

更新日志

向下兼容至 Android4.1 版本 --2018.1.29

特点

  1. 列表显示图片,点击查看更多。
  2. 快速跳转至顶部,底部,指定位置。
  3. 收藏,查看历史记录。
  4. 设置壁纸。
  5. 离线缓存。

组成

  1. 语言:Kotlin,Java
  2. 网络请求:HttpUrlConnection
  3. 数据库:Sqlite
  4. 数据源:Jsoup
  5. 第三方库:Glide

概述

1) 网络请求

网络框架并没有使用 RxRetrofit 等,为了保证精简高效直接使用的 HttpUrlConnection

  • get
    val request = AsyncNetwork()
    request.request(Constants.HOST_MOBILE_URL, null)
    request.setRequestCallback(object : IRequestCallback {
      override fun onSuccess(httpURLConnection: HttpURLConnection?, response: String) {
          //todo
      }
    })
    
  • post
    val request = AsyncNetwork()
    request.request(Constants.HOST_MOBILE_URL, mutableMapOf())
    request.setRequestCallback(object : IRequestCallback {
      override fun onSuccess(httpURLConnection: HttpURLConnection?, response: String) {
          //todo
      }
    })
    
    2) 数据库

数据库没有使用第三方框架,直接使用的 sql 语句。

CREATE TABLE tb_class_page_list ( 
                    _id           INTEGER PRIMARY KEY ASC AUTOINCREMENT,
                    href          STRING  UNIQUE,
                    description   STRING,
                    image_url     STRING,
                    id_class_page INTEGER REFERENCES tb_class_page (_id) ON DELETE CASCADE ON UPDATE CASCADE,
                    [index]       INTEGER);

3) 读写缓存

Serializable 的效率远低于 Parcelable,所以采用 Parcelable 实现的缓存机制,速度快了大概 7,8 倍。

  • 读取缓存
    val helper = PageListCacheHelper(container?.context?.filesDir?.absolutePath)
    val pageModel: Any? = helper.get(key)
    
  • 写入缓存
    val helper = PageListCacheHelper(context.filesDir.absolutePath)
    helper.put(key, object)
    
  • 删除缓存
    val helper = PageListCacheHelper(context.filesDir.absolutePath)
    helper.remove(key)
    
    4) jsoup 获取数据

由于数据是用从 html 页面中提取的,所以速度偏慢,为了不影响体验做了一套缓存机制,来做到流畅体验。

Document doc = Jsoup.parse(html);
Elements elements = body.getElementsByTag("a");
String text = elements.get(0).text();
String imageUrl = elements.get(0).attr("src");
...

5) 组件

  • Activity fragment 替代 activity 来显示新界面

    因为 activity 需要在 Manifiest 中注册,所以当有多个 activity 的时候,就需要编写很长的 Manifiest 文件,严重影响了 Manifiest 的可读性,界面的风格也比较笨重。所以一个新页面就注册一个 activity 不太合适,我们通过用 activity 做为容器添加不同的 Fragment 来达到注册一个 activity 启动多个不同页面的效果。生命周期由 activity 管理,更方便简洁。

      open class BaseFragmentActivity : BaseActionActivity() {
    
          companion object {
    
              private const val EXTRA_FRAGMENT_NAME = "extra_fragment_name"
              private const val EXTRA_FRAGMENT_ARG = "extra_fragment_arguments"
    
              @JvmOverloads
              @JvmStatic
              fun newInstance(context: Context, fragment: Class<*>, bundle: Bundle? = null,
                                            clazz: Class<out Activity> = getActivityClazz()): Intent {
                  val intent = Intent(context, clazz)
                  intent.putExtra(EXTRA_FRAGMENT_NAME, fragment.name)
                  intent.putExtra(EXTRA_FRAGMENT_ARG, bundle)
                  return intent
              }
    
              protected open fun getActivityClazz(): Class<out Activity> {
                  return BaseFragmentActivity::class.java
              }
          }
    
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.cc_activity_base_fragment)
              val fragmentName = intent.getStringExtra(EXTRA_FRAGMENT_NAME)
              var fragment: Fragment? = null
              if (TextUtils.isEmpty(fragmentName)) {
                  //set default fragment
                  //fragment = makeFragment(MainFragment::class.java!!.getName())
              } else {
                  val args = intent.getBundleExtra(EXTRA_FRAGMENT_ARG)
                  try {
                      fragment = makeFragment(fragmentName)
                      if (args != null)
                          fragment?.arguments = args
                  } catch (e: Exception) {
                      e.printStackTrace()
                  }
    
              }
    
              if (fragment == null) return
    
              supportFragmentManager
                      .beginTransaction()
                      .replace(R.id.fragment_container, fragment)
                      .commit()
          }
    
          fun makeFragment(name: String): Fragment? {
              try {
                  val fragmentClazz = Class.forName(name)
                  return fragmentClazz.newInstance() as Fragment
              } catch (e: Exception) {
                  e.printStackTrace()
              }
    
              return null
          }
    
      }
    

6) 序列化性能

性能测试,Serializable VS Externalizable,为了避免干扰,我们使用 AndroidTest 进行测试。

模型

class Model1 implements Serializable {
    String text;
    int code;
    boolean bool;
    Model1 child;
}

class Model2 extends Model1 implements Externalizable {

    public Model2() {
    }

    @Override
    public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException {
        text = input.readUTF();
        code = input.readInt();
        bool = input.readBoolean();

        child = new Model2();
        child.text = input.readUTF();
        child.code = input.readInt();
        child.bool = input.readBoolean();
    }

    @Override
    public void writeExternal(ObjectOutput output) throws IOException {
        output.writeUTF(text);
        output.writeInt(code);
        output.writeBoolean(bool);
        if (child != null) {
            output.writeUTF(child.text);
            output.writeInt(child.code);
            output.writeBoolean(child.bool);
        }
    }
}

测试

@Test
public void serializableVSExternalizable() throws Exception {
    List<Model1> testModel1 = new ArrayList<>();
    for (int i = 0; i < 50000; i++) {
        Model1 model1 = new Model1();
        model1.text = "Hello World " + i;
        model1.code = i;
        model1.bool = false;

        Model1 child = new Model1();
        child.text = "Hello World Child" + i;
        child.code = i;
        child.bool = false;

        model1.child = child;
        testModel1.add(model1);
    }
    long startTime = System.currentTimeMillis();
    File file = new File("/sdcard/serializable");
    ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream(file));
    oStream.writeObject(testModel1);
    oStream.close();
    Log.e("serializable", "write time " + (System.currentTimeMillis() - startTime));
    startTime = System.currentTimeMillis();
    ObjectInputStream iStream = new ObjectInputStream(new FileInputStream(file));
    testModel1 = (List<Model1>) iStream.readObject();
    iStream.close();
    Log.e("serializable", "read time " + (System.currentTimeMillis() - startTime));
    testModel1 = null;

    List<Model2> testModel2 = new ArrayList<>();
    for (int i = 0; i < 50000; i++) {
        Model2 model2 = new Model2();
        model2.text = "Hello World " + i;
        model2.code = i;
        model2.bool = false;

        Model2 child = new Model2();
        child.text = "Hello World Child" + i;
        child.code = i;
        child.bool = false;

        model2.child = child;
        testModel2.add(model2);
    }
    startTime = System.currentTimeMillis();
    file = new File("/sdcard/externalizable");
    oStream = new ObjectOutputStream(new FileOutputStream(file));
    oStream.writeObject(testModel2);
    oStream.close();
    Log.e("externalizable", "write time " + (System.currentTimeMillis() - startTime));
    startTime = System.currentTimeMillis();
    iStream = new ObjectInputStream(new FileInputStream(file));
    testModel2 = (List<Model2>) iStream.readObject();
    iStream.close();
    Log.e("externalizable", "read time " + (System.currentTimeMillis() - startTime));
}

结果

序列化 5000 个对象
Serializable:写入耗时 4026 ms,读取耗时 177 ms
Externalizable:写入耗时 2680 ms,读取耗时 79 ms

序列化 50000 个对象
Serializable:写入耗时 46872 ms,读取耗时 1807 ms
Externalizable:写入耗时 41334 ms,读取耗时 792 ms

从结果上可以看到 Externalizalbe 相比于 Serializable 是稍微快一些点不管是写入还是读取速度。对象存储还可以使用一些对象关系映射(ORM)型的数据库。如GreenDao等等。

7) Java 中的深拷贝

由于 System.arrayCopy()该方法拷贝数组的时候,如果是基本数据类型则是深拷贝,如果是对象类型则会是浅拷贝,无法做到深拷贝,所以想深拷贝一个数组就得循环创建对象并赋值,这显得很麻烦。所以项目中使用序列化的方法进行深拷贝。PS:Serializable 序列化方式读取的时候并不会调用对象构造方法,而 Externalizable 序列化方式读取时会调用对象的无参构造方法。

@SuppressWarnings("unchecked")
public static <T> T deepCopyOrThrow(T src) throws IOException, ClassNotFoundException {
    ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(byteOut);
    out.writeObject(src);

    ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
    ObjectInputStream in = new ObjectInputStream(byteIn);
    return (T) in.readObject();
}

public static <T> T deepCopy(T src) {
    try {
        return deepCopyOrThrow(src);
    } catch (Exception ignore) {
        ignore.printStackTrace();
        return null;
    }
}

8) 释放进程资源

直接调用 System.exit()方法可释放所在进程的资源,腾出内存给其他组件使用,减少被系统回收的概率。PS:如果该进程下有服务没有关闭,该进程会在后续重新启动。

ProGuard

-keep class org.jsoup.**{*;}
-keep public class com.ecjtu.netcore.jsoup.SoupFactory{*;}
-keep public class * extends com.ecjtu.netcore.jsoup.BaseSoup{*;}
-keep public class com.ecjtu.netcore.Constants{static <fields>;}
-keep public class com.ecjtu.netcore.model.**{*;}
-keep public class com.ecjtu.netcore.network.BaseNetwork{public <methods>;}
-keep public class * extends com.ecjtu.netcore.network.BaseNetwork{ public <methods>; }
-keep public interface com.ecjtu.netcore.network.IRequestCallback{*;}
-keep public class * extends android.support.design.widget.CoordinatorLayout$Behavior{*;}

Contributing

contributors submmit pull requests.

Thanks

Author

KerriGan - mnsync@outlook.com or ethanxiang95@gmail.com

License

Apache2

Disclaimer

Only available for study and communication.If the flesh violate your rights,we can delete immediately violate to your rights and interests content.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools