commit 6366233201babdd1e6e47a139eeaf17d034c7c4f Author: BBIT-Kai <2911862937@qq.com> Date: Mon May 25 16:02:38 2026 +0800 初始化项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2bfca55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +.idea/ +app/release/ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..76c6e48 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,105 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.bbitcn.silk' + compileSdk 34 + + defaultConfig { + applicationId "com.bbitcn.silk" + minSdk 22 + targetSdk 34 + versionCode 22 + versionName "1.2.2" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + signingConfigs { + release { + keyAlias 'store' + keyPassword '123456' + storeFile file('../key/store.jks') + storePassword '123456' + } + debug { + keyAlias 'store' + keyPassword '123456' + storeFile file('../key/store.jks') + storePassword '123456' + } + } + + //修改生成的apk名字,格式为 app名_版本号_打包时间.apk + applicationVariants.all { variant -> + def type = variant.buildType.name + def time = new Date().format("yyyy-MM-dd_HH-mm") + if (type == 'release') { + variant.outputs.all { output -> + outputFileName = "BBIT智慧蚕桑_${versionCode}_${versionName}_${time}.apk" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + buildFeatures { + viewBinding true + dataBinding true + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.android.support:support-annotations:28.0.0' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.1.0' + //屏幕适配 + implementation 'com.github.JessYanCoding:AndroidAutoSize:v1.2.1' + //dataBinding + implementation 'androidx.databinding:databinding-runtime:8.0.0' + //RxJava + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0' + implementation 'com.github.xuexiangjys:RxUtil2:1.2.1' + //util + implementation 'com.blankj:utilcodex:1.31.1' + //网络请求库 +// implementation 'org.xutils:xutils:3.9.0' + //流式布局 + implementation 'com.google.android.flexbox:flexbox:3.0.0' + //MMKV + implementation 'com.tencent:mmkv:1.2.13' + //日志库 + implementation 'com.jakewharton.timber:timber:5.0.1' + //权限库 + implementation 'com.github.getActivity:XXPermissions:18.5' + //GSON + implementation 'com.google.code.gson:gson:2.9.0' + //下拉刷新 加载更多 + implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3' + implementation 'com.scwang.smart:refresh-header-classics:2.0.3' + //Toast + implementation 'com.github.GrenderG:Toasty:1.5.2' + //加载中 + implementation 'me.samlss:broccoli:1.0.0' + //解决依赖冲突 + implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) + //日期下拉框 + implementation 'com.contrarywind:Android-PickerView:4.1.9' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/bbitcn/silk/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/bbitcn/silk/ExampleInstrumentedTest.java new file mode 100644 index 0000000..920bcfd --- /dev/null +++ b/app/src/androidTest/java/com/bbitcn/silk/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.bbitcn.silk; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.bbitcn.silk", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a98cb60 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/MyApp.java b/app/src/main/java/com/bbitcn/silk/MyApp.java new file mode 100644 index 0000000..cf32e9d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/MyApp.java @@ -0,0 +1,58 @@ +package com.bbitcn.silk; + +import android.app.Application; +import android.content.Context; +import android.provider.Settings; +import android.view.Gravity; + +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.CrashHandlerUtil; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.tencent.mmkv.MMKV; + +import org.xutils.x; + +import es.dmoral.toasty.Toasty; +import timber.log.Timber; + +/** + * @Description APPLICATION类 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 13:43 + */ +public class MyApp extends Application { + private static MyApp instance; + + @Override + public void onCreate() { + super.onCreate(); + instance = this; + // 初始化网络请求地址 + Url.initUrl(); + // 初始化MMKV + MMKV.initialize(getApplicationContext()); + MMKVUtil.init(); + // 初始化崩溃捕捉 + CrashHandlerUtil.getInstance().init(getApplicationContext()); + // 初始化日志库 + Timber.plant(new MyLog()); + // 初始化网络请求库 + x.Ext.init(this); + // 初始化Toast + Toasty.Config + .getInstance() + .setTextSize(30) + .tintIcon(true) + .allowQueue(false) + .setGravity(Gravity.CENTER) + .supportDarkTheme(true) + .apply(); + MMKVUtil.put(Global.DEVICE_ID, Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID)); + } + + public static Context getAppContext() { + return instance.getApplicationContext(); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/AboutAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/AboutAdapter.java new file mode 100644 index 0000000..7633d79 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/AboutAdapter.java @@ -0,0 +1,28 @@ +package com.bbitcn.silk.adapter; + + +import com.bbitcn.silk.base.BaseRecyclerAdapter; +import com.bbitcn.silk.databinding.ItemAboutBinding; +import com.bbitcn.silk.model.net.About; + +import java.util.List; + +/** + * @Author:DuanKaiji + * @Date:2024年4月7日 15:10:50 + */ +public class AboutAdapter extends BaseRecyclerAdapter { + + + public AboutAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + + } + + @Override + protected void bindData(ItemAboutBinding binding, About.DataDTO.ItemsDTO.ItemsDTO2 data) { + binding.tvKey.setText(data.getName()); + binding.tvValue.setText(data.getValue()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/ConvertAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/ConvertAdapter.java new file mode 100644 index 0000000..3031c67 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/ConvertAdapter.java @@ -0,0 +1,36 @@ +package com.bbitcn.silk.adapter; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.base.BaseRecyclerAdapter; +import com.bbitcn.silk.databinding.ItemConvertBinding; +import com.bbitcn.silk.model.InfoContent; +import com.bbitcn.silk.ui.dialog.SpinnerDialog; +import com.bbitcn.silk.utils.database.AllTypeDataBase; + +import java.util.List; + +/** + * @Author DuanKaiji + * @CreateTime 2024年04月01日 14:13:39 + */ +public class ConvertAdapter extends BaseRecyclerAdapter { + + public ConvertAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemConvertBinding binding, InfoContent data) { + binding.tvOld.setText(data.getTv1()); + binding.tvWeight.setText(data.getTv2()); + binding.llConvertTarget.setOnClickListener(v -> { + new SpinnerDialog(binding.getRoot().getContext(), "目标茧别", "不转换", AllTypeDataBase.getAllTypeName(), new BaseDialog.CustomString() { + @Override + public void listener(String selString) { + binding.tvNew.setText(selString); + } + }, true).show(); + }); + + } +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/ItemContentAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/ItemContentAdapter.java new file mode 100644 index 0000000..c5b3aa3 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/ItemContentAdapter.java @@ -0,0 +1,34 @@ +package com.bbitcn.silk.adapter; + +import android.view.View; + +import com.bbitcn.silk.base.BaseRecyclerAdapter; +import com.bbitcn.silk.databinding.ItemInfoContentBinding; +import com.bbitcn.silk.model.InfoContent; + +import java.util.List; + +/** + * @Description 信息项的列表细则 + * @Author DuanKaiji + * @CreateTime 2024年04月01日 09:30:28 + */ +public class ItemContentAdapter extends BaseRecyclerAdapter { + + View.OnClickListener listener; + + public ItemContentAdapter(List dataList, Class bindingClass, View.OnClickListener listener) { + super(dataList, bindingClass); + this.listener = listener; + } + + @Override + protected void bindData(ItemInfoContentBinding binding, InfoContent data) { + binding.llRoot.setOnClickListener(listener); + binding.tv1.setText(data.getTv1()); + binding.tv2.setText(data.getTv2()); + binding.tv3.setText(data.getTv3()); + binding.tv4.setText(data.getTv4()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/ItemInfoAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/ItemInfoAdapter.java new file mode 100644 index 0000000..fe70ef7 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/ItemInfoAdapter.java @@ -0,0 +1,132 @@ +package com.bbitcn.silk.adapter; + +import android.content.Context; +import android.content.Intent; +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bbitcn.silk.base.BaseRecyclerAdapter; +import com.bbitcn.silk.databinding.ItemInfoBinding; +import com.bbitcn.silk.databinding.ItemInfoContentBinding; +import com.bbitcn.silk.model.InfoContent; +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.ui.activity.ConvertActivity; +import com.bbitcn.silk.ui.activity.TareActivity; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.GsonUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description 信息项 + * @Author DuanKaiji + * @CreateTime 2024年04月01日 09:30:28 + */ +public class ItemInfoAdapter extends BaseRecyclerAdapter { + /** + * 使用类型 + * 0磅单查询 1扣皮定价 2茧别转换 + */ + int type; + View.OnClickListener listener; + Context mContext; + + public ItemInfoAdapter(Context context, List dataList, Class bindingClass, int type) { + super(dataList, bindingClass); + this.type = type; + this.mContext = context; + } + + @Override + protected void bindData(ItemInfoBinding binding, QueryList.DataDTO data) { + if (type != 0) { + if (type == 3) { + //弃售/确认售 + binding.llExertFunc.setVisibility(View.VISIBLE); + binding.tvCancel.setOnClickListener(v -> new ConfirmDialog(mContext, "弃售", "确定要弃售" + data.getCzCode() + "吗", + () -> requestNet(-1, data.getCzSysid())).show()); + binding.tvConfirm.setOnClickListener(v -> new ConfirmDialog(mContext, "确认售", "确定要确认售" + data.getCzCode() + "吗", + () -> requestNet(2, data.getCzSysid())).show()); + } else { + Intent intent = new Intent(); + intent.putExtra("data", GsonUtils.toJson(data)); + binding.llExertFunc.setVisibility(View.GONE); + if (type == 1) { + //扣皮定价 + intent.setClass(mContext, TareActivity.class); + } else if (type == 2) { + //茧别转换 + intent.setClass(mContext, ConvertActivity.class); + } + listener = v -> binding.clRoot.getContext().startActivity(intent); + binding.clRoot.setOnClickListener(listener); + } + } + binding.tvState.setText(data.getStatus()); + binding.tvName.setText(data.getNhName()); + binding.tvId.setText(data.getCzCode()); + binding.recycleView.setLayoutManager(new LinearLayoutManager(mContext)); + List infoContents = new ArrayList<>(); + for ( + QueryList.DataDTO.ItemsDTO itemsDTO : data.getItems()) { + infoContents.add(new InfoContent(itemsDTO.getSgTypeName(), itemsDTO.getBaoshu(), itemsDTO.getMaozhong(), itemsDTO.getPrice())); + } + binding.recycleView.setAdapter(new ItemContentAdapter(infoContents, ItemInfoContentBinding.class, listener)); + } + + /** + * @param type -1 弃售 2 确认销售 + * @param id 称重sysid + */ + private void requestNet(int type, String id) { + DialogUtil.showLoadingDialog(mContext, "正在提交中"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.configOrCancel); + params.addQueryStringParameter("sgBillstate", type); + params.addQueryStringParameter("czsysid", id); + x.http().put(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + if (model.getResult()) { + MyLog.app("操作成功"); + new TipsDialog(mContext, "提交成功").show(); + // 删除这行数据 + for (int i = 0; i < dataList.size(); i++) { + if (dataList.get(i).getCzSysid().equals(id)) { + dataList.remove(i); + notifyItemRemoved(i); + break; + } + } + } else { + new TipsDialog(mContext, model.getMsg()).show(); + MyLog.appError("查询失败"); + } + } + + @Override + public void error(Throwable e) { + new TipsDialog(mContext, e.getMessage()).show(); + } + + @Override + public void myFinish() { + super.myFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/PageAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/PageAdapter.java new file mode 100644 index 0000000..046dd02 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/PageAdapter.java @@ -0,0 +1,35 @@ +package com.bbitcn.silk.adapter; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.bbitcn.silk.ui.fragment.QueryFragment; + +/** + * @Author:DuanKaiji + */ +public class PageAdapter extends FragmentStateAdapter { + public PageAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + } + + @Override + public Fragment createFragment(int position) { + switch (position) { + case 1: + default: + return new QueryFragment(3, 3,2); + case 2: + return new QueryFragment(3, 0,3); + } + + } + + @Override + public int getItemCount() { + return 2; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/adapter/PriceAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/PriceAdapter.java new file mode 100644 index 0000000..22a83d4 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/PriceAdapter.java @@ -0,0 +1,25 @@ +package com.bbitcn.silk.adapter; + +import com.bbitcn.silk.base.BaseRecyclerAdapter; +import com.bbitcn.silk.databinding.ItemPriceBinding; +import com.bbitcn.silk.model.net.TodayPrice; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月01日 11:00:15 + */ +public class PriceAdapter extends BaseRecyclerAdapter { + public PriceAdapter(List dataList, Class bindingClass) { + super(dataList, bindingClass); + } + + @Override + protected void bindData(ItemPriceBinding binding, TodayPrice.DataDTO.ItemsDTO data) { + binding.tvType.setText(data.getName()); + binding.tvPrice.setText(data.getMinprice() + " ~ " + data.getMaxprice()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/adapter/TarePageAdapter.java b/app/src/main/java/com/bbitcn/silk/adapter/TarePageAdapter.java new file mode 100644 index 0000000..df4f9e0 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/adapter/TarePageAdapter.java @@ -0,0 +1,42 @@ +package com.bbitcn.silk.adapter; + +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.ui.activity.TareActivity; +import com.bbitcn.silk.ui.fragment.TareInfoFragment; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author:DuanKaiji + */ +public class TarePageAdapter extends FragmentStateAdapter { + + QueryList.DataDTO item; + List fragments; + + public TarePageAdapter(TareActivity fragmentActivity, QueryList.DataDTO item) { + super(fragmentActivity); + fragments = new ArrayList<>(); + this.item = item; + } + + @Override + public TareInfoFragment createFragment(int position) { + TareInfoFragment fragment = new TareInfoFragment(item.getCzSysid(), item.getItems().get(position)); + fragments.add(fragment); + return fragment; + } + + @Override + public int getItemCount() { + return item.getItems().size(); + } + + public TareInfoFragment getFragment(int position) { + return fragments.get(position); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/base/BaseActivity.java b/app/src/main/java/com/bbitcn/silk/base/BaseActivity.java new file mode 100644 index 0000000..780d863 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/base/BaseActivity.java @@ -0,0 +1,226 @@ +package com.bbitcn.silk.base; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.databinding.ViewDataBinding; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.global.Variable; +import com.bbitcn.silk.utils.log.MyLog; +import com.blankj.utilcode.util.KeyboardUtils; +import com.blankj.utilcode.util.StringUtils; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; +import me.jessyan.autosize.internal.CustomAdapt; + + +/** + * Activity基类 + */ +public abstract class BaseActivity extends AppCompatActivity implements CustomAdapt, View.OnClickListener { + + public Presenter presenter; + public Binding binding; + public Context mContext; + /** + * 防抖 + */ + long lastClickTime; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + initTransition(); + super.onCreate(savedInstanceState); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + mContext = this; + Type type = this.getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + Class controllerClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; + try { + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class); + binding = (Binding) inflateMethod.invoke(null, getLayoutInflater()); + presenter = new ViewModelProvider(this).get(controllerClass); +// presenter = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(controllerClass); + binding.setLifecycleOwner(this); + registerPresenter(presenter); + } catch (Exception e) { + MyLog.appError("BaseActivity初始化失败:" + e.getMessage()); + e.printStackTrace(); + } + setContentView(binding.getRoot()); + initView(); + initListener(); +// setFullScreen(true);//需要显示状态栏 所以注释 + } + } + + protected void registerPresenter(BasePresenter presenter) { + presenter.getIsLoadingLiveData().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + if (StringUtils.isEmpty(msg)) { + DialogUtil.hideLoadingDialog(); + } else { + DialogUtil.showLoadingDialog(mContext, msg); + } + } + }); + presenter.getErrorTips().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + errorTips(msg); + } + }); + presenter.getSuccessTips().observe(this, new Observer() { + @Override + public void onChanged(String msg) { + successTips(msg); + } + }); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void showLoadingDialogInIO(String msg) { + DialogUtil.showLoadingDialogInIO(mContext, msg); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void showLoadingDialog(String msg) { + DialogUtil.showLoadingDialog(mContext, msg); + } + + /** + * 开启加载中弹窗 + * 在IO线程中调用的 + */ + public void hideLoadingDialog() { + DialogUtil.hideLoadingDialog(); + } + + public void setFullScreen(boolean isFullScreen) { + if (isFullScreen) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + View decorView = getWindow().getDecorView(); + int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_FULLSCREEN; + decorView.setSystemUiVisibility(uiOptions); + } else { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); + } + } + + /** + * 初始化转场动画 + */ + protected void initTransition() { + } + + /** + * 初始化界面 + */ + public abstract void initView(); + + /** + * 初始化监听器,可选 + */ + public void initListener() { + } + + @Override + public final void onClick(View view) { + //防抖拦截 + long now = System.currentTimeMillis(); + if (now - lastClickTime > Variable.CLICK_INTERVAL) { + myClick(view); + } + lastClickTime = now; + } + + /** + * 点击事件 + * 会被防抖拦截,需要的子类可以按需重写,可选 + */ + public void myClick(View view) { + } + + /** + * 屏幕自适应 + */ + @Override + public boolean isBaseOnWidth() { + return true; + } + + @Override + public float getSizeInDp() { + return 0; + } + + /** + * 通用成功提示 + */ + public void successTips() { + Toasty.success(mContext, getString(R.string.operate_success)).show(); + } + + public void successTips(String msg) { + if (StringUtils.isEmpty(msg)) { + successTips(); + }else{ + Toasty.success(mContext, msg).show(); + } + + } + + /** + * 通用错误提示 + */ + public void errorTips() { + new TipsDialog(mContext, getString(R.string.operate_fail)).show(); + } + + public void errorTips(String msg) { + if (StringUtils.isEmpty(msg)) { + errorTips(); + } else { + new TipsDialog(mContext, msg).show(); + } + } + + protected void fillDataInText(TextView textView, T data) { + if (data == null) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Double && (Double) data == 0) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Integer && (Integer) data == 0) { + textView.setText(getText(R.string.none)); + } else { + textView.setText(data.toString()); + } + } + +} + diff --git a/app/src/main/java/com/bbitcn/silk/base/BaseDialog.java b/app/src/main/java/com/bbitcn/silk/base/BaseDialog.java new file mode 100644 index 0000000..8d2a938 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/base/BaseDialog.java @@ -0,0 +1,86 @@ +package com.bbitcn.silk.base; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; +import androidx.databinding.ViewDataBinding; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.log.MyLog; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 弹窗基类 + * @Author DuanKaiji + * @CreateTime 2024年02月29日 14:49 + */ +public abstract class BaseDialog extends Dialog { + protected Binding binding; + protected Context mContext; + + public BaseDialog(@NonNull Context context) { + this(context, false); + } + + /** + * 构造函数 + * + * @param context 上下文 + * @param needInput 是否需要输入,即软键盘弹出 + */ + public BaseDialog(@NonNull Context context, boolean needInput) { + super(context, R.style.MyDialogTheme); + mContext = context; + Type type = this.getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + try { + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class); + binding = (Binding) inflateMethod.invoke(null, getLayoutInflater()); + setContentView(binding.getRoot()); + findViewById(R.id.iv_dialog_close).setOnClickListener(view -> dismiss()); + getWindow().setGravity(Gravity.CENTER); + if (!needInput) { + getWindow().setAttributes(getAttributes(this)); + } + } catch (Exception e) { + MyLog.appError("BaseDialog初始化失败:" + e.getMessage()); + e.printStackTrace(); + } + } + } + + public interface CustomString { + /** + * @param selString 选中的项 + */ + void listener(String selString); + } + /** + * 获得弹窗属性设置 + * 全屏弹窗,但无法使用输入法 + */ + public static WindowManager.LayoutParams getAttributes(Dialog dialog) { + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.copyFrom(dialog.getWindow().getAttributes()); + layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; + return layoutParams; + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/base/BaseFragment.java b/app/src/main/java/com/bbitcn/silk/base/BaseFragment.java new file mode 100644 index 0000000..845311d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/base/BaseFragment.java @@ -0,0 +1,196 @@ +package com.bbitcn.silk.base; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.databinding.ViewDataBinding; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.global.Variable; +import com.blankj.utilcode.util.StringUtils; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import es.dmoral.toasty.Toasty; +import me.jessyan.autosize.internal.CustomAdapt; + +/** + * Fragment基类 + * + * @Author:DuanKaiji + */ +public abstract class BaseFragment extends Fragment implements CustomAdapt, View.OnClickListener { + + protected Binding binding; + protected Presenter presenter; + protected Context mContext; + /** + * 防抖 + */ + long lastClickTime; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Type type = getClass().getGenericSuperclass(); + if (type instanceof ParameterizedType) { + Class bindingClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + Class presenterClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[1]; + try { + getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + Method inflateMethod = bindingClass.getMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class); + binding = (Binding) inflateMethod.invoke(null, inflater, container, false); + presenter = new ViewModelProvider(this).get(presenterClass); +// presenter = new ViewModelProvider(this, new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication())).get(presenterClass); + binding.setLifecycleOwner(getViewLifecycleOwner()); + } catch (Exception e) { + e.printStackTrace(); + } + registerPresenter(presenter); + } + return binding.getRoot(); + } + + protected void registerPresenter(BasePresenter presenter) { + presenter.getIsLoadingLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + if (StringUtils.isEmpty(msg)) { + DialogUtil.hideLoadingDialog(); + } else { + DialogUtil.showLoadingDialog(mContext, msg); + } + } + }); + presenter.getErrorTips().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + error(msg); + } + }); + presenter.getSuccessTips().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(String msg) { + success(msg); + } + }); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mContext = getActivity(); + init(); + initListener(); + } + + /** + * 初始化界面 + */ + public abstract void init(); + + /** + * 初始化监听器 + */ + public void initListener() { + } + + @Override + public final void onClick(View view) { + //防抖拦截 + long now = System.currentTimeMillis(); + if (now - lastClickTime > Variable.CLICK_INTERVAL) { + myClick(view); + } + lastClickTime = now; + } + + /** + * 点击事件 + * 会被防抖拦截 + * + * @param view + */ + public void myClick(View view) { + } + + @Override + public boolean isBaseOnWidth() { + return true; + } + + @Override + public float getSizeInDp() { + return 0; + } + + protected void fillDataInText(TextView textView, T data) { + if (data == null) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Double && (Double) data == 0) { + textView.setText(getText(R.string.none)); + } else if (data instanceof Integer && (Integer) data == 0) { + textView.setText(getText(R.string.none)); + } else { + textView.setText(data.toString()); + } + } + + /** + * 通用成功提示 + */ + public void success() { + Toasty.success(mContext, getString(R.string.operate_success)).show(); + } + + public void success(String msg) { + if (StringUtils.isEmpty(msg)) { + success(); + } else { + Toasty.success(mContext, msg).show(); + } + } + + /** + * 通用错误提示 + */ + public void error() { + new TipsDialog(mContext, getString(R.string.operate_fail)).show(); + } + + public void error(String msg) { + if (StringUtils.isEmpty(msg)) { + error(); + } else { + new TipsDialog(mContext, msg).show(); + } + } + + /** + * 开启加载中弹窗 + */ + public void showLoadingDialog(String msg) { + DialogUtil.showLoadingDialog(mContext, msg); + } + + /** + * 关闭加载中弹窗 + */ + public void hideLoadingDialog() { + DialogUtil.hideLoadingDialog(); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/base/BasePresenter.java b/app/src/main/java/com/bbitcn/silk/base/BasePresenter.java new file mode 100644 index 0000000..1f50133 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/base/BasePresenter.java @@ -0,0 +1,65 @@ +package com.bbitcn.silk.base; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.MutableLiveData; + +/** + * @Description ViewModel 逻辑层 + * @Author DuanKaiji + * @CreateTime 2024年01月17日 09:32 + */ +public class BasePresenter extends AndroidViewModel { + + private MutableLiveData isLoadingLiveData = new MutableLiveData<>(); + private MutableLiveData errorTips = new MutableLiveData<>(); + private MutableLiveData successTips = new MutableLiveData<>(); + public BasePresenter(@NonNull Application application) { + super(application); + } + public MutableLiveData getIsLoadingLiveData() { + return isLoadingLiveData; + } + + public MutableLiveData getErrorTips() { + return errorTips; + } + + public MutableLiveData getSuccessTips() { + return successTips; + } + + public void showLoadingDialog(String content) { + isLoadingLiveData.setValue(content); + } + + public void showLoadingDialogInIO(String content) { + isLoadingLiveData.postValue(content); + } + + public void hideLoadingDialog() { + isLoadingLiveData.setValue(null); + } + + public void errorTips() { + errorTips(null); + } + + public void errorTips(String msg) { + errorTips.setValue(msg); + } + + public void successTips() { + successTips(null); + } + + public void successTips(String msg) { + successTips.setValue(msg); + } + + public String getString(int id) { + return getApplication().getString(id); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/base/BaseRecyclerAdapter.java b/app/src/main/java/com/bbitcn/silk/base/BaseRecyclerAdapter.java new file mode 100644 index 0000000..3b78d85 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/base/BaseRecyclerAdapter.java @@ -0,0 +1,134 @@ +package com.bbitcn.silk.base; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * RecyclerAdapter基类 + * + * @param 数据-类型 + * @param 布局-类型 + */ +public abstract class BaseRecyclerAdapter extends RecyclerView.Adapter.BaseViewHolder> { + + View tvTipNone; + + protected List dataList; + private Class bindingClass; + + /** + * 构造方法,必须重写此方法 + * + * @param dataList 数据list + * @param bindingClass 布局类,例如ActivityMainBinding.class + */ + public BaseRecyclerAdapter(List dataList, Class bindingClass) { + this.dataList = dataList; + this.bindingClass = bindingClass; + } + + public BaseRecyclerAdapter(List dataList, Class bindingClass, View tvTipNone) { + this.dataList = dataList; + this.bindingClass = bindingClass; + this.tvTipNone = tvTipNone; + if (dataList != null && dataList.size() > 0) { + tvTipNone.setVisibility(View.GONE); + } else { + tvTipNone.setVisibility(View.VISIBLE); + } + } + + @NonNull + @Override + public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + try { + Method inflateMethod = bindingClass.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class); + B binding = (B) inflateMethod.invoke(null, LayoutInflater.from(parent.getContext()), parent, false); + return new BaseViewHolder(binding); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to inflate binding class: " + bindingClass.getSimpleName()); + } + } + + @Override + public void onBindViewHolder(@NonNull BaseViewHolder holder, int position) { + if (holder.binding != null) { + T data = dataList.get(position); + bindData(holder.binding, data, position); + } + } + + @Override + public int getItemCount() { + return dataList == null ? 0 : dataList.size(); + } + + /** + * 快捷更新RecycleView数据 + * + * @param dataList 数据 + */ + public void setDataList(List dataList) { + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + if (tvTipNone != null) { + if (dataList != null && dataList.size() > 0) { + tvTipNone.setVisibility(View.GONE); + } else { + tvTipNone.setVisibility(View.VISIBLE); + } + } + BaseRecyclerAdapter.this.dataList = dataList; + notifyDataSetChanged(); + } + }); + } + + public void addDataList(List dataList) { + this.dataList.addAll(dataList); + //只刷新增加新数据 + notifyItemRangeInserted(this.dataList.size() - dataList.size(), dataList.size()); + } + + /** + * 手动实现具体数据与布局的绑定 + * + * @param binding 布局 + * @param data 数据 + */ + protected abstract void bindData(B binding, T data); + + /** + * 有需要位置信息的,可以重写该方法 + * + * @param binding 布局 + * @param data 数据 + * @param position 位置 + */ + protected void bindData(B binding, T data, int position) { + bindData(binding, data); + } + + public class BaseViewHolder extends RecyclerView.ViewHolder { + public B binding; + + public BaseViewHolder(B binding) { + super(binding.getRoot()); + this.binding = binding; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/model/InfoContent.java b/app/src/main/java/com/bbitcn/silk/model/InfoContent.java new file mode 100644 index 0000000..57998a8 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/InfoContent.java @@ -0,0 +1,57 @@ +package com.bbitcn.silk.model; + +/** + * @Description 细则项 + * @Author DuanKaiji + * @CreateTime 2024年04月01日 10:29:48 + */ +public class InfoContent { + String tv1; + String tv2; + String tv3; + String tv4; + + public InfoContent(Object tv1, Object tv2) { + this.tv1 = String.valueOf(tv1); + this.tv2 = String.valueOf(tv2); + } + + public InfoContent(Object tv1, Object tv2, Object tv3, Object tv4) { + this.tv1 = String.valueOf(tv1); + this.tv2 = String.valueOf(tv2); + this.tv3 = String.valueOf(tv3); + this.tv4 = String.valueOf(tv4); + } + + public String getTv1() { + return tv1; + } + + public void setTv1(String tv1) { + this.tv1 = tv1; + } + + public String getTv2() { + return tv2; + } + + public void setTv2(String tv2) { + this.tv2 = tv2; + } + + public String getTv3() { + return tv3; + } + + public void setTv3(String tv3) { + this.tv3 = tv3; + } + + public String getTv4() { + return tv4; + } + + public void setTv4(String tv4) { + this.tv4 = tv4; + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/Set.java b/app/src/main/java/com/bbitcn/silk/model/Set.java new file mode 100644 index 0000000..e6f816f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/Set.java @@ -0,0 +1,106 @@ +package com.bbitcn.silk.model; + +import android.view.View; +import android.widget.CompoundButton; + +/** + * @Author:DuanKaiji + * @Date:2024年3月28日 09:47:35 + * @Declaration:设置单项内容-配置界面用 + */ +public class Set { + /** + * 设置项 + */ + private String title; + /** + * 设置内容 + */ + private String detail; + /** + * 点击事件 + */ + private View.OnClickListener onClickListener; + /** + * 默认开关 + */ + private boolean isChecked; + /** + * 开关事件 + * 为空时不显示开关 + */ + private CompoundButton.OnCheckedChangeListener onCheckedChangeListener; + + /** + * 设置标题和事件 + */ + public Set(String title, View.OnClickListener onClickListener) { + this.title = title; + this.onClickListener = onClickListener; + } + /** + * 设置标题和事件 + */ + public Set(String title, String detail) { + this.title = title; + this.detail = detail; + } + + /** + * 设置标题、细节和事件 + */ + public Set(String title, String detail, View.OnClickListener onClickListener) { + this.title = title; + this.detail = detail; + this.onClickListener = onClickListener; + } + + /** + * 设置标题、开关默认状态和开关事件 + */ + public Set(String title, boolean isChecked, CompoundButton.OnCheckedChangeListener onCheckedChangeListener) { + this.title = title; + this.isChecked = isChecked; + this.onCheckedChangeListener = onCheckedChangeListener; + } + + public boolean isChecked() { + return isChecked; + } + + public void setChecked(boolean checked) { + isChecked = checked; + } + + public CompoundButton.OnCheckedChangeListener getOnCheckedChangeListener() { + return onCheckedChangeListener; + } + + public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener onCheckedChangeListener) { + this.onCheckedChangeListener = onCheckedChangeListener; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDetail() { + return detail; + } + + public void setDetail(String detail) { + this.detail = detail; + } + + public View.OnClickListener getOnClickListener() { + return onClickListener; + } + + public void setOnClickListener(View.OnClickListener onClickListener) { + this.onClickListener = onClickListener; + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/About.java b/app/src/main/java/com/bbitcn/silk/model/net/About.java new file mode 100644 index 0000000..755828a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/About.java @@ -0,0 +1,113 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月07日 15:10:19 + */ +public class About { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("about") + private String about; + @SerializedName("items") + private List items; + + public String getAbout() { + return about; + } + + public void setAbout(String about) { + this.about = about; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("name") + private String name; + @SerializedName("items") + private List items; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO2 { + @SerializedName("name") + private String name; + @SerializedName("value") + private String value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/AllType.java b/app/src/main/java/com/bbitcn/silk/model/net/AllType.java new file mode 100644 index 0000000..56200c7 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/AllType.java @@ -0,0 +1,87 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:18:09 + */ +public class AllType { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private List data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("sysid") + private String sysid; + @SerializedName("name") + private String name; + @SerializedName("group") + private String group; + @SerializedName("sort") + private Integer sort; + + public String getSysid() { + return sysid; + } + + public void setSysid(String sysid) { + this.sysid = sysid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public Integer getSort() { + return sort; + } + + public void setSort(Integer sort) { + this.sort = sort; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/Auth.java b/app/src/main/java/com/bbitcn/silk/model/net/Auth.java new file mode 100644 index 0000000..e3b8af8 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/Auth.java @@ -0,0 +1,135 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月08日 18:05:16 + */ +public class Auth { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("Flag") + private Integer flag; + @SerializedName("Sysid") + private String sysid; + @SerializedName("TenantCode") + private String tenantCode; + @SerializedName("HardwareId") + private String hardwareId; + @SerializedName("Applicant") + private String applicant; + @SerializedName("Job") + private String job; + @SerializedName("Memo") + private String memo; + @SerializedName("Phone") + private String phone; + @SerializedName("ClientType") + private String clientType; + + public Integer getFlag() { + return flag; + } + + public void setFlag(Integer flag) { + this.flag = flag; + } + + public String getSysid() { + return sysid; + } + + public void setSysid(String sysid) { + this.sysid = sysid; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + public String getHardwareId() { + return hardwareId; + } + + public void setHardwareId(String hardwareId) { + this.hardwareId = hardwareId; + } + + public String getApplicant() { + return applicant; + } + + public void setApplicant(String applicant) { + this.applicant = applicant; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public String getMemo() { + return memo; + } + + public void setMemo(String memo) { + this.memo = memo; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getClientType() { + return clientType; + } + + public void setClientType(String clientType) { + this.clientType = clientType; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/CheckAuth.java b/app/src/main/java/com/bbitcn/silk/model/net/CheckAuth.java new file mode 100644 index 0000000..8ce7d89 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/CheckAuth.java @@ -0,0 +1,135 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月09日 09:02:52 + */ +public class CheckAuth { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("Flag") + private Integer flag; + @SerializedName("Sysid") + private String sysid; + @SerializedName("TenantCode") + private String tenantCode; + @SerializedName("HardwareId") + private String hardwareId; + @SerializedName("Applicant") + private String applicant; + @SerializedName("Job") + private String job; + @SerializedName("Memo") + private String memo; + @SerializedName("Phone") + private String phone; + @SerializedName("ClientType") + private String clientType; + + public Integer getFlag() { + return flag; + } + + public void setFlag(Integer flag) { + this.flag = flag; + } + + public String getSysid() { + return sysid; + } + + public void setSysid(String sysid) { + this.sysid = sysid; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + public String getHardwareId() { + return hardwareId; + } + + public void setHardwareId(String hardwareId) { + this.hardwareId = hardwareId; + } + + public String getApplicant() { + return applicant; + } + + public void setApplicant(String applicant) { + this.applicant = applicant; + } + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public String getMemo() { + return memo; + } + + public void setMemo(String memo) { + this.memo = memo; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getClientType() { + return clientType; + } + + public void setClientType(String clientType) { + this.clientType = clientType; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/CommonResponse.java b/app/src/main/java/com/bbitcn/silk/model/net/CommonResponse.java new file mode 100644 index 0000000..7b5e5a5 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/CommonResponse.java @@ -0,0 +1,41 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * 通用网络请求 + * Data值为唯一值的 + */ +public class CommonResponse { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private Object data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/Login.java b/app/src/main/java/com/bbitcn/silk/model/net/Login.java new file mode 100644 index 0000000..389dcd6 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/Login.java @@ -0,0 +1,115 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description 登录获取Token + * @Author DuanKaiji + * @CreateTime 2024年03月27日 13:59 + */ +public class Login { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("AccessToken") + private String accessToken; + @SerializedName("ExpiresIn") + private Integer expiresIn; + @SerializedName("TokenType") + private String tokenType; + @SerializedName("RefreshToken") + private String refreshToken; + @SerializedName("ExpireTime") + private String expireTime; + @SerializedName("UserId") + private String userId; + @SerializedName("UserName") + private String userName; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getExpireTime() { + return expireTime; + } + + public void setExpireTime(String expireTime) { + this.expireTime = expireTime; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/QueryList.java b/app/src/main/java/com/bbitcn/silk/model/net/QueryList.java new file mode 100644 index 0000000..def225e --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/QueryList.java @@ -0,0 +1,273 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 08:59:59 + */ +public class QueryList { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("pageinfo") + private PageinfoDTO pageinfo; + @SerializedName("data") + private List data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public PageinfoDTO getPageinfo() { + return pageinfo; + } + + public void setPageinfo(PageinfoDTO pageinfo) { + this.pageinfo = pageinfo; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public static class PageinfoDTO { + @SerializedName("Paged") + private Boolean paged; + @SerializedName("Page") + private Integer page; + @SerializedName("Limit") + private Integer limit; + @SerializedName("Count") + private Integer count; + @SerializedName("PageCount") + private Integer pageCount; + + public Boolean getPaged() { + return paged; + } + + public void setPaged(Boolean paged) { + this.paged = paged; + } + + public Integer getPage() { + return page; + } + + public void setPage(Integer page) { + this.page = page; + } + + public Integer getLimit() { + return limit; + } + + public void setLimit(Integer limit) { + this.limit = limit; + } + + public Integer getCount() { + return count; + } + + public void setCount(Integer count) { + this.count = count; + } + + public Integer getPageCount() { + return pageCount; + } + + public void setPageCount(Integer pageCount) { + this.pageCount = pageCount; + } + } + + public static class DataDTO { + @SerializedName("CzSysid") + private String czSysid; + @SerializedName("CzCode") + private String czCode; + @SerializedName("NhName") + private String nhName; + @SerializedName("StatusCode") + private Integer statusCode; + @SerializedName("Status") + private String status; + @SerializedName("Items") + private List items; + + public String getCzSysid() { + return czSysid; + } + + public void setCzSysid(String czSysid) { + this.czSysid = czSysid; + } + + public String getCzCode() { + return czCode; + } + + public void setCzCode(String czCode) { + this.czCode = czCode; + } + + public String getNhName() { + return nhName; + } + + public void setNhName(String nhName) { + this.nhName = nhName; + } + + public Integer getStatusCode() { + return statusCode; + } + + public void setStatusCode(Integer statusCode) { + this.statusCode = statusCode; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("SgTypeSysid") + private String sgTypeSysid; + @SerializedName("SgTypeName") + private String sgTypeName; + @SerializedName("AllowEditPrice") + private Integer allowEditPrice; + @SerializedName("Baoshu") + private Integer baoshu; + @SerializedName("Maozhong") + private Double maozhong; + @SerializedName("Pizhong") + private Double pizhong; + @SerializedName("Kouzhong") + private Double kouzhong; + @SerializedName("Jingzhong") + private Double jingzhong; + @SerializedName("Price") + private Double price; + @SerializedName("Jine") + private Double jine; + + public String getSgTypeSysid() { + return sgTypeSysid; + } + + public void setSgTypeSysid(String sgTypeSysid) { + this.sgTypeSysid = sgTypeSysid; + } + + public String getSgTypeName() { + return sgTypeName; + } + + public void setSgTypeName(String sgTypeName) { + this.sgTypeName = sgTypeName; + } + + public Integer getAllowEditPrice() { + return allowEditPrice; + } + + public void setAllowEditPrice(Integer allowEditPrice) { + this.allowEditPrice = allowEditPrice; + } + + public Integer getBaoshu() { + return baoshu; + } + + public void setBaoshu(Integer baoshu) { + this.baoshu = baoshu; + } + + public Double getMaozhong() { + return maozhong; + } + + public void setMaozhong(Double maozhong) { + this.maozhong = maozhong; + } + + public Double getPizhong() { + return pizhong; + } + + public void setPizhong(Double pizhong) { + this.pizhong = pizhong; + } + + public Double getKouzhong() { + return kouzhong; + } + + public void setKouzhong(Double kouzhong) { + this.kouzhong = kouzhong; + } + + public Double getJingzhong() { + return jingzhong; + } + + public void setJingzhong(Double jingzhong) { + this.jingzhong = jingzhong; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Double getJine() { + return jine; + } + + public void setJine(Double jine) { + this.jine = jine; + } + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/SilkwormType.java b/app/src/main/java/com/bbitcn/silk/model/net/SilkwormType.java new file mode 100644 index 0000000..20a5583 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/SilkwormType.java @@ -0,0 +1,87 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 09:33:02 + */ +public class SilkwormType { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private List data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("sysid") + private String sysid; + @SerializedName("name") + private String name; + @SerializedName("group") + private String group; + @SerializedName("sort") + private Integer sort; + + public String getSysid() { + return sysid; + } + + public void setSysid(String sysid) { + this.sysid = sysid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public Integer getSort() { + return sort; + } + + public void setSort(Integer sort) { + this.sort = sort; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/Statistics.java b/app/src/main/java/com/bbitcn/silk/model/net/Statistics.java new file mode 100644 index 0000000..84eabca --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/Statistics.java @@ -0,0 +1,200 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:58:17 + */ +public class Statistics { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("summaozhong") + private Double summaozhong; + @SerializedName("sumpizhong") + private Double sumpizhong; + @SerializedName("sumkouzhong") + private Double sumkouzhong; + @SerializedName("sumjingzhong") + private Double sumjingzhong; + @SerializedName("price") + private Double price; + @SerializedName("summoney") + private Double summoney; + @SerializedName("items") + private List items; + + public Double getSummaozhong() { + return summaozhong; + } + + public void setSummaozhong(Double summaozhong) { + this.summaozhong = summaozhong; + } + + public Double getSumpizhong() { + return sumpizhong; + } + + public void setSumpizhong(Double sumpizhong) { + this.sumpizhong = sumpizhong; + } + + public Double getSumkouzhong() { + return sumkouzhong; + } + + public void setSumkouzhong(Double sumkouzhong) { + this.sumkouzhong = sumkouzhong; + } + + public Double getSumjingzhong() { + return sumjingzhong; + } + + public void setSumjingzhong(Double sumjingzhong) { + this.sumjingzhong = sumjingzhong; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Double getSummoney() { + return summoney; + } + + public void setSummoney(Double summoney) { + this.summoney = summoney; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("sgtypesysid") + private String sgtypesysid; + @SerializedName("sgtypename") + private String sgtypename; + @SerializedName("maozhong") + private Double maozhong; + @SerializedName("pizhong") + private Double pizhong; + @SerializedName("kouzhong") + private Double kouzhong; + @SerializedName("jingzhong") + private Double jingzhong; + @SerializedName("price") + private Double price; + @SerializedName("money") + private Double money; + + public String getSgtypesysid() { + return sgtypesysid; + } + + public void setSgtypesysid(String sgtypesysid) { + this.sgtypesysid = sgtypesysid; + } + + public String getSgtypename() { + return sgtypename; + } + + public void setSgtypename(String sgtypename) { + this.sgtypename = sgtypename; + } + + public Double getMaozhong() { + return maozhong; + } + + public void setMaozhong(Double maozhong) { + this.maozhong = maozhong; + } + + public Double getPizhong() { + return pizhong; + } + + public void setPizhong(Double pizhong) { + this.pizhong = pizhong; + } + + public Double getKouzhong() { + return kouzhong; + } + + public void setKouzhong(Double kouzhong) { + this.kouzhong = kouzhong; + } + + public Double getJingzhong() { + return jingzhong; + } + + public void setJingzhong(Double jingzhong) { + this.jingzhong = jingzhong; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public Double getMoney() { + return money; + } + + public void setMoney(Double money) { + this.money = money; + } + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/ToConvert.java b/app/src/main/java/com/bbitcn/silk/model/net/ToConvert.java new file mode 100644 index 0000000..54f4222 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/ToConvert.java @@ -0,0 +1,57 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:12:51 + */ +public class ToConvert { + + @SerializedName("czsysid") + private String czsysid; + @SerializedName("items") + private List items; + + public String getCzsysid() { + return czsysid; + } + + public void setCzsysid(String czsysid) { + this.czsysid = czsysid; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("oldsgtypesysid") + private String oldsgtypesysid; + @SerializedName("newsgtypesysid") + private String newsgtypesysid; + + public String getOldsgtypesysid() { + return oldsgtypesysid; + } + + public void setOldsgtypesysid(String oldsgtypesysid) { + this.oldsgtypesysid = oldsgtypesysid; + } + + public String getNewsgtypesysid() { + return newsgtypesysid; + } + + public void setNewsgtypesysid(String newsgtypesysid) { + this.newsgtypesysid = newsgtypesysid; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/ToTareSubmit.java b/app/src/main/java/com/bbitcn/silk/model/net/ToTareSubmit.java new file mode 100644 index 0000000..3f6159f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/ToTareSubmit.java @@ -0,0 +1,96 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 17:36:22 + */ +public class ToTareSubmit { + + @SerializedName("czsysid") + private String czsysid; + @SerializedName("items") + private List items; + + public String getCzsysid() { + return czsysid; + } + + public void setCzsysid(String czsysid) { + this.czsysid = czsysid; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("sgtypesysid") + private String sgtypesysid; + @SerializedName("pizhong") + private Double pizhong; + @SerializedName("kouzhong") + private Double kouzhong; + @SerializedName("price") + private Double price; + + public String getSgtypesysid() { + return sgtypesysid; + } + + public void setSgtypesysid(String sgtypesysid) { + this.sgtypesysid = sgtypesysid; + } + + public Double getPizhong() { + return pizhong; + } + + public void setPizhong(Double pizhong) { + this.pizhong = pizhong; + } + + public Double getKouzhong() { + return kouzhong; + } + + public void setKouzhong(Double kouzhong) { + this.kouzhong = kouzhong; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + /** + * 重写equals方法 使用sgtypesysid判断是否相等 + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof ItemsDTO) { + return ((ItemsDTO) obj).getSgtypesysid().equals(this.sgtypesysid); + } + return super.equals(obj); + } + + /** + * 重写hashCode方法 使用sgtypesysid的hashCode + */ + @Override + public int hashCode() { + return sgtypesysid.hashCode(); + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/TodayPrice.java b/app/src/main/java/com/bbitcn/silk/model/net/TodayPrice.java new file mode 100644 index 0000000..75c3384 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/TodayPrice.java @@ -0,0 +1,100 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月01日 11:00:56 + */ +public class TodayPrice { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("RefreshTime") + private String refreshTime; + @SerializedName("Items") + private List items; + + public String getRefreshTime() { + return refreshTime; + } + + public void setRefreshTime(String refreshTime) { + this.refreshTime = refreshTime; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public static class ItemsDTO { + @SerializedName("name") + private String name; + @SerializedName("minprice") + private String minprice; + @SerializedName("maxprice") + private String maxprice; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMinprice() { + return minprice; + } + + public void setMinprice(String minprice) { + this.minprice = minprice; + } + + public String getMaxprice() { + return maxprice; + } + + public void setMaxprice(String maxprice) { + this.maxprice = maxprice; + } + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/Update.java b/app/src/main/java/com/bbitcn/silk/model/net/Update.java new file mode 100644 index 0000000..3a09528 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/Update.java @@ -0,0 +1,85 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月02日 16:16:42 + */ +public class Update { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("VersionNumber") + private Integer versionNumber; + @SerializedName("VersionName") + private String versionName; + @SerializedName("VersionDescription") + private String versionDescription; + @SerializedName("Url") + private String url; + + public Integer getVersionNumber() { + return versionNumber; + } + + public void setVersionNumber(Integer versionNumber) { + this.versionNumber = versionNumber; + } + + public String getVersionName() { + return versionName; + } + + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + public String getVersionDescription() { + return versionDescription; + } + + public void setVersionDescription(String versionDescription) { + this.versionDescription = versionDescription; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/UserInfo.java b/app/src/main/java/com/bbitcn/silk/model/net/UserInfo.java new file mode 100644 index 0000000..dbe24b1 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/UserInfo.java @@ -0,0 +1,134 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月02日 10:19:20 + */ +public class UserInfo { + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("LoginAccount") + private String loginAccount; + @SerializedName("UserSysID") + private String userSysID; + @SerializedName("UserName") + private String userName; + @SerializedName("RoleName") + private String roleName; + @SerializedName("DepName") + private String depName; + @SerializedName("DepSysid") + private String depSysid; + @SerializedName("TenantID") + private Long tenantID; + @SerializedName("TenantCode") + private String tenantCode; + @SerializedName("TenantName") + private String tenantName; + + public String getLoginAccount() { + return loginAccount; + } + + public void setLoginAccount(String loginAccount) { + this.loginAccount = loginAccount; + } + + public String getUserSysID() { + return userSysID; + } + + public void setUserSysID(String userSysID) { + this.userSysID = userSysID; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getDepName() { + return depName; + } + + public void setDepName(String depName) { + this.depName = depName; + } + + public String getDepSysid() { + return depSysid; + } + + public void setDepSysid(String depSysid) { + this.depSysid = depSysid; + } + + public Long getTenantID() { + return tenantID; + } + + public void setTenantID(Long tenantID) { + this.tenantID = tenantID; + } + + public String getTenantCode() { + return tenantCode; + } + + public void setTenantCode(String tenantCode) { + this.tenantCode = tenantCode; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/model/net/Weather.java b/app/src/main/java/com/bbitcn/silk/model/net/Weather.java new file mode 100644 index 0000000..f58cb80 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/model/net/Weather.java @@ -0,0 +1,200 @@ +package com.bbitcn.silk.model.net; + +import com.google.gson.annotations.SerializedName; + +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月07日 11:24:17 + */ +public class Weather { + + @SerializedName("result") + private Boolean result; + @SerializedName("msg") + private String msg; + @SerializedName("data") + private DataDTO data; + + public Boolean getResult() { + return result; + } + + public void setResult(Boolean result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + + public static class DataDTO { + @SerializedName("city") + private String city; + @SerializedName("adcode") + private String adcode; + @SerializedName("province") + private String province; + @SerializedName("reporttime") + private String reporttime; + @SerializedName("casts") + private List casts; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getAdcode() { + return adcode; + } + + public void setAdcode(String adcode) { + this.adcode = adcode; + } + + public String getProvince() { + return province; + } + + public void setProvince(String province) { + this.province = province; + } + + public String getReporttime() { + return reporttime; + } + + public void setReporttime(String reporttime) { + this.reporttime = reporttime; + } + + public List getCasts() { + return casts; + } + + public void setCasts(List casts) { + this.casts = casts; + } + + public static class CastsDTO { + @SerializedName("date") + private String date; + @SerializedName("week") + private String week; + @SerializedName("dayweather") + private String dayweather; + @SerializedName("nightweather") + private String nightweather; + @SerializedName("daytemp") + private String daytemp; + @SerializedName("nighttemp") + private String nighttemp; + @SerializedName("daywind") + private String daywind; + @SerializedName("nightwind") + private String nightwind; + @SerializedName("daypower") + private String daypower; + @SerializedName("nightpower") + private String nightpower; + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getWeek() { + return week; + } + + public void setWeek(String week) { + this.week = week; + } + + public String getDayweather() { + return dayweather; + } + + public void setDayweather(String dayweather) { + this.dayweather = dayweather; + } + + public String getNightweather() { + return nightweather; + } + + public void setNightweather(String nightweather) { + this.nightweather = nightweather; + } + + public String getDaytemp() { + return daytemp; + } + + public void setDaytemp(String daytemp) { + this.daytemp = daytemp; + } + + public String getNighttemp() { + return nighttemp; + } + + public void setNighttemp(String nighttemp) { + this.nighttemp = nighttemp; + } + + public String getDaywind() { + return daywind; + } + + public void setDaywind(String daywind) { + this.daywind = daywind; + } + + public String getNightwind() { + return nightwind; + } + + public void setNightwind(String nightwind) { + this.nightwind = nightwind; + } + + public String getDaypower() { + return daypower; + } + + public void setDaypower(String daypower) { + this.daypower = daypower; + } + + public String getNightpower() { + return nightpower; + } + + public void setNightpower(String nightpower) { + this.nightpower = nightpower; + } + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivity.java new file mode 100644 index 0000000..e0bb2e2 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivity.java @@ -0,0 +1,64 @@ +package com.bbitcn.silk.ui.activity; + +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bbitcn.silk.adapter.AboutAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityAboutBinding; +import com.bbitcn.silk.databinding.ItemAboutBinding; +import com.bbitcn.silk.model.net.About; + +import java.util.List; + +/** + * @Description 关于界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class AboutActivity extends BaseActivity { + AboutAdapter adapter; + + @Override + public void initView() { + presenter.getAboutInfo(); + adapter = new AboutAdapter(null, ItemAboutBinding.class); + binding.recycleView.setAdapter(adapter); + binding.recycleView.setLayoutManager(new LinearLayoutManager(mContext)); + binding.clSystem.setVisibility(View.GONE); + presenter.getAboutLiveData().observe(this, about -> { + //刷新数据 + binding.tvAbout.setText(about.getAbout()); + List items = about.getItems(); + if (items == null || items.size() == 0) { + return; + } + About.DataDTO.ItemsDTO itemsDTO = items.get(0); + binding.tvTitle.setText(itemsDTO.getName()); + adapter.setDataList(itemsDTO.getItems()); + if (items.size() < 2) { + return; + } + itemsDTO = items.get(1); + binding.clSystem.setVisibility(View.VISIBLE); + //显示甲方数据 + if (itemsDTO.getItems().size() > 0 && itemsDTO.getItems().get(0) != null) { + binding.tvKey1.setText(itemsDTO.getItems().get(0).getName()); + binding.tvValue1.setText(itemsDTO.getItems().get(0).getValue()); + } + if (itemsDTO.getItems().size() > 1 && itemsDTO.getItems().get(1) != null) { + binding.tvKey2.setText(itemsDTO.getItems().get(1).getName()); + binding.tvValue2.setText(itemsDTO.getItems().get(1).getValue()); + } + }); + + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivityPresenter.java new file mode 100644 index 0000000..415ecc1 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/AboutActivityPresenter.java @@ -0,0 +1,58 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.About; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年4月7日 15:47:42 + */ +public class AboutActivityPresenter extends BasePresenter { + MutableLiveData aboutLiveData = new MutableLiveData<>(); + + public AboutActivityPresenter(@NonNull Application application) { + super(application); + } + + public MutableLiveData getAboutLiveData() { + return aboutLiveData; + } + + public void getAboutInfo() { + showLoadingDialog("正在加载信息"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAboutInfo, false); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(About model) { + if (model.getResult()) { + aboutLiveData.setValue(model.getData()); + } else { + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivity.java new file mode 100644 index 0000000..21299cc --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivity.java @@ -0,0 +1,69 @@ +package com.bbitcn.silk.ui.activity; + +import android.transition.Fade; +import android.transition.TransitionManager; +import android.view.View; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityAuthBinding; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.global.Global; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.StringUtils; + +/** + * @Description 授权界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class AuthActivity extends BaseActivity { + + @Override + public void initView() { + if (!"未授权".equals(Global.getCompanyId())) { + presenter.checkAuth(); + } + binding.tvDeviceId.setText(Global.getDeviceId()); + presenter.getNotice().observe(this, integer -> { + binding.clCheckSuccess.setVisibility(View.VISIBLE); + binding.clCheck.setVisibility(View.GONE); + binding.tvCompanyId.setText(Global.getCompanyId()); + binding.tvName.setText(Global.getRegName()); + binding.tvTel.setText(Global.getRegTel()); + if (integer == 1) { + //授权成功 + binding.tvState.setText("已授权"); + binding.ivState.setImageResource(R.mipmap.check_success); + } else if (integer == 2) { + //待授权 + binding.tvState.setText("待授权"); + binding.ivState.setImageResource(R.mipmap.tips); + } + }); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + //申请授权 + binding.btnAuto.setOnClickListener(v -> { + String id = binding.etId.getText().toString(); + String name = binding.etName.getText().toString(); + String tel = binding.etTel.getText().toString(); + String pwd = binding.etPwd.getText().toString(); + if (StringUtils.isEmpty(id) || StringUtils.isEmpty(name) || StringUtils.isEmpty(tel)) { + new TipsDialog(mContext, "请先完善信息").show(); + } else { + presenter.auth(id, name, tel, pwd); + } + }); + binding.rlDeviceId.setOnClickListener(v -> { + //开始动画 + TransitionManager.beginDelayedTransition(binding.llInput, new Fade()); + binding.rlPwd.setVisibility(binding.rlPwd.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + }); + + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivityPresenter.java new file mode 100644 index 0000000..95fc747 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/AuthActivityPresenter.java @@ -0,0 +1,150 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.Auth; +import com.bbitcn.silk.model.net.CheckAuth; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.StringUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class AuthActivityPresenter extends BasePresenter { + + private MutableLiveData notice = new MutableLiveData<>(); + + + public AuthActivityPresenter(@NonNull Application application) { + super(application); + + } + + public MutableLiveData getNotice() { + return notice; + } + + /** + * 检查授权状态 + */ + public void checkAuth() { + showLoadingDialog("正在检查授权状态"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.checkAuth, false); + params.addParameter("TenantCode", Global.getCompanyId()); + params.addParameter("HardwareId", Global.getDeviceId()); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(CheckAuth model) { + if (model.getResult()) { + if (model.getData().getFlag() == 1) { + MMKVUtil.put(Global.COMPANY_ID, model.getData().getTenantCode()); + MMKVUtil.put(Global.REG_NAME, model.getData().getApplicant()); + MMKVUtil.put(Global.REG_TEL, model.getData().getPhone()); + MyLog.app("状态为:已授权"); + notice.postValue(1); + } else { + MyLog.app("状态为:待授权"); + notice.postValue(2); + } + } else { + MyLog.appError("授权失败"); + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("网络错误授权失败"); + notice.postValue(0); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 进行授权 + * + * @param id 公司ID + * @param name 授权人名 + * @param tel 授权人电话 + */ + public void auth(String id, String name, String tel, String pwd) { + showLoadingDialog("正在授权"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.auth, false); + if(!StringUtils.isEmpty(pwd)){ + // 将pwd进行MD5加密 + pwd = EncryptUtils.encryptMD5ToString(pwd.getBytes()); + // 并全部大写 + pwd = pwd.toUpperCase(); + params.addQueryStringParameter("pwd", pwd); + } + params.addParameter("TenantCode", id); + params.addParameter("HardwareId", Global.getDeviceId()); + params.addParameter("Applicant", name); + params.addParameter("Job", ""); + params.addParameter("Memo", ""); + params.addParameter("ClientType", 2); + params.addParameter("Phone", tel); + x.http().put(params, new XHttpShorthand() { + @Override + public void success(Auth model) { + if (model.getResult()) { + if (model.getData().getFlag() == 1) { + MMKVUtil.put(Global.COMPANY_ID, model.getData().getTenantCode()); + MMKVUtil.put(Global.REG_NAME, model.getData().getApplicant()); + MMKVUtil.put(Global.REG_TEL, model.getData().getPhone()); + MyLog.app("状态为:已授权"); + notice.postValue(1); + } else { + MMKVUtil.put(Global.COMPANY_ID, id); + MMKVUtil.put(Global.REG_NAME, name); + MMKVUtil.put(Global.REG_TEL, tel); + MyLog.app("状态为:待授权"); + notice.postValue(2); + } + } else { + MyLog.appError("授权失败"); + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("网络错误授权失败"); + notice.postValue(0); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 取消授权 + */ + public void cancelAuth() { + + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivity.java new file mode 100644 index 0000000..902494f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivity.java @@ -0,0 +1,86 @@ +package com.bbitcn.silk.ui.activity; + +import android.view.View; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.adapter.ConvertAdapter; +import com.bbitcn.silk.adapter.ItemContentAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityConvertBinding; +import com.bbitcn.silk.databinding.ItemConvertBinding; +import com.bbitcn.silk.databinding.ItemInfoContentBinding; +import com.bbitcn.silk.model.InfoContent; +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.model.net.ToConvert; +import com.bbitcn.silk.utils.database.AllTypeDataBase; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.KeyboardUtils; +import com.blankj.utilcode.util.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class ConvertActivity extends BaseActivity { + QueryList.DataDTO data; + + @Override + public void initView() { + data = GsonUtils.fromJson(getIntent().getStringExtra("data"), QueryList.DataDTO.class); + binding.include.tvName.setText(data.getNhName()); + binding.include.tvId.setText(data.getCzCode()); + binding.include.recycleView.setLayoutManager(new LinearLayoutManager(mContext)); + List temp = new ArrayList<>(); + for (QueryList.DataDTO.ItemsDTO itemsDTO : data.getItems()) { + temp.add(new InfoContent(itemsDTO.getSgTypeName(), itemsDTO.getBaoshu(), itemsDTO.getMaozhong(), itemsDTO.getPrice())); + } + binding.include.cardState.setVisibility(View.GONE); + binding.include.recycleView.setAdapter(new ItemContentAdapter(temp, ItemInfoContentBinding.class, null)); + binding.rvConvert.setLayoutManager(new LinearLayoutManager(mContext)); + List temp2 = new ArrayList<>(); + for (QueryList.DataDTO.ItemsDTO itemsDTO : data.getItems()) { + temp2.add(new InfoContent(itemsDTO.getSgTypeName(), itemsDTO.getMaozhong())); + } + binding.rvConvert.setAdapter(new ConvertAdapter(temp2, ItemConvertBinding.class)); + presenter.getNotice().observe(this, integer -> { + if (integer == 0) { + finish(); + } + }); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + binding.btnConvert.setOnClickListener(v -> { + ToConvert convert = new ToConvert(); + convert.setCzsysid(data.getCzSysid()); + List items = new ArrayList<>(); + RecyclerView.Adapter adapter = binding.rvConvert.getAdapter(); + for (int i = 0; i < adapter.getItemCount(); i++) { + RecyclerView.ViewHolder viewHolder = binding.rvConvert.findViewHolderForAdapterPosition(i); + if (viewHolder != null) { + String tvOld = ((TextView) viewHolder.itemView.findViewById(R.id.tv_old)).getText().toString(); + String tvNew = ((TextView) viewHolder.itemView.findViewById(R.id.tv_new)).getText().toString(); + if (!StringUtils.isEmpty(tvNew)) { + ToConvert.ItemsDTO itemsDTO = new ToConvert.ItemsDTO(); + itemsDTO.setOldsgtypesysid(AllTypeDataBase.getSysIdByName(tvOld)); + itemsDTO.setNewsgtypesysid(AllTypeDataBase.getSysIdByName(tvNew)); + items.add(itemsDTO); + } + } + } + convert.setItems(items); + presenter.convert(convert); + }); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivityPresenter.java new file mode 100644 index 0000000..9be3a27 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/ConvertActivityPresenter.java @@ -0,0 +1,67 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.model.net.ToConvert; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class ConvertActivityPresenter extends BasePresenter { + MutableLiveData notice = new MutableLiveData<>(); + + public ConvertActivityPresenter(@NonNull Application application) { + super(application); + } + + public MutableLiveData getNotice() { + return notice; + } + + /** + * 转换 + * + * @param convert + */ + public void convert(ToConvert convert) { + showLoadingDialog("正在转换"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.toConvert); + params.addParameter("czsysid", convert.getCzsysid()); + params.addParameter("items", convert.getItems()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + if (model.getResult()) { + successTips("转换成功"); + notice.setValue(0); + } else { + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivity.java new file mode 100644 index 0000000..b9fae8a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivity.java @@ -0,0 +1,39 @@ +package com.bbitcn.silk.ui.activity; + +import android.content.Intent; + +import androidx.fragment.app.FragmentTransaction; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityKindsBinding; +import com.bbitcn.silk.ui.fragment.QueryFragment; + +/** + * @Description 茧别转换界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class KindsActivity extends BaseActivity { + + @Override + public void initView() { + presenter.getAllType(); + } + + + @Override + protected void onResume() { + super.onResume(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment_container, new QueryFragment(3, 2, 6)); + transaction.commit(); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivityPresenter.java new file mode 100644 index 0000000..7a4cc33 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/KindsActivityPresenter.java @@ -0,0 +1,55 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.AllType; +import com.bbitcn.silk.utils.database.AllTypeDataBase; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class KindsActivityPresenter extends BasePresenter { + + public KindsActivityPresenter(@NonNull Application application) { + super(application); + } + + /** + * 查询所有收购类型 + */ + public void getAllType() { + showLoadingDialog("正在获取所有茧别"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getAllType); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(AllType model) { + if (model.getResult()) { + AllTypeDataBase.setAllType(model.getData()); + } else { + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivity.java new file mode 100644 index 0000000..3aae4c8 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivity.java @@ -0,0 +1,94 @@ +package com.bbitcn.silk.ui.activity; + +import android.content.Intent; +import android.view.View; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.ActivityLoginBinding; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.ui.dialog.TextViewDialog; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.MyUtil; +import com.bbitcn.silk.utils.global.Global; +import com.blankj.utilcode.util.AppUtils; +import com.blankj.utilcode.util.StringUtils; + +/** + * @Description 登录界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class LoginActivity extends BaseActivity { + @Override + public void initView() { + presenter.setActivity(this); + binding.tvVersion.setText("版本号:" + MyUtil.getVersionName(mContext)); + binding.tiUsername.setText(Global.getUserAccount()); + } + + @Override + public void initListener() { + super.initListener(); + binding.btnAbout.setOnClickListener(this); + binding.btnLogin.setOnClickListener(this); + binding.btnCheckUpdate.setOnClickListener(this); + binding.btnDeviceAuth.setOnClickListener(this); + binding.btnRetrievePassword.setOnClickListener(this); + presenter.getNotice().observe(this, integer -> { + if (integer == 0) { + //登录成功 + finish(); + startActivity(new Intent(mContext, MainActivity.class)); + } + }); + binding.llCompanyId.setOnLongClickListener(v -> { + new TextViewDialog(mContext, "企业编号", null, new BaseDialog.CustomString() { + @Override + public void listener(String selString) { + MMKVUtil.put(Global.COMPANY_ID, selString); + binding.tvCompanyId.setText("企业编号: " + Global.getCompanyId()); + } + }, false).show(); + return true; + }); + } + + @Override + protected void onResume() { + super.onResume(); + //每次进入登录界面,都要尝试获取企业编号 + binding.tvCompanyId.setText("企业编号: " + Global.getCompanyId()); + } + + + @Override + public void myClick(View view) { + super.myClick(view); + switch (view.getId()) { + case R.id.btn_device_auth -> startActivity(new Intent(mContext, AuthActivity.class)); + case R.id.btn_about -> startActivity(new Intent(mContext, AboutActivity.class)); + case R.id.btn_check_update -> startActivity(new Intent(mContext, UpdateActivity.class)); + case R.id.btn_retrieve_password -> + new TipsDialog(mContext, "PDA暂不支持修改密码,请通过电脑登录您的账号后,在【个人中心】修改密码,电脑端修改后,手机端同步生效。").show(); + case R.id.btn_login -> { + String username = binding.tiUsername.getText().toString(); + String password = binding.tiPassword.getText().toString(); + if (StringUtils.isEmpty(username)) { + new TipsDialog(mContext, "用户名不可为空").show(); + } else if (StringUtils.isEmpty(password)) { + new TipsDialog(mContext, "密码不可为空").show(); + } else { + presenter.login(username, password); + } + } + } + } + + @Override + public void onBackPressed() { + new ConfirmDialog(mContext, "提示", "是否退出程序?", () -> AppUtils.exitApp()).show(); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivityPresenter.java new file mode 100644 index 0000000..e28f453 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/LoginActivityPresenter.java @@ -0,0 +1,123 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Activity; +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.Login; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.EncryptUtils; +import com.hjq.permissions.OnPermissionCallback; +import com.hjq.permissions.Permission; +import com.hjq.permissions.XXPermissions; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.util.List; + +/** + * @Author DuanKaiji + * @CreateTime 2024年03月27日 13:51 + */ +public class LoginActivityPresenter extends BasePresenter { + + private MutableLiveData notice = new MutableLiveData<>(); + + public MutableLiveData getNotice() { + return notice; + } + + public LoginActivityPresenter(@NonNull Application application) { + super(application); + checkPermission(); + } + + /** + * 登录 + * + * @param account 账号 + * @param password 密码 + */ + public void login(String account, String password) { + showLoadingDialog("正在登录中,请稍等"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.login,false); + params.addParameter("Platform", "1"); + params.addParameter("HardwareId", Global.getDeviceId()); + params.addParameter("TenantCode", Global.getCompanyId()); + params.addParameter("Account", account); + params.addParameter("Password", EncryptUtils.encryptMD5ToString(password.getBytes())); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(Login model) { + MyLog.app("登录成功"); + if(model.getResult()){ + MMKVUtil.put(Global.USER_ACCOUNT, account); + MMKVUtil.put(Global.PASSWORD, password); + MMKVUtil.put(Global.TOKEN, model.getData().getTokenType() + " " + model.getData().getAccessToken()); + notice.setValue(0); + }else{ + new ConfirmDialog(currentActivity, "提示", "登录失败:" + model.getMsg(), null).show(); + } + } + + @Override + public void error(Throwable e) { + new ConfirmDialog(currentActivity, "提示", "登录失败:" + e.getMessage(), null).show(); + MyLog.appError("Token获取失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + private Activity currentActivity; + + public void setActivity(Activity activity) { + this.currentActivity = activity; + } + + /** + * 检查与请求权限 + */ + void checkPermission() { + XXPermissions.with(currentActivity) + .permission(Permission.WRITE_EXTERNAL_STORAGE) + .permission(Permission.READ_EXTERNAL_STORAGE) + .permission(Permission.READ_PHONE_STATE) + .permission(Permission.REQUEST_INSTALL_PACKAGES) + .request(new OnPermissionCallback() { + @Override + public void onGranted(@NonNull List permissions, boolean allGranted) { + if (!allGranted) { + MyLog.appError("获取部分权限成功,但部分权限未正常授予"); + XXPermissions.startPermissionActivity(currentActivity, permissions); + } + } + + @Override + public void onDenied(@NonNull List permissions, boolean doNotAskAgain) { + if (doNotAskAgain) { + MyLog.app("被永久拒绝授权,需手动授予权限"); + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(getApplication(), permissions); + } else { + MyLog.appError("权限请求失败"); + } + } + }); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivity.java new file mode 100644 index 0000000..6b800cb --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivity.java @@ -0,0 +1,104 @@ +package com.bbitcn.silk.ui.activity; + +import android.content.Intent; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; + +import com.bbitcn.silk.adapter.PriceAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityMainBinding; +import com.bbitcn.silk.databinding.ItemPriceBinding; +import com.bbitcn.silk.model.net.TodayPrice; +import com.bbitcn.silk.model.net.Weather; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.ui.dialog.UpdateDialog; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.MyUtil; +import com.bbitcn.silk.utils.global.Global; +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.AppUtils; +import com.blankj.utilcode.util.StringUtils; +import com.blankj.utilcode.util.TimeUtils; + +import java.text.SimpleDateFormat; +import java.util.List; + +public class MainActivity extends BaseActivity { + + @Override + public void initView() { + binding.rvPrice.setLayoutManager(new GridLayoutManager(mContext, 2)); + presenter.getNoticeUpdate().observe(this, model -> { + if (model != null) { + int server = model.getVersionNumber(); + if (server > MyUtil.getVersion(mContext)) { + // 强制升级 + new UpdateDialog(mContext, model.getUrl(), model.getVersionName(), model.getVersionDescription(), true).show(); + return; + } + } + successTips("已是最新版本"); + if (!StringUtils.isEmpty(MMKVUtil.get(Global.TOKEN))) { + //之前登陆过 -> 静默登录(后获取用户信息) + MyUtil.login(() -> presenter.getRefreshUserInfo()); + } else { + //之前没登陆过 - > 跳转到登录界面 + ActivityUtils.finishAllActivities(true); + startActivity(new Intent(mContext, LoginActivity.class)); + } + }); + presenter.getNoticeTodayPrice().observe(this, todayPrice -> { + binding.tvPriceDate.setText("价格获取时间:" + todayPrice.getData().getRefreshTime()); + List items = todayPrice.getData().getItems(); + binding.rvPrice.setAdapter(new PriceAdapter(items, ItemPriceBinding.class)); + binding.tvNoPrice.setVisibility(items.isEmpty() ? View.VISIBLE : View.GONE); + }); + presenter.getNoticeUserInfo().observe(this, userInfo -> { + if (userInfo != null) { + binding.tvUserName.setText(userInfo.getData().getUserName()); + binding.tvCompanyName.setText(userInfo.getData().getDepName()); + binding.tvUserRole.setText(userInfo.getData().getRoleName()); + } + // 获得用户信息后再获取天气与价格 + presenter.getWeather(); + presenter.getPriceOfToday(); + }); + presenter.getNoticeWeather().observe(this, dataDTO -> { + List casts = dataDTO.getCasts(); + Weather.DataDTO.CastsDTO castsDTO = casts.get(0); + binding.tvCity.setText("(" + dataDTO.getCity() + ")"); + binding.tvWeatherDate1.setText(castsDTO.getDate()); + binding.tvWeather1.setText(Math.min(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + + "~" + Math.max(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + "℃ " + + castsDTO.getDayweather()); + castsDTO = casts.get(1); + binding.tvWeatherDate2.setText(castsDTO.getDate()); + binding.tvWeather2.setText(Math.min(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + + "~" + Math.max(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + "℃ " + + castsDTO.getDayweather()); + castsDTO = casts.get(2); + binding.tvWeatherTop.setText(TimeUtils.getChineseWeek(castsDTO.getDate(), new SimpleDateFormat("yyyy-MM-dd"))); + binding.tvWeatherDate3.setText(castsDTO.getDate()); + binding.tvWeather3.setText(Math.min(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + + "~" + Math.max(Integer.parseInt(castsDTO.getDaytemp()), Integer.parseInt(castsDTO.getNighttemp())) + "℃ " + + castsDTO.getDayweather()); + }); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivUserCenter.setOnClickListener(v -> startActivity(new Intent(mContext, UserActivity.class))); + binding.ivQuery.setOnClickListener(v -> startActivity(new Intent(mContext, QueryActivity.class))); + binding.ivPrice.setOnClickListener(v -> startActivity(new Intent(mContext, PriceActivity.class))); + binding.ivTotal.setOnClickListener(v -> startActivity(new Intent(mContext, TotalActivity.class))); + binding.ivKinds.setOnClickListener(v -> startActivity(new Intent(mContext, KindsActivity.class))); + binding.btnRefreshPrice.setOnClickListener(v -> presenter.getPriceOfToday()); + } + + @Override + public void onBackPressed() { + new ConfirmDialog(mContext, "提示", "是否退出程序?", () -> AppUtils.exitApp()).show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivityPresenter.java new file mode 100644 index 0000000..7e8b5a8 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/MainActivityPresenter.java @@ -0,0 +1,181 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; +import android.os.Build; +import android.os.StrictMode; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.model.net.TodayPrice; +import com.bbitcn.silk.model.net.Update; +import com.bbitcn.silk.model.net.UserInfo; +import com.bbitcn.silk.model.net.Weather; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.database.PriceDataBase; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.StringUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年03月27日 13:51 + */ +public class MainActivityPresenter extends BasePresenter { + + private MutableLiveData noticeUpdate = new MutableLiveData<>(); + private MutableLiveData noticeUserInfo = new MutableLiveData<>(); + private MutableLiveData noticeTodayPrice = new MutableLiveData<>(); + private MutableLiveData noticeWeather = new MutableLiveData<>(); + + public MutableLiveData getNoticeUpdate() { + return noticeUpdate; + } + + public MutableLiveData getNoticeWeather() { + return noticeWeather; + } + + public MutableLiveData getNoticeTodayPrice() { + return noticeTodayPrice; + } + + public MutableLiveData getNoticeUserInfo() { + return noticeUserInfo; + } + + public MainActivityPresenter(@NonNull Application application) { + super(application); + checkVersion(); + } + + /** + * 检查是否需要升级APP,以及进行升级 + */ + public void checkVersion() { + showLoadingDialog("正在检测更新中"); + if (Build.VERSION.SDK_INT > 9) { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + } + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getNewVersion, false); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Update model) { + if (model.getData() != null) { + noticeUpdate.setValue(model.getData()); + } else if (!StringUtils.isEmpty(model.getMsg())) { + errorTips(model.getMsg()); + } + } + + @Override + public void onSuccessError(CommonResponse data) { + successTips(data.getMsg()); + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + + /** + * 获取用户信息 + */ + public void getRefreshUserInfo() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getUserInfo); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(UserInfo model) { + MyLog.app("用户信息获取成功"); + noticeUserInfo.setValue(model); + MMKVUtil.put(Global.USER_NAME, model.getData().getUserName()); + MMKVUtil.put(Global.USER_ID, model.getData().getUserSysID()); + MMKVUtil.put(Global.COMPANY_NAME, model.getData().getTenantName()); + MMKVUtil.put(Global.STATION_NAME, model.getData().getDepName()); + MMKVUtil.put(Global.DEP_SYS_ID, model.getData().getDepSysid()); + } + + @Override + public void error(Throwable e) { + MyLog.appError("用户信息获取失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 获取今日价格 + */ + public void getPriceOfToday() { + showLoadingDialog("正在获取今日价格"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getPriceOfToday); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(TodayPrice model) { + MyLog.app("今日价格获取成功"); + PriceDataBase.updateCatch(model.getData().getItems()); + noticeTodayPrice.setValue(model); + } + + @Override + public void error(Throwable e) { + MyLog.appError("今日价格获取失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 获取天气 + */ + public void getWeather() { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.weather); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Weather model) { + noticeWeather.setValue(model.getData()); + } + + @Override + public void error(Throwable e) { + MyLog.appError("今日价格获取失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivity.java new file mode 100644 index 0000000..b80cd7d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivity.java @@ -0,0 +1,39 @@ +package com.bbitcn.silk.ui.activity; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentTransaction; + +import com.bbitcn.silk.R; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityPriceBinding; +import com.bbitcn.silk.ui.fragment.QueryFragment; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +/** + * @Description 扣皮定价界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class PriceActivity extends BaseActivity { + + @Override + public void initView() { + } + + @Override + protected void onResume() { + super.onResume(); + FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); + transaction.replace(R.id.fragment_container, new QueryFragment(3, 1, 1)); + transaction.commit(); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + } + + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivityPresenter.java new file mode 100644 index 0000000..841a5fa --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/PriceActivityPresenter.java @@ -0,0 +1,20 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BasePresenter; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class PriceActivityPresenter extends BasePresenter { + + public PriceActivityPresenter(@NonNull Application application) { + super(application); + + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivity.java new file mode 100644 index 0000000..ff2b13b --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivity.java @@ -0,0 +1,40 @@ +package com.bbitcn.silk.ui.activity; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.adapter.PageAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityQueryBinding; +import com.blankj.utilcode.util.KeyboardUtils; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +/** + * @Description 磅单查询界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class QueryActivity extends BaseActivity { + + @Override + public void initView() { + binding.viewPage2.setAdapter(new PageAdapter( this)); + binding.viewPage2.setOffscreenPageLimit(2); + //禁止用户滑动 + binding.viewPage2.setUserInputEnabled(false); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + new TabLayoutMediator(binding.tabLayout, binding.viewPage2, (tab, i) -> { + if (i == 0) { + tab.setText("待确认售"); + } else { + tab.setText("全部磅单"); + } + }).attach(); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivityPresenter.java new file mode 100644 index 0000000..0a19b6f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/QueryActivityPresenter.java @@ -0,0 +1,20 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BasePresenter; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class QueryActivityPresenter extends BasePresenter { + + public QueryActivityPresenter(@NonNull Application application) { + super(application); + + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivity.java new file mode 100644 index 0000000..320d5b0 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivity.java @@ -0,0 +1,159 @@ +package com.bbitcn.silk.ui.activity; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bbitcn.silk.adapter.ItemContentAdapter; +import com.bbitcn.silk.adapter.TarePageAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityTareBinding; +import com.bbitcn.silk.databinding.ItemInfoContentBinding; +import com.bbitcn.silk.model.InfoContent; +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.model.net.ToTareSubmit; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.ui.dialog.SpinnerDialog; +import com.bbitcn.silk.ui.fragment.TareInfoFragment; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.database.PriceDataBase; +import com.bbitcn.silk.utils.database.TareDataBase; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.global.SpinnerList; +import com.blankj.utilcode.util.GsonUtils; +import com.blankj.utilcode.util.StringUtils; +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.util.ArrayList; +import java.util.List; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 扣皮定价界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class TareActivity extends BaseActivity { + QueryList.DataDTO data; + TarePageAdapter adapter; + + @Override + public void initView() { + data = GsonUtils.fromJson(getIntent().getStringExtra("data"), QueryList.DataDTO.class); + binding.include.tvName.setText(data.getNhName()); + binding.include.tvId.setText(data.getCzCode()); + binding.include.recycleView.setLayoutManager(new LinearLayoutManager(mContext)); + binding.include.view4.setVisibility(View.GONE); + binding.include.llExertFunc.setVisibility(View.VISIBLE); + binding.include.tvConfirm.setVisibility(View.GONE); + binding.include.tvCancel.setOnClickListener(v -> new ConfirmDialog(mContext, "弃售", "确定要弃售" + data.getCzCode() + "吗?", + () -> presenter.cancel(data.getCzSysid())).show()); + List temp = new ArrayList<>(); + for (QueryList.DataDTO.ItemsDTO itemsDTO : data.getItems()) { + temp.add(new InfoContent(itemsDTO.getSgTypeName(), itemsDTO.getBaoshu(), itemsDTO.getMaozhong(), itemsDTO.getPrice())); + } + binding.include.cardState.setVisibility(View.GONE); + binding.include.recycleView.setAdapter(new ItemContentAdapter(temp, ItemInfoContentBinding.class, null)); + adapter = new TarePageAdapter(this, data); + binding.viewPage2.setUserInputEnabled(false); + binding.viewPage2.setAdapter(adapter); + binding.viewPage2.setOffscreenPageLimit(adapter.getItemCount()); + //给tabLayout添加tab + for (int i = 0; i < data.getItems().size(); i++) { + binding.tabLayout.addTab(binding.tabLayout.newTab()); + } + new TabLayoutMediator(binding.tabLayout, binding.viewPage2, new TabLayoutMediator.TabConfigurationStrategy() { + @Override + public void onConfigureTab(@NonNull TabLayout.Tab tab, int i) { + tab.setText(data.getItems().get(i).getSgTypeName()); + } + }).attach(); + presenter.getNotice().observe(this, integer -> { + if (integer == 0) { + //保存成功 + finish(); + //删除缓存 + TareDataBase.removeCatchById(data); + } + }); + } + + @Override + public void initListener() { + super.initListener(); + View.OnClickListener setUnit = view -> new SpinnerDialog(mContext, "默认定价单位", "请选择单位", SpinnerList.UNIT, selString -> { + MMKVUtil.put(Global.UNIT, selString); + binding.tvUnit.setText("单位:" + selString); + Toasty.success(mContext, "设置成功").show(); + refreshPageUnit(); + }).show(); + binding.ivBack.setOnClickListener(v -> finish()); + binding.tvUnit.setOnClickListener(setUnit); + binding.ivUnit.setOnClickListener(setUnit); + binding.btnSubmit.setOnClickListener(v -> { + ToTareSubmit item = new ToTareSubmit(); + item.setCzsysid(data.getCzSysid()); + List itemsDTOS = new ArrayList<>(); + //获取ViewPage2中的每个Fragment + for (int i = 0; i < adapter.getItemCount(); i++) { + TareInfoFragment fragment = adapter.getFragment(i); + double price; + double net; + double skin; + if (StringUtils.isEmpty(fragment.getSkin())) { + errorTips(data.getItems().get(i).getSgTypeName() + "的皮重不能为空"); + return; + } + try { + // 不可为空 不可为“数据错误” + price = Double.parseDouble(fragment.getPrice()); + net = Double.parseDouble(fragment.getNet()); + skin = Double.parseDouble(fragment.getSkin()); + } catch (Exception e) { + errorTips("请检查" + data.getItems().get(i).getSgTypeName() + "的价格、皮重与净重是否正确"); + return; + } + QueryList.DataDTO.ItemsDTO itemsDTO = data.getItems().get(i); + // 不可为0 + if (price == 0) { + errorTips(itemsDTO.getSgTypeName() + "的价格不可为0,请检查"); + return; + } + double[] minMax = PriceDataBase.getCatchById(itemsDTO.getSgTypeName()); + if (price < minMax[0] || price > minMax[1]) { + errorTips(itemsDTO.getSgTypeName() + "的今日价格为¥" + minMax[0] + "~" + minMax[1] + ",请检查"); + return; + } + if (net == 0) { + errorTips(itemsDTO.getSgTypeName() + "的净重不可为0,请检查"); + return; + } + ToTareSubmit.ItemsDTO dto = new ToTareSubmit.ItemsDTO(); + dto.setSgtypesysid(itemsDTO.getSgTypeSysid()); + //可为空 + String ti_skin = fragment.getSkin(); + String ti_tare = fragment.getTare(); + dto.setPizhong(StringUtils.isEmpty(ti_skin) ? 0 : Double.parseDouble(ti_skin) / Global.getUnitInt()); + dto.setKouzhong(StringUtils.isEmpty(ti_tare) ? 0 : Double.parseDouble(ti_tare) / Global.getUnitInt()); + dto.setPrice(price * Global.getUnitInt()); + itemsDTOS.add(dto); + } + item.setItems(itemsDTOS); + presenter.tareSubmit(item); + }); + } + + /** + * 刷新子页面单位 + */ + private void refreshPageUnit() { + for (int i = 0; i < adapter.getItemCount(); i++) { + TareInfoFragment fragment = adapter.getFragment(i); + fragment.refreshUnit(); + } + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivityPresenter.java new file mode 100644 index 0000000..7b8d74d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/TareActivityPresenter.java @@ -0,0 +1,94 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.model.net.ToTareSubmit; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class TareActivityPresenter extends BasePresenter { + MutableLiveData notice = new MutableLiveData<>(); + + public TareActivityPresenter(@NonNull Application application) { + super(application); + } + + public MutableLiveData getNotice() { + return notice; + } + + public void tareSubmit(ToTareSubmit data) { + showLoadingDialog("正在提交中"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.tareSubmit); + params.addParameter("czsysid", data.getCzsysid()); + params.addParameter("items", data.getItems()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + if (model.getResult()) { + successTips("提交成功"); + notice.setValue(0); + } else { + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } + + /** + * 弃售 + */ + public void cancel(String id) { + showLoadingDialog("正在提交弃售中"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.configOrCancel); + params.addQueryStringParameter("sgBillstate", -1); + params.addQueryStringParameter("czsysid", id); + x.http().put(params, new XHttpShorthand() { + @Override + public void success(CommonResponse model) { + if (model.getResult()) { + successTips("提交成功"); + notice.setValue(0); + } else { + errorTips(model.getMsg()); + } + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivity.java new file mode 100644 index 0000000..81724f6 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivity.java @@ -0,0 +1,98 @@ +package com.bbitcn.silk.ui.activity; + +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bbitcn.silk.adapter.ItemContentAdapter; +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityTotalBinding; +import com.bbitcn.silk.databinding.ItemInfoContentBinding; +import com.bbitcn.silk.model.InfoContent; +import com.bbitcn.silk.model.net.Statistics; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.TimeUtils; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +/** + * @Description 收购统计界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class TotalActivity extends BaseActivity { + + @Override + public void initView() { + binding.recycleView.setLayoutManager(new LinearLayoutManager(this)); + presenter.getQueryListLiveData().observe(this, data -> { + if (data != null) { + DecimalFormat df = new DecimalFormat("#.##"); + List infoContents = new ArrayList<>(); + for (Statistics.DataDTO.ItemsDTO itemsDTO : data.getItems()) { + infoContents.add(new InfoContent(itemsDTO.getSgtypename(), itemsDTO.getJingzhong(), itemsDTO.getPrice(), df.format(itemsDTO.getMoney()))); + }// 创建 DecimalFormat 对象并指定输出格式 + binding.tvTotalWeight.setText(df.format(data.getSumjingzhong())); + binding.recycleView.setAdapter(new ItemContentAdapter(infoContents, ItemInfoContentBinding.class, null)); + binding.tvTotalMoney.setText(df.format(data.getSummoney())); + binding.tvTotalPrice.setText(df.format(data.getPrice())); + binding.tvTotalWeight.setText(df.format(data.getSumjingzhong())); + } + }); + //设置默认开始时间 + binding.tvStartDate.setText(TimeUtils.date2String(TimeUtils.getDateByNow(-31, TimeConstants.DAY), "yyyy-MM-dd")); + binding.tvEndDate.setText(TimeUtils.date2String(TimeUtils.getNowDate(), "yyyy-MM-dd")); + presenter.query(binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString()); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + //选择开始日期 + binding.tvStartDate.setOnClickListener(getDateListener(true)); + binding.tvEndDate.setOnClickListener(getDateListener(false)); + } + + /** + * 获取日期选择监听 + */ + private View.OnClickListener getDateListener(boolean isStart) { + return v -> { + Calendar defaultDate = Calendar.getInstance(); + if (isStart) { + defaultDate.setTime(TimeUtils.string2Date(binding.tvStartDate.getText().toString(), "yyyy-MM-dd")); + } else { + defaultDate.setTime(TimeUtils.string2Date(binding.tvEndDate.getText().toString(), "yyyy-MM-dd")); + } + new TimePickerBuilder(mContext, (date, v1) -> { + if (isStart) { + binding.tvStartDate.setText(TimeUtils.date2String(date, "yyyy-MM-dd")); + } else { + binding.tvEndDate.setText(TimeUtils.date2String(date, "yyyy-MM-dd")); + } + //判断时间区间是否合理 + if (TimeUtils.string2Date(binding.tvStartDate.getText().toString(), "yyyy-MM-dd").getTime() + > TimeUtils.string2Date(binding.tvEndDate.getText().toString(), "yyyy-MM-dd").getTime()) { + new TipsDialog(mContext, "开始时间不能大于结束时间").show(); + return; + } + presenter.query(binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString()); + }) + .setDate(defaultDate) + .setContentTextSize(35) + .setTitleSize(35) + .isCyclic(false) + .setSubCalSize(20) + .setType(new boolean[]{true, true, true, false, false, false}) + .build() + .show(); + }; + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivityPresenter.java new file mode 100644 index 0000000..fe40c58 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/TotalActivityPresenter.java @@ -0,0 +1,65 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.Statistics; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class TotalActivityPresenter extends BasePresenter { + private MutableLiveData queryListLiveData = new MutableLiveData<>(); + + public MutableLiveData getQueryListLiveData() { + return queryListLiveData; + } + + + + public TotalActivityPresenter(@NonNull Application application) { + super(application); + } + + public void query( String startDate, String endDate) { + showLoadingDialog("正在查询"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.queryStatistics); + params.addParameter("dtstart", startDate); + params.addParameter("dtend", endDate); + params.addParameter("depsysid", Global.getDepSysId() ); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(Statistics model) { + if (model.getResult()) { + MyLog.app("查询成功"); + queryListLiveData.setValue(model.getData()); + } else { + MyLog.appError("查询失败"); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("查询失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivity.java new file mode 100644 index 0000000..0c19d1a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivity.java @@ -0,0 +1,56 @@ +package com.bbitcn.silk.ui.activity; + +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityUpdateBinding; +import com.bbitcn.silk.ui.dialog.UpdateDialog; +import com.bbitcn.silk.utils.MyUtil; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 升级界面 + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:12:15 + */ +public class UpdateActivity extends BaseActivity { + /** + * 升级地址 + */ + String updateUrl; + /** + * 新版本名 + */ + String lastVersionName; + /** + * 新版本描述 + */ + String versionDescription; + + @Override + public void initView() { + binding.btnUpdate.setEnabled(false); + binding.tvCurVersion.setText(MyUtil.getVersionName(mContext)); + presenter.getVersionInfo().observe(this, model -> { + int server = model.getVersionNumber(); + binding.tvLastVersion.setText(model.getVersionName()); + if (server > MyUtil.getVersion(mContext)) { + //可以升级 + updateUrl = model.getUrl(); + binding.btnUpdate.setEnabled(true); + lastVersionName = model.getVersionName(); + versionDescription = model.getVersionDescription(); + Toasty.success(mContext, "检测到新版本").show(); + } else { + Toasty.success(mContext, "当前已是最新版本").show(); + } + }); + } + + @Override + public void initListener() { + super.initListener(); + binding.ivBack.setOnClickListener(v -> finish()); + //升级 + binding.btnUpdate.setOnClickListener(v -> new UpdateDialog(mContext, updateUrl, lastVersionName, versionDescription).show()); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivityPresenter.java new file mode 100644 index 0000000..53dc3df --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/UpdateActivityPresenter.java @@ -0,0 +1,78 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; +import android.os.Build; +import android.os.StrictMode; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.model.net.Update; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.StringUtils; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:06:01 + */ +public class UpdateActivityPresenter extends BasePresenter { + public MutableLiveData lastVersion = new MutableLiveData<>(""); + public MutableLiveData versionInfo = new MutableLiveData<>(); + + public UpdateActivityPresenter(@NonNull Application application) { + super(application); + checkVersion(); + } + + public MutableLiveData getVersionInfo() { + return versionInfo; + } + + /** + * 检查是否需要升级APP,以及进行升级 + */ + public void checkVersion() { + showLoadingDialog("正在检测更新中"); + if (Build.VERSION.SDK_INT > 9) { + StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); + StrictMode.setThreadPolicy(policy); + } + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.getNewVersion,false); + x.http().get(params, new XHttpShorthand() { + @Override + public void success(Update model) { + if (model.getData() != null) { + versionInfo.setValue(model.getData()); + lastVersion.setValue(model.getData().getVersionName()); + } else if (!StringUtils.isEmpty(model.getMsg())) { + errorTips(model.getMsg()); + } + } + + @Override + public void onSuccessError(CommonResponse data) { + successTips(data.getMsg()); + } + + @Override + public void error(Throwable e) { + errorTips(e.getMessage()); + } + + @Override + public void myFinish() { + super.myFinish(); + DialogUtil.hideLoadingDialog(); + } + }); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivity.java b/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivity.java new file mode 100644 index 0000000..b1bad8e --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivity.java @@ -0,0 +1,61 @@ +package com.bbitcn.silk.ui.activity; + +import android.content.Intent; + +import com.bbitcn.silk.base.BaseActivity; +import com.bbitcn.silk.databinding.ActivityUserBinding; +import com.bbitcn.silk.ui.dialog.ConfirmDialog; +import com.bbitcn.silk.ui.dialog.SpinnerDialog; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.MyUtil; +import com.bbitcn.silk.utils.database.TareDataBase; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.global.SpinnerList; +import com.blankj.utilcode.util.ActivityUtils; + +/** + * @Description 用户中心界面 + * @Author DuanKaiji + * @CreateTime 2024年03月27日 15:12 + */ +public class UserActivity extends BaseActivity { + + @Override + public void initView() { + binding.tvVersion.setText(MyUtil.getVersionName(mContext)); + binding.tvAuthInfo.setText(Global.getCompanyId()); + binding.tvUnit.setText(Global.getUnit()); + binding.tvUserInfo.setText(Global.getUserName()); + binding.tvCompanyName.setText(Global.getCompanyName()); + binding.tvStationName.setText(Global.getStationName()); + } + + @Override + public void initListener() { + super.initListener(); + // 返回 + binding.ivBack.setOnClickListener(v -> finish()); + // 退出登录 + binding.btnLogOut.setOnClickListener(v -> { + new ConfirmDialog(mContext, "退出登录", "确定退出登录吗?", () -> { + MMKVUtil.put(Global.TOKEN, null); + MMKVUtil.put(Global.PASSWORD, null); + ActivityUtils.finishAllActivities(); + startActivity(new Intent(mContext, LoginActivity.class)); + + }).show(); + }); + binding.clUnit.setOnClickListener(view -> new SpinnerDialog(mContext, "默认定价单位", "请选择单位", SpinnerList.UNIT, selString -> { + MMKVUtil.put(Global.UNIT, selString); + binding.tvUnit.setText(selString); + new TipsDialog(mContext, "设置成功").show(); + }).show()); + binding.clAbout.setOnClickListener(v -> startActivity(new Intent(mContext, AboutActivity.class))); + binding.clClear.setOnClickListener(v -> { + TareDataBase.removeCatch(); + successTips("已清空缓存"); + }); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivityPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivityPresenter.java new file mode 100644 index 0000000..6598def --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/activity/UserActivityPresenter.java @@ -0,0 +1,18 @@ +package com.bbitcn.silk.ui.activity; + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BasePresenter; + +/** + * @Author DuanKaiji + * @CreateTime 2024年3月28日 10:04:22 + */ +public class UserActivityPresenter extends BasePresenter { + + public UserActivityPresenter(@NonNull Application application) { + super(application); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/dialog/ConfirmDialog.java b/app/src/main/java/com/bbitcn/silk/ui/dialog/ConfirmDialog.java new file mode 100644 index 0000000..a2be84f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/dialog/ConfirmDialog.java @@ -0,0 +1,38 @@ +package com.bbitcn.silk.ui.dialog; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.DialogCustomBinding; + +/** + * @Description 获得确认通用弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 13:57 + */ +public class ConfirmDialog extends BaseDialog { + Runnable runnable; + + public ConfirmDialog(@NonNull Context context, String title, String message, Runnable runnable) { + super(context); + this.runnable = runnable; + binding.tvTitle.setText(title); + binding.tvMessage.setText(message); + if (runnable != null) { + //询问确定执行的弹窗 + binding.btnYes.setOnClickListener(view -> { + runnable.run(); + dismiss(); + }); + binding.btnNo.setOnClickListener(v -> dismiss()); + } else { + //通知显示的弹窗 + binding.btnYes.setOnClickListener(view -> dismiss()); + binding.btnNo.setVisibility(View.GONE); + } + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/dialog/SpinnerDialog.java b/app/src/main/java/com/bbitcn/silk/ui/dialog/SpinnerDialog.java new file mode 100644 index 0000000..ea381ba --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/dialog/SpinnerDialog.java @@ -0,0 +1,54 @@ +package com.bbitcn.silk.ui.dialog; + +import android.content.Context; +import android.view.View; +import android.widget.ArrayAdapter; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.DialogCustomBinding; +import com.bbitcn.silk.ui.view.CustomSpinnerAdapter; + +import es.dmoral.toasty.Toasty; + +/** + * @Description 下拉框弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 14:08 + */ +public class SpinnerDialog extends BaseDialog { + /** + * 获取下拉框弹窗 + * + * @param context 上下文 + * @param title 标题 + * @param hint 提示信息 会在默认时、未选择点击确定时显示 + * @param list 下拉框列表内容 + * @param listener 点击确定后的事件 + */ + public SpinnerDialog(@NonNull Context context, String title, String hint, String[] list, CustomString listener) { + this(context, title, hint, list, listener, false); + } + + public SpinnerDialog(@NonNull Context context, String title, String hint, String[] list, CustomString listener, boolean canEmpty) { + super(context); + binding.tilSpinner.setVisibility(View.VISIBLE); + binding.tvMessage.setVisibility(View.GONE); + binding.btnNo.setVisibility(View.GONE); + binding.tvTitle.setText(title); + ArrayAdapter adapter = new CustomSpinnerAdapter(context, list); + binding.acInput.setAdapter(adapter); + binding.acInput.setHint(hint); + //需要默认显示已设置的项,还能同时显示所有项 + binding.btnYes.setOnClickListener(view -> { + if (binding.acInput.getText().toString().isEmpty() && !canEmpty) { + Toasty.info(context, hint).show(); + return; + } + listener.listener(binding.acInput.getText().toString()); + dismiss(); + }); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/dialog/TextViewDialog.java b/app/src/main/java/com/bbitcn/silk/ui/dialog/TextViewDialog.java new file mode 100644 index 0000000..797959a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/dialog/TextViewDialog.java @@ -0,0 +1,34 @@ +package com.bbitcn.silk.ui.dialog; + +import android.content.Context; +import android.text.InputType; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.DialogCustomBinding; + +/** + * @Description 文本输入弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月12日 15:54 + */ +public class TextViewDialog extends BaseDialog { + public TextViewDialog(@NonNull Context context, String title, String inputText, CustomString listener, boolean isNumber) { + super(context,true); + binding.tilInput.setVisibility(View.VISIBLE); + binding.tvMessage.setVisibility(View.GONE); + binding.btnNo.setVisibility(View.GONE); + binding.tvTitle.setText(title); + binding.tiInput.setText(inputText); + binding.tiInput.setInputType(isNumber?InputType.TYPE_CLASS_NUMBER:InputType.TYPE_CLASS_TEXT); + binding.btnYes.setOnClickListener(view -> { + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + listener.listener(binding.tiInput.getText().toString()); + dismiss(); + }); + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/dialog/TipsDialog.java b/app/src/main/java/com/bbitcn/silk/ui/dialog/TipsDialog.java new file mode 100644 index 0000000..ce5ddea --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/dialog/TipsDialog.java @@ -0,0 +1,26 @@ +package com.bbitcn.silk.ui.dialog; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.DialogCustomBinding; +import com.bbitcn.silk.databinding.DialogTipsBinding; + +/** + * @Description 获得确认通用弹窗 + * @Author DuanKaiji + * @CreateTime 2024年03月13日 13:57 + */ +public class TipsDialog extends BaseDialog { + + public TipsDialog(@NonNull Context context, String message) { + super(context); + binding.tvMessage.setText(message); + //通知显示的弹窗 + binding.ivDialogClose.setOnClickListener(view -> dismiss()); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/dialog/UpdateDialog.java b/app/src/main/java/com/bbitcn/silk/ui/dialog/UpdateDialog.java new file mode 100644 index 0000000..1a2ba18 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/dialog/UpdateDialog.java @@ -0,0 +1,147 @@ +package com.bbitcn.silk.ui.dialog; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.Settings; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.content.FileProvider; + +import com.bbitcn.silk.base.BaseDialog; +import com.bbitcn.silk.databinding.DialogCustomBinding; +import com.bbitcn.silk.utils.DialogUtil; +import com.bbitcn.silk.utils.log.MyLog; +import com.blankj.utilcode.util.AppUtils; +import com.blankj.utilcode.util.FileUtils; + +import org.xutils.common.Callback; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.io.File; + +import es.dmoral.toasty.Toasty; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月02日 16:59:30 + */ +public class UpdateDialog extends BaseDialog { + public UpdateDialog(@NonNull Context context, String apkUpdateUrl, String versionName, String versionDescription) { + this(context, apkUpdateUrl, versionName, versionDescription, false); + } + + public UpdateDialog(@NonNull Context context, String apkUpdateUrl, String versionName, String versionDescription, boolean isForce) { + super(context); + binding.btnYes.setText("更新"); + //询问确定执行的弹窗 + binding.btnYes.setOnClickListener(view -> { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + //点击确定将apk下载 + downFile(context, apkUpdateUrl, versionName); + } else { + Toasty.error(context, "SD卡不可用,请插入SD卡").show(); + } + }); + binding.btnNo.setVisibility(isForce ? View.GONE : View.VISIBLE); + binding.ivDialogClose.setVisibility(isForce ? View.GONE : View.VISIBLE); + binding.tvTitle.setText("检测到新版本"); + binding.tvMessage.setText("最新版本:" + versionName + "\n新版本内容:" + versionDescription); + } + + /** + * 下载APK + * + * @param path 下载地址 + */ + private void downFile(Context context, final String path, String newVersionName) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) { + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + context.startActivity(intent); + return; + } + String fileName = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + "智慧共育" + newVersionName + ".apk"; + FileUtils.delete(fileName); + RequestParams params = new RequestParams(path); + params.setSaveFilePath(fileName); + params.setAutoRename(true); + x.http().get(params, new Callback.ProgressCallback() { + @Override + public void onStarted() { + DialogUtil.showProgressIndicatorDialog(context, "开始下载新版本"); + } + + @Override + public void onLoading(long total, long current, boolean isDownloading) { + if (isDownloading) { + int progress = (int) ((current * 100) / total); + DialogUtil.setProgressIndicatorDialog("正在下载新版本,已下载" + progress + "%", progress); + } + } + + @Override + public void onSuccess(File result) { + MyLog.network("下载成功: " + result); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + installAPK(context, fileName); + } else { + AppUtils.installApp(result); + } + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + ex.printStackTrace(); + MyLog.networkError("下载失败,原因:" + ex.getMessage()); + new ConfirmDialog(context, "提示", "下载失败,原因:" + ex.getMessage(), null).show(); + + } + + @Override + public void onWaiting() { + } + + @Override + public void onCancelled(CancelledException cex) { + } + + @Override + public void onFinished() { + DialogUtil.hideProgressIndicatorDialog(); + + } + }); + } + + /** + * 安装程序 + * + * @param context 上下文 + * @param filePath 安装的文件路径 + */ + public static void installAPK(Context context, String filePath) { + try { + File apkFile = new File(filePath); + if (apkFile.exists()) { + + + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri apkUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(apkUri, "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } else { + Toasty.error(context, "安装包文件不存在").show(); + } + } catch (Exception e) { + e.printStackTrace(); + Toasty.error(context, "安装失败:" + e.getMessage()).show(); + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragment.java b/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragment.java new file mode 100644 index 0000000..a7daee5 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragment.java @@ -0,0 +1,134 @@ +package com.bbitcn.silk.ui.fragment; + + +import android.view.View; + +import androidx.recyclerview.widget.LinearLayoutManager; + +import com.bbitcn.silk.adapter.ItemInfoAdapter; +import com.bbitcn.silk.base.BaseFragment; +import com.bbitcn.silk.databinding.FragmentQueryBinding; +import com.bbitcn.silk.databinding.ItemInfoBinding; +import com.bbitcn.silk.ui.dialog.TipsDialog; +import com.bigkoo.pickerview.builder.TimePickerBuilder; +import com.blankj.utilcode.constant.TimeConstants; +import com.blankj.utilcode.util.KeyboardUtils; +import com.blankj.utilcode.util.TimeUtils; + +import java.util.Calendar; + +/** + * 只显示列表 + */ +public class QueryFragment extends BaseFragment { + + private int queryType; + private int clickType; + private int type; + /** + * 当前所在页数 + */ + private int page = 1; + ItemInfoAdapter adapter; + + /** + * @param queryType 0单显示 1显示日期 2显示关键字 3均显示 + * @param clickType 0无点击事件, 1去扣皮,2去转换 3弃售/确认售 + * @param paramType 类型 磅单查询:1:待定价/扣皮 2:待确认售 3:全部榜单 4:待定价 5:待扣皮 + */ + public QueryFragment(int queryType, int clickType, int paramType) { + this.queryType = queryType; + this.clickType = clickType; + this.type = paramType; + } + + @Override + public void init() { + switch (queryType) { + case 0 -> { + binding.llDate.setVisibility(View.GONE); + binding.llKey.setVisibility(View.GONE); + } + case 1 -> binding.llKey.setVisibility(View.GONE); + case 2 -> binding.llDate.setVisibility(View.GONE); + } + adapter = new ItemInfoAdapter(mContext, null, ItemInfoBinding.class, clickType); + binding.recycleView.setAdapter(adapter); + binding.recycleView.setLayoutManager(new LinearLayoutManager(mContext)); + presenter.getAddListLiveData().observe(this, queryList -> { + if (queryList != null) { + page++; + adapter.addDataList(queryList.getData()); + if (queryList.getData().size() == 0) { + binding.refreshLayout.finishLoadMoreWithNoMoreData(); + } else { + binding.refreshLayout.finishLoadMore(); + } + } + }); + presenter.getSetListLiveData().observe(this, queryList -> { + if (queryList != null) { + adapter.setDataList(queryList.getData()); + //回到顶部 + binding.recycleView.scrollToPosition(0); + } + binding.refreshLayout.finishRefresh(); + }); + //设置默认开始时间为当前时间的一年前 + binding.tvStartDate.setText(TimeUtils.date2String(TimeUtils.getDateByNow(-31, TimeConstants.DAY), "yyyy-MM-dd")); + binding.tvEndDate.setText(TimeUtils.date2String(TimeUtils.getNowDate(), "yyyy-MM-dd")); + presenter.query(type, binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString(), binding.etKey.getText().toString(), 1, 5); + } + + @Override + public void initListener() { + super.initListener(); + //选择开始日期 + binding.tvStartDate.setOnClickListener(getDateListener(true)); + binding.tvEndDate.setOnClickListener(getDateListener(false)); + binding.btnQuery.setOnClickListener(v -> + presenter.query(type, binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString(), binding.etKey.getText().toString(), 1, 5)); + binding.refreshLayout.setOnRefreshListener(refreshlayout -> { + page = 1; + presenter.query(type, binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString(), binding.etKey.getText().toString(), 1, 5); + }); + binding.refreshLayout.setOnLoadMoreListener(refreshlayout -> { + presenter.query(type, binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString(), binding.etKey.getText().toString(), ++page, 5); + }); + } + + private View.OnClickListener getDateListener(boolean isStart) { + return v -> { + //隐藏系统软键盘 + KeyboardUtils.hideSoftInput(binding.etKey); + Calendar defaultDate = Calendar.getInstance(); + if (isStart) { + defaultDate.setTime(TimeUtils.string2Date(binding.tvStartDate.getText().toString(), "yyyy-MM-dd")); + } else { + defaultDate.setTime(TimeUtils.string2Date(binding.tvEndDate.getText().toString(), "yyyy-MM-dd")); + } + new TimePickerBuilder(mContext, (date, v1) -> { + if (isStart) { + binding.tvStartDate.setText(TimeUtils.date2String(date, "yyyy-MM-dd")); + } else { + binding.tvEndDate.setText(TimeUtils.date2String(date, "yyyy-MM-dd")); + } + //判断时间区间是否合理 + if (TimeUtils.string2Date(binding.tvStartDate.getText().toString(), "yyyy-MM-dd").getTime() + > TimeUtils.string2Date(binding.tvEndDate.getText().toString(), "yyyy-MM-dd").getTime()) { + new TipsDialog(mContext, "开始时间不能大于结束时间").show(); + return; + } + presenter.query(type, binding.tvStartDate.getText().toString(), binding.tvEndDate.getText().toString(), binding.etKey.getText().toString(), 1, 5); + }) + .setDate(defaultDate) + .setContentTextSize(35) + .setTitleSize(35) + .isCyclic(false) + .setSubCalSize(20) + .setType(new boolean[]{true, true, true, false, false, false}) + .build() + .show(); + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragmentPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragmentPresenter.java new file mode 100644 index 0000000..baf23a6 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/fragment/QueryFragmentPresenter.java @@ -0,0 +1,71 @@ +package com.bbitcn.silk.ui.fragment; + + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.MutableLiveData; + +import com.bbitcn.silk.base.BasePresenter; +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +public class QueryFragmentPresenter extends BasePresenter { + private MutableLiveData setListLiveData = new MutableLiveData<>(); + private MutableLiveData addListLiveData = new MutableLiveData<>(); + + public QueryFragmentPresenter(@NonNull Application application) { + super(application); + } + + public MutableLiveData getAddListLiveData() { + return addListLiveData; + } + + public MutableLiveData getSetListLiveData() { + return setListLiveData; + } + + public void query(int type, String startDate, String endDate, String key, int page, int limit) { + showLoadingDialog("正在查询中"); + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.queryList, true, true, page, limit); + params.addParameter("type", type); + params.addParameter("dtstart", startDate); + params.addParameter("dtend", endDate); + params.addParameter("like", key); + params.addParameter("depsysid", Global.getDepSysId()); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(QueryList model) { + if (model.getResult()) { + MyLog.app("查询成功"); + if (page == 1) { + setListLiveData.setValue(model); + } else { + addListLiveData.setValue(model); + } + } else { + MyLog.appError("查询失败"); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("查询失败"); + } + + @Override + public void myFinish() { + super.myFinish(); + hideLoadingDialog(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragment.java b/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragment.java new file mode 100644 index 0000000..6a16e45 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragment.java @@ -0,0 +1,125 @@ +package com.bbitcn.silk.ui.fragment; + +import android.text.Editable; +import android.text.TextWatcher; + +import com.bbitcn.silk.base.BaseFragment; +import com.bbitcn.silk.databinding.FragmentTareInfoBinding; +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.model.net.ToTareSubmit; +import com.bbitcn.silk.utils.database.TareDataBase; +import com.bbitcn.silk.utils.global.Global; +import com.blankj.utilcode.util.StringUtils; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月01日 10:54:37 + */ +public class TareInfoFragment extends BaseFragment { + QueryList.DataDTO.ItemsDTO data; + String CzSysid; + + public TareInfoFragment(String CzSysid, QueryList.DataDTO.ItemsDTO data) { + this.CzSysid = CzSysid; + this.data = data; + } + + @Override + public void init() { + ToTareSubmit.ItemsDTO catchData = TareDataBase.getCatchById(CzSysid + data.getSgTypeSysid()); + if (catchData != null) { + //如果有缓存数据则显示缓存数据 + binding.tiSkin.setText(String.valueOf(catchData.getPizhong())); + binding.tiTare.setText(String.valueOf(catchData.getKouzhong())); + binding.tiPrice.setText(String.valueOf(catchData.getPrice())); + } else { + binding.tiSkin.setText(String.valueOf(data.getPizhong())); + binding.tiTare.setText(String.valueOf(data.getKouzhong())); + binding.tiPrice.setText(String.valueOf(data.getPrice())); + } + binding.tvPackage.setText(String.valueOf(data.getBaoshu())); + if (data.getAllowEditPrice() == 0) { + // 0表示不允许修改价格 + binding.tiPrice.setEnabled(false); + } + refreshUnit(); + } + + @Override + public void initListener() { + super.initListener(); + //输入皮重和扣重后即时计算净重 + TextWatcher tw = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + //do nothing + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + calculateTotal(); + } + + @Override + public void afterTextChanged(Editable s) { + ToTareSubmit.ItemsDTO dto = new ToTareSubmit.ItemsDTO(); + dto.setSgtypesysid(CzSysid + data.getSgTypeSysid()); + dto.setPizhong(binding.tiSkin.getText().toString().isEmpty() ? 0 : Double.parseDouble(binding.tiSkin.getText().toString()) / Global.getUnitInt()); + dto.setKouzhong(binding.tiTare.getText().toString().isEmpty() ? 0 : Double.parseDouble(binding.tiTare.getText().toString()) / Global.getUnitInt()); + dto.setPrice(binding.tiPrice.getText().toString().isEmpty() ? 0 : Double.parseDouble(binding.tiPrice.getText().toString())); + TareDataBase.updateCatch(dto); + } + }; + binding.tiSkin.addTextChangedListener(tw); + binding.tiPrice.addTextChangedListener(tw); + binding.tiTare.addTextChangedListener(tw); + binding.tiSkin.setSelectAllOnFocus(true); + binding.tiPrice.setSelectAllOnFocus(true); + binding.tiTare.setSelectAllOnFocus(true); + } + + /** + * 计算总价 + */ + private void calculateTotal() { + double skin = StringUtils.isEmpty(binding.tiSkin.getText().toString()) ? 0 : Double.parseDouble(binding.tiSkin.getText().toString()); + double tare = StringUtils.isEmpty(binding.tiTare.getText().toString()) ? 0 : Double.parseDouble(binding.tiTare.getText().toString()); + double gross = binding.tvGross.getText().toString().isEmpty() ? 0 : Double.parseDouble(binding.tvGross.getText().toString()); + double net = gross - skin - tare; + double total = net * Double.parseDouble(StringUtils.isEmpty(binding.tiPrice.getText().toString()) ? "0" : binding.tiPrice.getText().toString()); + if (gross < 0 || skin < 0 || total < 0) { + binding.tvNet.setText("数据错误"); + binding.tvTotal.setText("数据错误"); + } else { + //将输出格式化为两位小数 + binding.tvNet.setText(String.format("%.2f", net)); + binding.tvTotal.setText(String.format("%.2f", total)); + } + } + + public String getSkin() { + return binding.tiSkin.getText().toString(); + } + + public String getTare() { + return binding.tiTare.getText().toString(); + } + + public String getPrice() { + return binding.tiPrice.getText().toString(); + } + + public String getNet() { + return binding.tvNet.getText().toString(); + } + + public void refreshUnit() { + binding.tvUnit.setText("(元/" + Global.getUnit() + ")"); + //刷新单位后重新显示净重与毛重. + binding.tvGross.setText(String.valueOf(data.getMaozhong() * Global.getUnitInt())); + binding.tvNet.setText(String.valueOf(data.getMaozhong() * Global.getUnitInt())); + calculateTotal(); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragmentPresenter.java b/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragmentPresenter.java new file mode 100644 index 0000000..d077f7d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/fragment/TareInfoFragmentPresenter.java @@ -0,0 +1,15 @@ +package com.bbitcn.silk.ui.fragment; + + +import android.app.Application; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.base.BasePresenter; + +public class TareInfoFragmentPresenter extends BasePresenter { + + public TareInfoFragmentPresenter(@NonNull Application application) { + super(application); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/ui/view/CustomSpinnerAdapter.java b/app/src/main/java/com/bbitcn/silk/ui/view/CustomSpinnerAdapter.java new file mode 100644 index 0000000..6b505e1 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/ui/view/CustomSpinnerAdapter.java @@ -0,0 +1,40 @@ +package com.bbitcn.silk.ui.view; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.bbitcn.silk.R; + +/** + * 自定义下拉框适配器 + */ +public class CustomSpinnerAdapter extends ArrayAdapter { + + public CustomSpinnerAdapter(Context context, String[] items) { + super(context, R.layout.item_list, items); + } + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.item_list, parent, false); + } + + // 获取布局中的TextView和ImageView + TextView textView = view.findViewById(R.id.tv); + + // 设置TextView和ImageView的内容 + textView.setText(getItem(position)); + + return view; + } +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/DialogUtil.java b/app/src/main/java/com/bbitcn/silk/utils/DialogUtil.java new file mode 100644 index 0000000..31194c5 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/DialogUtil.java @@ -0,0 +1,137 @@ +package com.bbitcn.silk.utils; + +import android.app.Dialog; +import android.content.Context; +import android.os.Build; +import android.view.Gravity; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import com.bbitcn.silk.R; +import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +public class DialogUtil { + private static Dialog progressDialog; + private static TextView textViewInfo; + + /** + * 显示加载中弹窗,支持中途改变提示文字 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showLoadingDialog(Context context, String info) { + if (progressDialog != null) { + progressDialog.dismiss(); // 如果之前有Dialog显示,则先关闭 + progressDialog = null; // 释放旧Dialog + } + progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressDialog.setContentView(R.layout.dialog_loading); + progressDialog.setCancelable(false); + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + textViewInfo.setText(info); + WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(); + layoutParams.copyFrom(progressDialog.getWindow().getAttributes()); + layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + layoutParams.gravity = Gravity.CENTER; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + layoutParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + layoutParams.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; + progressDialog.getWindow().setAttributes(layoutParams); + progressDialog.show(); + } + + /** + * 显示加载中弹窗 + * 在IO线程中使用 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showLoadingDialogInIO(Context context, String info) { + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + showLoadingDialogInIO(context, info); + } + }); + } + + /** + * 关闭加载中弹窗 + * 假设都在UI线程调用 + */ + public static void hideLoadingDialog() { + if (progressDialog != null) { + progressDialog.dismiss(); + } + } + + /** + * 显示进度条弹窗 + * + * @param context 上下文 + * @param info 提示信息 + */ + public static void showProgressIndicatorDialog(Context context, String info) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressDialog = new Dialog(context, R.style.MyDialogTheme); + progressDialog.setContentView(R.layout.dialog_process_indicator); + progressDialog.setCancelable(false); + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + progressIndicator = progressDialog.findViewById(R.id.progress_indicator); + // 设置对话框位置 + Window window = progressDialog.getWindow(); + if (window != null) { + // 设置对话框位置为屏幕顶部 + window.setGravity(Gravity.CENTER); + WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT; + layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(layoutParams); + } + textViewInfo.setText(info); + progressDialog.show(); + } else { + showLoadingDialog(context, info); + } + } + + private static LinearProgressIndicator progressIndicator; + + + /** + * 修改进度条弹窗 + * + * @param info 提示信息 + * @param progress 进度 + */ + public static void setProgressIndicatorDialog(String info, int progress) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + textViewInfo = progressDialog.findViewById(R.id.tv_loading); + progressIndicator = progressDialog.findViewById(R.id.progress_indicator); + textViewInfo.setText(info); + // 设置进度 + progressIndicator.setProgress(progress, true); + } + } + + /** + * 关闭进度条弹窗 + */ + public static void hideProgressIndicatorDialog() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (progressDialog != null) { + progressDialog.dismiss(); + } + } else { + hideLoadingDialog(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/utils/MMKVUtil.java b/app/src/main/java/com/bbitcn/silk/utils/MMKVUtil.java new file mode 100644 index 0000000..c953615 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/MMKVUtil.java @@ -0,0 +1,132 @@ +package com.bbitcn.silk.utils; + + +import android.os.Parcelable; + +import com.bbitcn.silk.MyApp; +import com.blankj.utilcode.util.GsonUtils; +import com.tencent.mmkv.MMKV; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * MMKV工具类 + */ +public class MMKVUtil { + private static MMKVUtil INSTANCE; + private static MMKV mmkvInstance; + + private MMKVUtil() { + } + + /** + * 初始化MMKV,只能在Application中初始化一次 + */ + public static void init() { + INSTANCE = new MMKVUtil(); + mmkvInstance = MMKV.defaultMMKV(); + } + + /** + * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法 + */ + public static void put(String key, T object) { + if (object instanceof String) { + mmkvInstance.encode(key, (String) object); + } else if (object instanceof Integer) { + mmkvInstance.encode(key, (Integer) object); + } else if (object instanceof Boolean) { + mmkvInstance.encode(key, (Boolean) object); + } else if (object instanceof Float) { + mmkvInstance.encode(key, (Float) object); + } else if (object instanceof Long) { + mmkvInstance.encode(key, (Long) object); + } else if (object instanceof Double) { + mmkvInstance.encode(key, (Double) object); + } else if (object instanceof Set) { + mmkvInstance.encode(key, (Set) object); + } else if (object instanceof Parcelable) { + mmkvInstance.encode(key, (Parcelable) object); + } else { + mmkvInstance.encode(key, object == null ? "" : object.toString()); + } + } + + /** + * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值 + */ + public static T get(String key, T defaultObject) { + if (defaultObject instanceof String || defaultObject == null) { + return (T) mmkvInstance.decodeString(key, (String) defaultObject); + } else if (defaultObject instanceof Integer) { + return (T) Integer.valueOf(mmkvInstance.decodeInt(key, (Integer) defaultObject)); + } else if (defaultObject instanceof Boolean) { + return (T) Boolean.valueOf(mmkvInstance.decodeBool(key, (Boolean) defaultObject)); + } else if (defaultObject instanceof Float) { + return (T) Float.valueOf(mmkvInstance.decodeFloat(key, (Float) defaultObject)); + } else if (defaultObject instanceof Long) { + return (T) Long.valueOf(mmkvInstance.decodeLong(key, (Long) defaultObject)); + } else if (defaultObject instanceof Double) { + return (T) Double.valueOf(mmkvInstance.decodeDouble(key, (Double) defaultObject)); + } else if (defaultObject instanceof Parcelable) { + Parcelable p = (Parcelable) defaultObject; + return (T) mmkvInstance.decodeParcelable(key, p.getClass()); + } else { + return null; + } + } + + /** + * 获得字符串 + */ + public static String get(String key) { + return mmkvInstance.decodeString(key, ""); + } + + /** + * 移除某个key值已经对应的值 + */ + public void remove(String key) { + mmkvInstance.remove(key); + } + + /** + * 查询某个key是否已经存在 + */ + public boolean contains(String key) { + return mmkvInstance.contains(key); + } + + /** + * 清除所有数据 + */ + public static void clear() { + mmkvInstance.clear(); + } + + /** + * 获取所有key-value的json字符串 + */ + public static String getAllKeyValues() { + Map result = new HashMap<>(); + String[] keys = mmkvInstance.allKeys(); + if (keys == null || keys.length == 0) { + return ""; + } + for (String key : keys) { + if (key.contains("HISTORY") || key.contains("user") || key.contains("password")) { + continue; + } + //将所有的key-value转换成json字符串 + result.put(key, mmkvInstance.decodeString(key, "")); + //解决boolean、int等基本类型无法转换成json字符串的问题 + if (result.get(key) == null) { + result.put(key, mmkvInstance.decodeInt(key, 0)); + } + } + return GsonUtils.toJson(result); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/utils/MyUtil.java b/app/src/main/java/com/bbitcn/silk/utils/MyUtil.java new file mode 100644 index 0000000..8c5d063 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/MyUtil.java @@ -0,0 +1,188 @@ +package com.bbitcn.silk.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.view.View; +import android.view.animation.LinearInterpolator; + +import com.bbitcn.silk.model.net.Login; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; +import com.bbitcn.silk.utils.network.Url; +import com.bbitcn.silk.utils.network.XHttpManager; +import com.bbitcn.silk.utils.network.XHttpShorthand; +import com.blankj.utilcode.util.AppUtils; +import com.blankj.utilcode.util.EncryptUtils; +import com.blankj.utilcode.util.TimeUtils; +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxUITask; + +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import me.samlss.broccoli.Broccoli; +import me.samlss.broccoli.BroccoliGradientDrawable; +import me.samlss.broccoli.PlaceholderParameter; + +public class MyUtil { + + public static Broccoli getMyBroccoli(View... v) { + Broccoli broccoli = new Broccoli(); + for (int i = 0; i < v.length; i++) { + broccoli.addPlaceholders(new PlaceholderParameter.Builder() + .setView(v[i]) + .setDrawable(new BroccoliGradientDrawable(Color.parseColor("#DDDDDD"), + Color.parseColor("#CCCCCC"), 0, 1000, new LinearInterpolator())) + .build()); + } + return broccoli; + } + + + /*** + * 获得格式化后的时间 + */ + public static int[] getHourAndMinuteUsingLocalTime(Date date) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + int hour = calendar.get(Calendar.HOUR_OF_DAY); + int minute = calendar.get(Calendar.MINUTE); + + return new int[]{hour, minute}; + } + + + /** + * 获取当前版本号 + */ + public static int getVersion(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionCode; + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + /** + * 静默登录 + */ + public static void login() { + login(null); + } + + public static void login(Runnable runnable) { + RequestParams params = XHttpManager.getInstance().getRequestParams(Url.login, false); + params.addParameter("Platform", "1"); + params.addParameter("TenantCode", Global.getCompanyId()); + params.addParameter("HardwareId", Global.getDeviceId()); + params.addParameter("Account", Global.getUserAccount()); + params.addParameter("Password", EncryptUtils.encryptMD5ToString(Global.getPassword())); + x.http().post(params, new XHttpShorthand() { + @Override + public void success(Login model) { + MyLog.app("登录成功"); + if (model.getResult()) { + MMKVUtil.put(Global.TOKEN, model.getData().getTokenType() + " " + model.getData().getAccessToken()); + MMKVUtil.put(Global.TOKEN_TIME, TimeUtils.string2Millis(model.getData().getExpireTime())); + } + if (runnable != null) { + runnable.run(); + } + } + + @Override + public void error(Throwable e) { + MyLog.appError("Token获取失败"); + } + + }); + } + + /** + * 获取当前版本号 + */ + public static String getVersionId(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return String.valueOf(packageInfo.versionCode); + } catch (Exception e) { + e.printStackTrace(); + return "Err"; + } + } + + /** + * 获取当前版本名称 + */ + public static String getVersionName(Context context) { + try { + PackageManager packageManager = context.getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionName; + } catch (Exception e) { + e.printStackTrace(); + return "Err"; + } + } + + /** + * 获取当前时间 + */ + public static String getDateTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); + } + + /** + * 获取当前日期 + */ + public static String getDateDay() { + return new SimpleDateFormat("yyyy-MM-dd").format(new Date()); + } + + /** + * 转换date格式为标准时间String + */ + public static String getFormatDateTime(Date date) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); + } + + /** + * 关闭APP + */ + public static void exit(Context mContext, String tip) { + DialogUtil.showLoadingDialogInIO(mContext, tip); + RxJavaUtils.delay(1, aLong -> { + DialogUtil.hideLoadingDialog(); + AppUtils.exitApp(); + }); + } + + /** + * 重启APP + */ + public static void relaunchApp(Context mContext, String tip) { + RxJavaUtils.doInUIThread(new RxUITask(null) { + @Override + public void doInUIThread(Object o) { + if (mContext != null) { + DialogUtil.showLoadingDialogInIO(mContext, tip); + } + RxJavaUtils.delay(2, aLong -> { + AppUtils.relaunchApp(true); + DialogUtil.hideLoadingDialog(); + }); + } + }); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/database/AllTypeDataBase.java b/app/src/main/java/com/bbitcn/silk/utils/database/AllTypeDataBase.java new file mode 100644 index 0000000..cb16d8f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/database/AllTypeDataBase.java @@ -0,0 +1,47 @@ +package com.bbitcn.silk.utils.database; + +import com.bbitcn.silk.model.net.AllType; +import com.bbitcn.silk.utils.MMKVUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:20:10 + */ +public class AllTypeDataBase { + + public static final String ALL_TYPE = "ALL_TYPE"; + + public static void setAllType(List temp) { + MMKVUtil.put(ALL_TYPE, new Gson().toJson(temp)); + } + + public static String getSysIdByName(String name) { + String json = MMKVUtil.get(ALL_TYPE, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for(AllType.DataDTO dataDTO : temp) { + if(dataDTO.getName().equals(name)) { + return dataDTO.getSysid(); + } + } + return null; + } + + public static String[] getAllTypeName() { + String json = MMKVUtil.get(ALL_TYPE, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + List result = new ArrayList<>(); + for(AllType.DataDTO dataDTO : temp) { + result.add(dataDTO.getName()); + } + return result.toArray(new String[0]); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/database/PriceDataBase.java b/app/src/main/java/com/bbitcn/silk/utils/database/PriceDataBase.java new file mode 100644 index 0000000..284234b --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/database/PriceDataBase.java @@ -0,0 +1,50 @@ +package com.bbitcn.silk.utils.database; + +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.model.net.ToTareSubmit; +import com.bbitcn.silk.model.net.TodayPrice; +import com.bbitcn.silk.utils.MMKVUtil; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.Iterator; +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:20:10 + */ +public class PriceDataBase { + + public static final String PRICE_CATCH = "PRICE_CATCH"; + + + /** + * 获取缓存 + * + * @return 最小值,最大值 + */ + public static double[] getCatchById(String name) { + String json = MMKVUtil.get(PRICE_CATCH, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for (TodayPrice.DataDTO.ItemsDTO t : temp) { + if (t.getName().equals(name)) { + return new double[]{Double.parseDouble(t.getMinprice()), Double.parseDouble(t.getMaxprice())}; + } + } + return new double[]{0,0}; + } + + /** + * 更新缓存 + * 注意:sgtypesysid + * + * @param data + */ + public static void updateCatch(List data) { + MMKVUtil.put(PRICE_CATCH, new Gson().toJson(data)); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/database/TareDataBase.java b/app/src/main/java/com/bbitcn/silk/utils/database/TareDataBase.java new file mode 100644 index 0000000..31a8a5a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/database/TareDataBase.java @@ -0,0 +1,89 @@ +package com.bbitcn.silk.utils.database; + +import com.bbitcn.silk.model.net.QueryList; +import com.bbitcn.silk.model.net.ToTareSubmit; +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.log.MyLog; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.util.Iterator; +import java.util.List; + +/** + * @Description TODO + * @Author DuanKaiji + * @CreateTime 2024年04月03日 15:20:10 + */ +public class TareDataBase { + + public static final String TARE_CATCH = "TARE_CATCH"; + + + /** + * 获取缓存 + * + * @return + */ + public static ToTareSubmit.ItemsDTO getCatchById(String id) { + String json = MMKVUtil.get(TARE_CATCH, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + for (ToTareSubmit.ItemsDTO itemsDTO : temp) { + if (itemsDTO.getSgtypesysid().equals(id)) { + return itemsDTO; + } + } + return null; + } + + /** + * 更新缓存 + * 注意:sgtypesysid + * + * @param data + */ + public static void updateCatch(ToTareSubmit.ItemsDTO data) { + String json = MMKVUtil.get(TARE_CATCH, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + //判断是否有重复 则删除重复数据 + for (int i = 0; i < temp.size(); i++) { + if (temp.get(i).equals(data)) { + temp.remove(i); + break; + } + } + temp.add(data); + MMKVUtil.put(TARE_CATCH, new Gson().toJson(temp)); + } + + /** + * 清除缓存 + */ + public static void removeCatch() { + MMKVUtil.put(TARE_CATCH, "[]"); + } + + /** + * 清除指定缓存 + */ + public static void removeCatchById(QueryList.DataDTO dataDTO) { + String json = MMKVUtil.get(TARE_CATCH, "[]"); + List temp = new Gson().fromJson(json, new TypeToken>() { + }.getType()); + // 涉及列表删除 使用迭代器进行删除 + Iterator iterator = temp.iterator(); + while (iterator.hasNext()) { + ToTareSubmit.ItemsDTO item = iterator.next(); + for (QueryList.DataDTO.ItemsDTO itemsDTO : dataDTO.getItems()) { + if (item.getSgtypesysid().equals(dataDTO.getCzSysid() + itemsDTO.getSgTypeSysid())) { + iterator.remove(); + break; + } + } + } + MMKVUtil.put(TARE_CATCH, new Gson().toJson(temp)); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/global/Global.java b/app/src/main/java/com/bbitcn/silk/utils/global/Global.java new file mode 100644 index 0000000..90414f1 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/global/Global.java @@ -0,0 +1,125 @@ +package com.bbitcn.silk.utils.global; + +import com.bbitcn.silk.utils.MMKVUtil; + +/** + * @Description MMKV + * @Author DuanKaiji + * @CreateTime 2024年03月27日 13:48 + */ +public class Global { + /** + * Token + */ + public static final String TOKEN = "TOKEN"; + /** + * 公司ID + */ + public static final String COMPANY_ID = "COMPANY_ID"; + public static final String REG_NAME = "REG_NAME"; + public static final String REG_TEL = "REG_TEL"; + public static final String TOKEN_TIME = "TOKEN_TIME"; + /** + * 所属部门ID + */ + public static final String DEP_SYS_ID = "DEP_SYS_ID"; + + public static String getDepSysId() { + return MMKVUtil.get(DEP_SYS_ID, "——"); + } + + public static boolean isTokenAvailable() { + //10分钟后都没过期 + return System.currentTimeMillis() < MMKVUtil.get(TOKEN_TIME, 0L) - 10 * 60 * 1000; + } + + public static String getRegName() { + return MMKVUtil.get(REG_NAME, "——"); + } + + public static String getRegTel() { + return MMKVUtil.get(REG_TEL, "——"); + } + + /** + * 用户ID + */ + public static final String USER_ID = "USER_ID"; + /** + * 用户名 + */ + public static final String USER_NAME = "USER_NAME"; + /** + * 用户账号 + */ + public static final String USER_ACCOUNT = "USER_ACCOUNT"; + /** + * 密码 + */ + public static final String PASSWORD = "PASSWORD"; + /** + * 设备码 + */ + public static final String DEVICE_ID = "DEVICE_ID"; + + /** + * 默认单位 + */ + public static final String UNIT = "UNIT"; + /** + * 公司名称 + */ + public static final String COMPANY_NAME = "COMPANY_NAME"; + /** + * 茧站名称 + */ + public static final String STATION_NAME = "STATION_NAME"; + + public static String getStationName() { + return MMKVUtil.get(STATION_NAME, "——"); + } + + public static String getDeviceId() { + return MMKVUtil.get(DEVICE_ID, "——"); + } + + public static String getUnit() { + return MMKVUtil.get(UNIT, SpinnerList.UNIT[0]); + } + + public static String getCompanyId() { + return MMKVUtil.get(COMPANY_ID, "未授权").toUpperCase(); + } + + public static String getUserName() { + return MMKVUtil.get(USER_NAME, "——"); + } + + public static String getUserAccount() { + return MMKVUtil.get(USER_ACCOUNT, null); + } + + public static String getPassword() { + return MMKVUtil.get(PASSWORD, null); + } + + public static String getUserId() { + return MMKVUtil.get(USER_ID, "——"); + } + + public static String getCompanyName() { + return MMKVUtil.get(COMPANY_NAME, "——"); + } + + /** + * 单位 + * 用于换算 + */ + public static int getUnitInt() { + if (SpinnerList.UNIT[0].equals(getUnit())) { + return 1; + } else { + return 2; + } + } +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/global/RxTag.java b/app/src/main/java/com/bbitcn/silk/utils/global/RxTag.java new file mode 100644 index 0000000..8c870d4 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/global/RxTag.java @@ -0,0 +1,12 @@ +package com.bbitcn.silk.utils.global; + +public class RxTag { + //RxBus——————————————————————————————————————————————————————————————————— + public static final String UPDATE_AUTO = "UPDATE_AUTO"; + + //DisposablePool—————————————————————————————————————————————————————————— + /** + * 循环获取Token + */ + public static final String GET_TOKEN = "GET_TOKEN"; +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/global/SpinnerList.java b/app/src/main/java/com/bbitcn/silk/utils/global/SpinnerList.java new file mode 100644 index 0000000..547fc4d --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/global/SpinnerList.java @@ -0,0 +1,12 @@ +package com.bbitcn.silk.utils.global; + +/** + * 通用数组 + */ +public class SpinnerList { + /** + * + */ + public static String[] UNIT = {"公斤", "市斤"}; + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/global/Variable.java b/app/src/main/java/com/bbitcn/silk/utils/global/Variable.java new file mode 100644 index 0000000..b0b6f04 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/global/Variable.java @@ -0,0 +1,15 @@ +package com.bbitcn.silk.utils.global; + +/** + * 系统默认值,需要手动设置 + * + * @Author DuanKaiji + */ +public class Variable { + + /** + * 防抖拦截时间,即此时间内的点击事件不会被重复执行 + * 单位:毫秒 + */ + public static final long CLICK_INTERVAL = 600; +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/log/CrashHandlerUtil.java b/app/src/main/java/com/bbitcn/silk/utils/log/CrashHandlerUtil.java new file mode 100644 index 0000000..e83e82c --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/log/CrashHandlerUtil.java @@ -0,0 +1,61 @@ +package com.bbitcn.silk.utils.log; + + +import android.content.Context; +import android.content.Intent; + +public class CrashHandlerUtil implements Thread.UncaughtExceptionHandler { + /** + * 饿汉式单例模式 + */ + private static CrashHandlerUtil crashHandlerUtil = new CrashHandlerUtil(); + private Thread.UncaughtExceptionHandler mDefaultCaughtExceptionHandler; + private Context mContext; + + private CrashHandlerUtil() { + // 私有构造函数,确保单例模式 + } + + public static CrashHandlerUtil getInstance() { + return crashHandlerUtil; + } + + public void init(Context context) { + mContext = context; + mDefaultCaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + ex.printStackTrace(); + MyLog.appError("软件已崩溃,重启应用:" + getFormattedException(ex)); + Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(mContext.getPackageName()); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivity(intent); + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(0); + } + + /** + * 格式化异常信息的方法 + */ + public String getFormattedException(Throwable throwable) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("异常详情:\n") + .append("异常类型:").append(throwable.getClass().getName()).append("\n") + .append("异常消息:").append(throwable.getMessage()).append("\n"); + StackTraceElement[] stackTrace = throwable.getStackTrace(); + if (stackTrace != null && stackTrace.length > 0) { + stringBuilder.append("异常位置:").append(stackTrace[0].toString()).append("\n"); + } + + stringBuilder.append("完整堆栈跟踪:\n"); + for (StackTraceElement element : stackTrace) { + stringBuilder.append("\t").append(element.toString()).append("\n"); + } + + return stringBuilder.toString(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/bbitcn/silk/utils/log/MyLog.java b/app/src/main/java/com/bbitcn/silk/utils/log/MyLog.java new file mode 100644 index 0000000..4288433 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/log/MyLog.java @@ -0,0 +1,140 @@ +package com.bbitcn.silk.utils.log; + + +import android.util.Log; + +import com.xuexiang.rxutil2.rxjava.RxJavaUtils; +import com.xuexiang.rxutil2.rxjava.task.RxIOTask; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import timber.log.Timber; + +public class MyLog extends Timber.Tree { + public static final String TAG_ERROR = "Error"; + /** + * 测试类 + */ + public static final String TAG_TEST = "调试"; + /** + * 预警类 + */ + public static final String TAG_WARNING = "预警"; + + /** + * 传感器类 + */ + public static final String TAG_SENSOR = "传感器"; + + /** + * 控制器类 + */ + public static final String TAG_CONTROLLER = "控制器"; + + /** + * 网络类 + */ + public static final String TAG_NETWORK = "网络"; + + /** + * 系统类 + */ + public static final String TAG_APP = "系统"; + + /** + * 远程控制类 + */ + public static final String TAG_REMOTE = "远程控制"; + + /** + * 自动控制类 + */ + public static final String TAG_AUTO = "自动控制"; + /** + * 人脸识别类 + */ + public static final String TAG_FACE = "人脸识别"; + + public static List getUserQueryTag() { + List tags = new ArrayList<>(); + Collections.addAll(tags, TAG_APP, TAG_REMOTE, TAG_CONTROLLER, TAG_WARNING,TAG_SENSOR, TAG_NETWORK, TAG_AUTO); + return tags; + } + + public static void test(String msg) { + Timber.tag(TAG_TEST).i(msg); + } + + public static void auto(String msg) { + Timber.tag(TAG_AUTO).i(msg); + } + + public static void autoError(String msg) { + Timber.tag(TAG_AUTO + TAG_ERROR).e(msg); + } + + public static void warning(String msg) { + Timber.tag(TAG_WARNING).i(msg); + } + + public static void warningError(String msg) { + Timber.tag(TAG_WARNING + TAG_ERROR).e(msg); + } + + public static void sensor(String msg) { + Timber.tag(TAG_SENSOR).i(msg); + } + + public static void sensorError(String msg) { + Timber.tag(TAG_SENSOR + TAG_ERROR).e(msg); + } + + public static void controller(String msg) { + Timber.tag(TAG_CONTROLLER).i(msg); + } + + public static void controllerError(String msg) { + Timber.tag(TAG_CONTROLLER + TAG_ERROR).e(msg); + } + + public static void network(String msg) { + Timber.tag(TAG_NETWORK).i(msg); + } + + public static void networkError(String msg) { + Timber.tag(TAG_NETWORK + TAG_ERROR).e(msg); + } + + public static void app(String msg) { + Timber.tag(TAG_APP).i(msg); + } + + public static void appError(String msg) { + Timber.tag(TAG_APP + TAG_ERROR).e(msg); + } + + public static void remote(String msg) { + Timber.tag(TAG_REMOTE).i(msg); + } + + public static void remoteError(String msg) { + Timber.tag(TAG_REMOTE + TAG_ERROR).e(msg); + } + + public static void face(String msg) { + Timber.tag(TAG_FACE).i(msg); + } + + public static void faceError(String msg) { + Timber.tag(TAG_FACE + TAG_ERROR).e(msg); + } + + @Override + protected void log(int priority, String tag, String message, Throwable t) { + //输出日志到控制台 + new Thread(() -> Log.println(priority, tag, message)).start(); + } + +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/network/Url.java b/app/src/main/java/com/bbitcn/silk/utils/network/Url.java new file mode 100644 index 0000000..e67d9a4 --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/network/Url.java @@ -0,0 +1,97 @@ +package com.bbitcn.silk.utils.network; + +public class Url { + /** + * Environment 当前环境 true 生产环境 false 测试环境 + */ + private static final boolean userProduct = true; +// private static final boolean userProduct = false; + private static String baseUrls; + + public static void initUrl() { + if (userProduct) { + // 生产环境 + baseUrls = "https://f8.api.dev.bbitcn.cn/"; + } else { + // 测试环境 + baseUrls = "http://f8.api.dev.bbitcn.cn:9999/"; + } + login = baseUrls + "api/CBMSAccount/LoginJwtAndValidHardware"; + getNewVersion = baseUrls + "api/app/App.ZHCS.GetNewVersion"; + auth = baseUrls + "api/client/Authorize.AddInfo"; + isAuth = baseUrls + "api/client/Authorize.IsAuthorize"; + getUserInfo = baseUrls + "api/CBMSAccount/GetLoginInfoByJwt"; + getPriceOfToday = baseUrls + "api/app/App.ZHCS.Common.GetTodayPrice"; + queryList = baseUrls + "api/app/App.ZHCS.Bangdan.GetViewList"; + queryStatistics = baseUrls + "api/app/App.ZHCS.Bangdan.GetStatistic"; + getAllType = baseUrls + "api/app/App.ZHCS.GetAllSgType"; + toConvert = baseUrls + "api/app/App.ZHCS.Bangdan.ChangeSgType"; + tareSubmit = baseUrls +"api/app/App.ZHCS.Bangdan.KoupiDingjia"; + weather = baseUrls +"api/Weather/GetWeatherInfoForecasts"; + getAboutInfo = baseUrls + "api/app/App.ZHCS.Common.GetAboutInfo"; + checkAuth = baseUrls + "api/client/Authorize.IsAuthorize"; + configOrCancel = baseUrls + "api/ShouGou/ChengZhong.UpdateBillState"; + } + + /** + * 登录 + * 获取Token + */ + public static String login; + /** + * 获取最新版本 + */ + public static String getNewVersion; + /** + * 进行授权 + */ + public static String auth; + /** + * 判断是否授权 + */ + public static String isAuth; + /** + * 获取用户信息 + */ + public static String getUserInfo; + /** + * 获取今日价格 + */ + public static String getPriceOfToday; + /** + * 磅单查询 扣皮定价 茧别转换 + */ + public static String queryList; + /** + * 收购统计 + */ + public static String queryStatistics; + /** + * 查询所有收购类型 + */ + public static String getAllType; + /** + * 茧别转换 + */ + public static String toConvert; + /** + * 扣皮保存 + */ + public static String tareSubmit; + /** + * 天气 + */ + public static String weather; + /** + * 关于信息 + */ + public static String getAboutInfo; + /** + * 检查授权状态 + */ + public static String checkAuth; + /** + * 弃售/确认售 + */ + public static String configOrCancel; +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/network/XHttpManager.java b/app/src/main/java/com/bbitcn/silk/utils/network/XHttpManager.java new file mode 100644 index 0000000..325148f --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/network/XHttpManager.java @@ -0,0 +1,61 @@ +package com.bbitcn.silk.utils.network; + +import com.bbitcn.silk.utils.MMKVUtil; +import com.bbitcn.silk.utils.global.Global; + +import org.xutils.http.RequestParams; + +/** + * @Author:BinJianXin + * @Date:2022/5/30/9:34 + * @Declaration: + */ +public class XHttpManager { + private static XHttpManager xHttpManager; + + public static XHttpManager getInstance() { + if (xHttpManager == null) { + synchronized (XHttpManager.class) { + if (xHttpManager == null) { + xHttpManager = new XHttpManager(); + } + } + } + return xHttpManager; + } + + /** + * 获得网络请求头 + * + * @param url 地址 + */ + public RequestParams getRequestParams(String url) { + return getRequestParams(url, true, false, 0, 0); + } + + /** + * 获得网络请求头 + * + * @param url 地址 + * @param needToken 是否需要token + */ + public RequestParams getRequestParams(String url, boolean needToken) { + return getRequestParams(url, needToken, false, 0, 0); + } + public RequestParams getRequestParams(String url, boolean needToken, boolean needPage, int page, int limit) { + RequestParams params = new RequestParams(url); + params.setReadTimeout(10000); + params.setConnectTimeout(10000); + params.addHeader("Content-Type", "application/json,charset=UTF-8"); + params.setUseCookie(false); + params.setAsJsonContent(true); + if (needToken) { + params.addHeader("Authorization", MMKVUtil.get(Global.TOKEN)); + } + if (needPage) { + //NeedPage:是否需要分页,Page:第几页,Limit:每页数目,OrderBy:排序 + params.addHeader("Page-Info", "{\"NeedPage\":true,\"Page\":" + page + ",\"Limit\":" + limit + ",\"OrderBy\":\"\"}"); + } + return params; + } +} diff --git a/app/src/main/java/com/bbitcn/silk/utils/network/XHttpShorthand.java b/app/src/main/java/com/bbitcn/silk/utils/network/XHttpShorthand.java new file mode 100644 index 0000000..66e4b5a --- /dev/null +++ b/app/src/main/java/com/bbitcn/silk/utils/network/XHttpShorthand.java @@ -0,0 +1,129 @@ +package com.bbitcn.silk.utils.network; + +import android.app.Activity; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.bbitcn.silk.model.net.CommonResponse; +import com.bbitcn.silk.utils.log.MyLog; +import com.blankj.utilcode.util.ActivityUtils; +import com.blankj.utilcode.util.GsonUtils; + +import org.json.JSONException; +import org.xutils.common.Callback; +import org.xutils.ex.HttpException; + +import java.lang.reflect.ParameterizedType; +import java.net.UnknownHostException; + +/** + * @Author:BinJianXin + * @Date:2022/5/30/9:49 + * @Declaration:用于减少逻辑界面xhttp需要实现的方法数 + */ +public abstract class XHttpShorthand implements Callback.CommonCallback { + + public abstract void success(T result) throws JSONException; + + private Class clazz; + + public XHttpShorthand() { + try { + ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); + clazz = (Class) genericSuperclass.getActualTypeArguments()[0]; + } catch (Exception e) { + e.printStackTrace(); + } + } + + public abstract void error(Throwable e); + + @Override + public void onSuccess(String result) { + MyLog.network("网络请求成功:" + result); + Message message = new Message(); + try { + CommonResponse temp = GsonUtils.fromJson(result, CommonResponse.class); + MyLog.network("已转为通用请求内容"); + if (temp != null) { +// MyLog.network("Code == 200"); + message.what = 0; + message.obj = GsonUtils.fromJson(result, clazz); + } else { +// MyLog.network("Code != 200"); + message.what = 2; + message.obj = temp; + } + } catch (Exception e) { + message.what = 1; + message.obj = e; + } + handler.sendMessage(message); + } + + public void onSuccessError(CommonResponse data) { + + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + MyLog.networkError("网络请求失败:错误消息:" + ex.getMessage() + "\t本地化消息:" + ex.getLocalizedMessage() + "\t原因:" + ex.getCause()); + if(ex instanceof UnknownHostException){ + //修改ex.getMessage()为"网络连接失败,请检查网络连接" 友好型提示 + ex = new Throwable("网络连接失败,请检查网络连接"); + } +// if (401 == ((HttpException) ex).getCode()) { +// MyLog.networkError("未授权"); +// //跳转到登录界面 +// Activity topActivity = ActivityUtils.getTopActivity(); +// ActivityUtils.startActivities(new Intent[]{new Intent(topActivity, LoginActivity.class)}); +// topActivity.finish(); +// } else { + Message message = new Message(); + message.what = 1; + message.obj = ex; + handler.sendMessage(message); +// } + } + + @Override + public void onCancelled(CancelledException cex) { + MyLog.network("网络请求取消" + cex.getMessage()); + } + + @Override + public void onFinished() { + Message message = new Message(); + message.what = 3; + handler.sendMessage(message); + } + + public void myFinish() { + MyLog.network("网络请求完成"); + } + + /** + * 转回到主线程 + */ + public Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(@androidx.annotation.NonNull Message msg) { + super.handleMessage(msg); + try { + if (msg.what == 0) { + success((T) msg.obj); + } else if (msg.what == 1) { + error((Throwable) msg.obj); + } else if (msg.what == 2) { + onSuccessError((CommonResponse) msg.obj); + } else if (msg.what == 3) { + myFinish(); + } + } catch (Exception e) { + MyLog.networkError("网络请求错误:" + e.getMessage()); + } + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/DbManager.java b/app/src/main/java/org/xutils/DbManager.java new file mode 100644 index 0000000..03c09bf --- /dev/null +++ b/app/src/main/java/org/xutils/DbManager.java @@ -0,0 +1,233 @@ +package org.xutils; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.text.TextUtils; + +import org.xutils.common.util.KeyValue; +import org.xutils.db.Selector; +import org.xutils.db.sqlite.SqlInfo; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.db.table.DbModel; +import org.xutils.db.table.TableEntity; +import org.xutils.ex.DbException; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.List; + +/** + * 数据库访问接口 + */ +public interface DbManager extends Closeable { + + DaoConfig getDaoConfig(); + + SQLiteDatabase getDatabase(); + + /** + * 保存实体类或实体类的List到数据库, + * 如果该类型的id是自动生成的, 则保存完后会给id赋值. + */ + boolean saveBindingId(Object entity) throws DbException; + + /** + * 保存或更新实体类或实体类的List到数据库, 根据id对应的数据是否存在. + */ + void saveOrUpdate(Object entity) throws DbException; + + /** + * 保存实体类或实体类的List到数据库 + */ + void save(Object entity) throws DbException; + + /** + * 保存或更新实体类或实体类的List到数据库, 根据id和其他唯一索引判断数据是否存在. + */ + void replace(Object entity) throws DbException; + + ///////////// delete + void deleteById(Class entityType, Object idValue) throws DbException; + + void delete(Object entity) throws DbException; + + void delete(Class entityType) throws DbException; + + int delete(Class entityType, WhereBuilder whereBuilder) throws DbException; + + ///////////// update + void update(Object entity, String... updateColumnNames) throws DbException; + + int update(Class entityType, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException; + + ///////////// find + T findById(Class entityType, Object idValue) throws DbException; + + T findFirst(Class entityType) throws DbException; + + List findAll(Class entityType) throws DbException; + + Selector selector(Class entityType) throws DbException; + + DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException; + + List findDbModelAll(SqlInfo sqlInfo) throws DbException; + + ///////////// table + + /** + * 获取表信息 + */ + TableEntity getTable(Class entityType) throws DbException; + + /** + * 删除表 + */ + void dropTable(Class entityType) throws DbException; + + /** + * 添加一列, + * 新的entityType中必须定义了这个列的属性. + */ + void addColumn(Class entityType, String column) throws DbException; + + ///////////// db + + /** + * 删除库 + */ + void dropDb() throws DbException; + + /** + * 关闭数据库. + * 同一个库是单实例的, 尽量不要调用这个方法, 会自动释放. + */ + void close() throws IOException; + + ///////////// custom + int executeUpdateDelete(SqlInfo sqlInfo) throws DbException; + + int executeUpdateDelete(String sql) throws DbException; + + void execNonQuery(SqlInfo sqlInfo) throws DbException; + + void execNonQuery(String sql) throws DbException; + + Cursor execQuery(SqlInfo sqlInfo) throws DbException; + + Cursor execQuery(String sql) throws DbException; + + public interface DbOpenListener { + void onDbOpened(DbManager db) throws DbException; + } + + public interface DbUpgradeListener { + void onUpgrade(DbManager db, int oldVersion, int newVersion) throws DbException; + } + + public interface TableCreateListener { + void onTableCreated(DbManager db, TableEntity table); + } + + public static class DaoConfig { + private File dbDir; + private String dbName = "xUtils.db"; // default db name + private int dbVersion = 1; + private boolean allowTransaction = true; + private DbUpgradeListener dbUpgradeListener; + private TableCreateListener tableCreateListener; + private DbOpenListener dbOpenListener; + + public DaoConfig() { + } + + public DaoConfig setDbDir(File dbDir) { + this.dbDir = dbDir; + return this; + } + + public DaoConfig setDbName(String dbName) { + if (!TextUtils.isEmpty(dbName)) { + this.dbName = dbName; + } + return this; + } + + public DaoConfig setDbVersion(int dbVersion) { + this.dbVersion = dbVersion; + return this; + } + + public DaoConfig setAllowTransaction(boolean allowTransaction) { + this.allowTransaction = allowTransaction; + return this; + } + + public DaoConfig setDbOpenListener(DbOpenListener dbOpenListener) { + this.dbOpenListener = dbOpenListener; + return this; + } + + public DaoConfig setDbUpgradeListener(DbUpgradeListener dbUpgradeListener) { + this.dbUpgradeListener = dbUpgradeListener; + return this; + } + + public DaoConfig setTableCreateListener(TableCreateListener tableCreateListener) { + this.tableCreateListener = tableCreateListener; + return this; + } + + public File getDbDir() { + return dbDir; + } + + public String getDbName() { + return dbName; + } + + public int getDbVersion() { + return dbVersion; + } + + public boolean isAllowTransaction() { + return allowTransaction; + } + + public DbOpenListener getDbOpenListener() { + return dbOpenListener; + } + + public DbUpgradeListener getDbUpgradeListener() { + return dbUpgradeListener; + } + + public TableCreateListener getTableCreateListener() { + return tableCreateListener; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DaoConfig daoConfig = (DaoConfig) o; + + if (!dbName.equals(daoConfig.dbName)) return false; + return dbDir == null ? daoConfig.dbDir == null : dbDir.equals(daoConfig.dbDir); + } + + @Override + public int hashCode() { + int result = dbName.hashCode(); + result = 31 * result + (dbDir != null ? dbDir.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return String.valueOf(dbDir) + "/" + dbName; + } + } +} diff --git a/app/src/main/java/org/xutils/HttpManager.java b/app/src/main/java/org/xutils/HttpManager.java new file mode 100644 index 0000000..758447d --- /dev/null +++ b/app/src/main/java/org/xutils/HttpManager.java @@ -0,0 +1,52 @@ +package org.xutils; + +import org.xutils.common.Callback; +import org.xutils.http.HttpMethod; +import org.xutils.http.RequestParams; + +/** + * Created by wyouflf on 15/6/17. + * http请求接口 + */ +public interface HttpManager { + + /** + * 异步GET请求 + */ + Callback.Cancelable get(RequestParams entity, Callback.CommonCallback callback); + + /** + * 异步POST请求 + */ + Callback.Cancelable post(RequestParams entity, Callback.CommonCallback callback); + /** + * 异步POST请求 + */ + Callback.Cancelable put(RequestParams entity, Callback.CommonCallback callback); + + /** + * 异步请求 + */ + Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback callback); + + + /** + * 同步GET请求 + */ + T getSync(RequestParams entity, Class resultType) throws Throwable; + + /** + * 同步POST请求 + */ + T postSync(RequestParams entity, Class resultType) throws Throwable; + + /** + * 同步请求 + */ + T requestSync(HttpMethod method, RequestParams entity, Class resultType) throws Throwable; + + /** + * 同步请求 + */ + T requestSync(HttpMethod method, RequestParams entity, Callback.TypedCallback callback) throws Throwable; +} diff --git a/app/src/main/java/org/xutils/ImageManager.java b/app/src/main/java/org/xutils/ImageManager.java new file mode 100644 index 0000000..5ec19e8 --- /dev/null +++ b/app/src/main/java/org/xutils/ImageManager.java @@ -0,0 +1,32 @@ +package org.xutils; + +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import org.xutils.common.Callback; +import org.xutils.image.ImageOptions; + +import java.io.File; + +/** + * Created by wyouflf on 15/6/17. + * 图片绑定接口 + */ +public interface ImageManager { + + void bind(ImageView view, String url); + + void bind(ImageView view, String url, ImageOptions options); + + void bind(ImageView view, String url, Callback.CommonCallback callback); + + void bind(ImageView view, String url, ImageOptions options, Callback.CommonCallback callback); + + Callback.Cancelable loadDrawable(String url, ImageOptions options, Callback.CommonCallback callback); + + Callback.Cancelable loadFile(String url, ImageOptions options, Callback.CacheCallback callback); + + void clearMemCache(); + + void clearCacheFiles(); +} diff --git a/app/src/main/java/org/xutils/ViewInjector.java b/app/src/main/java/org/xutils/ViewInjector.java new file mode 100644 index 0000000..e2bf92f --- /dev/null +++ b/app/src/main/java/org/xutils/ViewInjector.java @@ -0,0 +1,35 @@ +package org.xutils; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +/** + * Created by wyouflf on 15/10/29. + * view注入接口 + */ +public interface ViewInjector { + + /** + * 注入view + */ + void inject(View view); + + /** + * 注入activity + */ + void inject(Activity activity); + + /** + * 注入view holder + * + * @param handler view holder + */ + void inject(Object handler, View view); + + /** + * 注入fragment + */ + View inject(Object fragment, LayoutInflater inflater, ViewGroup container); +} diff --git a/app/src/main/java/org/xutils/cache/DiskCacheEntity.java b/app/src/main/java/org/xutils/cache/DiskCacheEntity.java new file mode 100644 index 0000000..62cfac0 --- /dev/null +++ b/app/src/main/java/org/xutils/cache/DiskCacheEntity.java @@ -0,0 +1,129 @@ +package org.xutils.cache; + +import org.xutils.db.annotation.Column; +import org.xutils.db.annotation.Table; + +import java.util.Date; + +/** + * Created by wyouflf on 15/8/2. + * 磁盘缓存对象 + */ +@Table(name = "disk_cache") +public final class DiskCacheEntity { + + @Column(name = "id", isId = true) + private long id; + + @Column(name = "key", property = "UNIQUE") + private String key; + + @Column(name = "path") + private String path; + + @Column(name = "textContent") + private String textContent; + + @Column(name = "bytesContent") + private byte[] bytesContent; + + // from "max-age" (since http 1.1) + @Column(name = "expires") + private long expires = Long.MAX_VALUE; + + @Column(name = "etag") + private String etag; + + @Column(name = "hits") + private long hits; + + @Column(name = "lastModify") + private Date lastModify; + + @Column(name = "lastAccess") + private long lastAccess; + + + public DiskCacheEntity() { + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + /*package*/ String getPath() { + return path; + } + + /*package*/ void setPath(String path) { + this.path = path; + } + + public String getTextContent() { + return textContent; + } + + public void setTextContent(String textContent) { + this.textContent = textContent; + } + + public byte[] getBytesContent() { + return bytesContent; + } + + public void setBytesContent(byte[] bytesContent) { + this.bytesContent = bytesContent; + } + + public long getExpires() { + return expires; + } + + public void setExpires(long expires) { + this.expires = expires; + } + + public String getEtag() { + return etag; + } + + public void setEtag(String etag) { + this.etag = etag; + } + + public long getHits() { + return hits; + } + + public void setHits(long hits) { + this.hits = hits; + } + + public Date getLastModify() { + return lastModify; + } + + public void setLastModify(Date lastModify) { + this.lastModify = lastModify; + } + + public long getLastAccess() { + return lastAccess == 0 ? System.currentTimeMillis() : lastAccess; + } + + public void setLastAccess(long lastAccess) { + this.lastAccess = lastAccess; + } +} diff --git a/app/src/main/java/org/xutils/cache/DiskCacheFile.java b/app/src/main/java/org/xutils/cache/DiskCacheFile.java new file mode 100644 index 0000000..021109c --- /dev/null +++ b/app/src/main/java/org/xutils/cache/DiskCacheFile.java @@ -0,0 +1,48 @@ +package org.xutils.cache; + +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.ProcessLock; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; + +/** + * Created by wyouflf on 15/8/3. + * 磁盘缓存文件, 操作完成后必须及时调用close()方法关闭. + */ +public final class DiskCacheFile extends File implements Closeable { + + private final DiskCacheEntity cacheEntity; + private final ProcessLock lock; + + /*package*/ DiskCacheFile(String path, DiskCacheEntity cacheEntity, ProcessLock lock) { + super(path); + this.cacheEntity = cacheEntity; + this.lock = lock; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(lock); + } + + public DiskCacheFile commit() throws IOException { + return getDiskCache().commitDiskCacheFile(this); + } + + public LruDiskCache getDiskCache() { + String dirName = this.getParentFile().getName(); + return LruDiskCache.getDiskCache(dirName); + } + + public DiskCacheEntity getCacheEntity() { + return cacheEntity; + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + this.close(); + } +} diff --git a/app/src/main/java/org/xutils/cache/LruCache.java b/app/src/main/java/org/xutils/cache/LruCache.java new file mode 100644 index 0000000..c031d55 --- /dev/null +++ b/app/src/main/java/org/xutils/cache/LruCache.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.cache; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Static library version of {@link android.util.LruCache}. Used to write apps + * that run on API levels prior to 12. When running on API level 12 or above, + * this implementation is still used; it does not try to switch to the + * framework's implementation. See the framework SDK documentation for a class + * overview. + */ +public class LruCache { + private final LinkedHashMap map; + + /** + * Size of this cache in units. Not necessarily the number of elements. + */ + private int size; + private int maxSize; + + private int putCount; + private int createCount; + private int evictionCount; + private int hitCount; + private int missCount; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap(0, 0.75f, true); + } + + /** + * Sets the size of the cache. + * + * @param maxSize The new maximum size. + */ + public void resize(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + + synchronized (this) { + this.maxSize = maxSize; + } + trimToSize(maxSize); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + */ + public final V get(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map + * may be different when create() returns. If a conflicting value was + * added to the map while create() was working, we leave that value in + * the map and release the created value. + */ + + V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + // There was a conflict so undo that last put + map.put(key, mapValue); + } else { + size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @return the previous value mapped by {@code key}. + */ + public final V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + putCount++; + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * Remove the eldest entries until the total of remaining entries is at or + * below the requested size. + * + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + public void trimToSize(int maxSize) { + while (true) { + K key; + V value; + synchronized (this) { + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + + if (size <= maxSize || map.isEmpty()) { + break; + } + + Map.Entry toEvict = map.entrySet().iterator().next(); + key = toEvict.getKey(); + value = toEvict.getValue(); + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @return the previous value mapped by {@code key}. + */ + public final V remove(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + * Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing. + *

The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. + */ + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { + } + + /** + * Called after a cache miss to compute a value for the corresponding key. + * Returns the computed value or null if no value can be computed. The + * default implementation returns null. + *

The method is called without synchronization: other threads may + * access the cache while this method is executing. + *

If a value for {@code key} exists in the cache when this method + * returns, the created value will be released with {@link #entryRemoved} + * and discarded. This can occur when multiple threads request the same key + * at the same time (causing multiple values to be created), or when one + * thread calls {@link #put} while another is creating a value for the same + * key. + */ + protected V create(K key) { + return null; + } + + private int safeSizeOf(K key, V value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size + * is the number of entries and max size is the maximum number of entries. + *

An entry's size must not change while it is in the cache. + */ + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + */ + public synchronized final int size() { + return size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + */ + public synchronized final int maxSize() { + return maxSize; + } + + /** + * Returns the number of times {@link #get} returned a value that was + * already present in the cache. + */ + public synchronized final int hitCount() { + return hitCount; + } + + /** + * Returns the number of times {@link #get} returned null or required a new + * value to be created. + */ + public synchronized final int missCount() { + return missCount; + } + + /** + * Returns the number of times {@link #create(Object)} returned a value. + */ + public synchronized final int createCount() { + return createCount; + } + + /** + * Returns the number of times {@link #put} was called. + */ + public synchronized final int putCount() { + return putCount; + } + + /** + * Returns the number of values that have been evicted. + */ + public synchronized final int evictionCount() { + return evictionCount; + } + + /** + * Returns a copy of the current contents of the cache, ordered from least + * recently accessed to most recently accessed. + */ + public synchronized final Map snapshot() { + return new LinkedHashMap(map); + } + + @Override + public synchronized final String toString() { + int accesses = hitCount + missCount; + int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; + return String.format(Locale.getDefault(), + "LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", + maxSize, hitCount, missCount, hitPercent); + } +} diff --git a/app/src/main/java/org/xutils/cache/LruDiskCache.java b/app/src/main/java/org/xutils/cache/LruDiskCache.java new file mode 100644 index 0000000..8101a63 --- /dev/null +++ b/app/src/main/java/org/xutils/cache/LruDiskCache.java @@ -0,0 +1,391 @@ +package org.xutils.cache; + + +import android.text.TextUtils; + +import org.xutils.DbManager; +import org.xutils.common.task.PriorityExecutor; +import org.xutils.common.util.FileUtil; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.common.util.MD5; +import org.xutils.common.util.ProcessLock; +import org.xutils.config.DbConfigs; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.ex.FileLockedException; +import org.xutils.x; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Created by wyouflf on 15/7/23. + * 使用sqlite索引实现的LruDiskCache + */ +public final class LruDiskCache { + + /** + * key: cacheDirName + */ + private static final HashMap DISK_CACHE_MAP = new HashMap(5); + + private static final int LIMIT_COUNT = 5000; // 限制最多5000条数据 + private static final long LIMIT_SIZE = 1024L * 1024L * 100L; // 限制最多100M文件 + + private static final int LOCK_WAIT = 1000 * 3; // 3s + private static final String CACHE_DIR_NAME = "xUtils_cache"; + private static final String TEMP_FILE_SUFFIX = ".tmp"; + + private boolean available = false; + private DbManager cacheDb; + private File cacheDir; + private long diskCacheSize = LIMIT_SIZE; + private final Executor trimExecutor = new PriorityExecutor(1, true); + + private long lastTrimTime = 0L; + private static final long TRIM_TIME_SPAN = 1000; + + public synchronized static LruDiskCache getDiskCache(String dirName) { + if (TextUtils.isEmpty(dirName)) dirName = CACHE_DIR_NAME; + LruDiskCache cache = DISK_CACHE_MAP.get(dirName); + if (cache == null) { + cache = new LruDiskCache(dirName); + DISK_CACHE_MAP.put(dirName, cache); + } + return cache; + } + + private LruDiskCache(String dirName) { + try { + this.cacheDir = FileUtil.getCacheDir(dirName); + if (this.cacheDir != null && (this.cacheDir.exists() || this.cacheDir.mkdirs())) { + available = true; + } + this.cacheDb = x.getDb(DbConfigs.HTTP.getConfig()); + } catch (Throwable ex) { + available = false; + LogUtil.e(ex.getMessage(), ex); + } + deleteNoIndexFiles(); + } + + public LruDiskCache setMaxSize(long maxSize) { + if (maxSize > 0L) { + long diskFreeSize = FileUtil.getDiskAvailableSize(); + if (diskFreeSize > maxSize) { + diskCacheSize = maxSize; + } else { + diskCacheSize = diskFreeSize; + } + } + return this; + } + + public DiskCacheEntity get(String key) { + if (!available || TextUtils.isEmpty(key)) return null; + + DiskCacheEntity result = null; + try { + result = this.cacheDb.selector(DiskCacheEntity.class) + .where("key", "=", key).findFirst(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + if (result != null) { + + if (result.getExpires() < System.currentTimeMillis()) { + return null; + } + + { // update hint & lastAccess... + final DiskCacheEntity finalResult = result; + trimExecutor.execute(new Runnable() { + @Override + public void run() { + finalResult.setHits(finalResult.getHits() + 1); + finalResult.setLastAccess(System.currentTimeMillis()); + try { + cacheDb.update(finalResult, "hits", "lastAccess"); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + } + + return result; + } + + public void put(DiskCacheEntity entity) { + if (!available + || entity == null + || TextUtils.isEmpty(entity.getTextContent()) + || entity.getExpires() < System.currentTimeMillis()) { + return; + } + + try { + cacheDb.replace(entity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } + + public DiskCacheFile getDiskCacheFile(String key) throws InterruptedException { + if (!available || TextUtils.isEmpty(key)) { + return null; + } + + DiskCacheFile result = null; + DiskCacheEntity entity = get(key); + if (entity != null && new File(entity.getPath()).exists()) { + ProcessLock processLock = ProcessLock.tryLock(entity.getPath(), false, LOCK_WAIT); + if (processLock != null && processLock.isValid()) { + result = new DiskCacheFile(entity.getPath(), entity, processLock); + if (!result.exists()) { + try { + cacheDb.delete(entity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + result = null; + } + } + } + + return result; + } + + public DiskCacheFile createDiskCacheFile(DiskCacheEntity entity) throws IOException { + if (!available || entity == null) { + return null; + } + + DiskCacheFile result = null; + + entity.setPath(new File(this.cacheDir, MD5.md5(entity.getKey())).getAbsolutePath()); + String tempFilePath = entity.getPath() + TEMP_FILE_SUFFIX; + ProcessLock processLock = ProcessLock.tryLock(tempFilePath, true); + if (processLock != null && processLock.isValid()) { + result = new DiskCacheFile(tempFilePath, entity, processLock); + if (!result.getParentFile().exists()) { + result.mkdirs(); + } + } else { + throw new FileLockedException(entity.getPath()); + } + + return result; + } + + public void clearCacheFiles() { + IOUtil.deleteFileOrDir(cacheDir); + } + + /** + * 添加缓存文件 + * + * @param cacheFile + */ + /*package*/ DiskCacheFile commitDiskCacheFile(DiskCacheFile cacheFile) throws IOException { + if (!available || cacheFile == null) { + return cacheFile; + } + + DiskCacheFile result = null; + DiskCacheEntity cacheEntity = cacheFile.getCacheEntity(); + if (cacheFile.getName().endsWith(TEMP_FILE_SUFFIX)) { // is temp file + ProcessLock processLock = null; + DiskCacheFile destFile = null; + try { + String destPath = cacheEntity.getPath(); + processLock = ProcessLock.tryLock(destPath, true, LOCK_WAIT); + if (processLock != null && processLock.isValid()) { // lock + destFile = new DiskCacheFile(destPath, cacheEntity, processLock); + if (cacheFile.renameTo(destFile)) { + try { + result = destFile; + cacheDb.replace(cacheEntity); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } else { + throw new IOException("rename:" + cacheFile.getAbsolutePath()); + } + } else { + throw new FileLockedException(destPath); + } + } catch (InterruptedException ex) { + result = cacheFile; + LogUtil.e(ex.getMessage(), ex); + } finally { + if (result == null) { + result = cacheFile; + IOUtil.closeQuietly(destFile); + IOUtil.closeQuietly(processLock); + IOUtil.deleteFileOrDir(destFile); + } else { + IOUtil.closeQuietly(cacheFile); + IOUtil.deleteFileOrDir(cacheFile); + } + } + } else { + result = cacheFile; + } + + return result; + } + + private void trimSize() { + trimExecutor.execute(new Runnable() { + @Override + public void run() { + if (!available) return; + + long current = System.currentTimeMillis(); + if (current - lastTrimTime < TRIM_TIME_SPAN) { + return; + } else { + lastTrimTime = current; + } + + // trim expires + deleteExpiry(); + + // trim db + try { + int count = (int) cacheDb.selector(DiskCacheEntity.class).count(); + if (count > LIMIT_COUNT + 10) { + List rmList = cacheDb.selector(DiskCacheEntity.class) + .orderBy("lastAccess").orderBy("hits") + .limit(count - LIMIT_COUNT).offset(0).findAll(); + if (rmList != null && rmList.size() > 0) { + // delete cache files + for (DiskCacheEntity entity : rmList) { + try { + // delete db entity + cacheDb.delete(entity); + // delete cache files + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + deleteFileWithLock(path + TEMP_FILE_SUFFIX); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + // trim disk + try { + while (FileUtil.getFileOrDirSize(cacheDir) > diskCacheSize) { + List rmList = cacheDb.selector(DiskCacheEntity.class) + .orderBy("lastAccess").orderBy("hits").limit(10).offset(0).findAll(); + if (rmList != null && rmList.size() > 0) { + // delete cache files + for (DiskCacheEntity entity : rmList) { + try { + // delete db entity + cacheDb.delete(entity); + // delete cache files + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + deleteFileWithLock(path + TEMP_FILE_SUFFIX); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + private void deleteExpiry() { + if (!available) return; + + try { + WhereBuilder whereBuilder = WhereBuilder.b("expires", "<", System.currentTimeMillis()); + List rmList = cacheDb.selector(DiskCacheEntity.class).where(whereBuilder).findAll(); + // delete db entities + cacheDb.delete(DiskCacheEntity.class, whereBuilder); + if (rmList != null && rmList.size() > 0) { + // delete cache files + for (DiskCacheEntity entity : rmList) { + String path = entity.getPath(); + if (!TextUtils.isEmpty(path)) { + deleteFileWithLock(path); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + /** + * 清理未被数据库索引的历史缓存文件 + */ + private void deleteNoIndexFiles() { + trimExecutor.execute(new Runnable() { + @Override + public void run() { + if (!available) return; + + try { + File[] fileList = cacheDir.listFiles(); + if (fileList != null) { + for (File file : fileList) { + try { + long count = cacheDb.selector(DiskCacheEntity.class) + .where("path", "=", file.getAbsolutePath()).count(); + if (count < 1) { + IOUtil.deleteFileOrDir(file); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + private boolean deleteFileWithLock(String path) { + ProcessLock processLock = null; + try { + processLock = ProcessLock.tryLock(path, true); + if (processLock != null && processLock.isValid()) { // lock + File file = new File(path); + return IOUtil.deleteFileOrDir(file); + } + } finally { + IOUtil.closeQuietly(processLock); + } + return false; + } +} diff --git a/app/src/main/java/org/xutils/common/Callback.java b/app/src/main/java/org/xutils/common/Callback.java new file mode 100644 index 0000000..8abd2fd --- /dev/null +++ b/app/src/main/java/org/xutils/common/Callback.java @@ -0,0 +1,72 @@ +package org.xutils.common; + +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/6/5. + * 通用回调接口 + */ +public interface Callback { + + public interface CommonCallback extends Callback { + void onSuccess(ResultType result); + + void onError(Throwable ex, boolean isOnCallback); + + void onCancelled(CancelledException cex); + + void onFinished(); + } + + public interface TypedCallback extends CommonCallback { + Type getLoadType(); + } + + public interface CacheCallback extends CommonCallback { + boolean onCache(ResultType result); + } + + public interface ProxyCacheCallback extends CacheCallback { + boolean onlyCache(); + } + + public interface PrepareCallback extends CommonCallback { + ResultType prepare(PrepareType rawData) throws Throwable; + } + + public interface ProgressCallback extends CommonCallback { + void onWaiting(); + + void onStarted(); + + void onLoading(long total, long current, boolean isDownloading); + } + + public interface GroupCallback extends Callback { + void onSuccess(ItemType item); + + void onError(ItemType item, Throwable ex, boolean isOnCallback); + + void onCancelled(ItemType item, CancelledException cex); + + void onFinished(ItemType item); + + void onAllFinished(); + } + + public interface Callable { + void call(ResultType result); + } + + public interface Cancelable { + void cancel(); + + boolean isCancelled(); + } + + public static class CancelledException extends RuntimeException { + public CancelledException(String detailMessage) { + super(detailMessage); + } + } +} diff --git a/app/src/main/java/org/xutils/common/TaskController.java b/app/src/main/java/org/xutils/common/TaskController.java new file mode 100644 index 0000000..940cee8 --- /dev/null +++ b/app/src/main/java/org/xutils/common/TaskController.java @@ -0,0 +1,54 @@ +package org.xutils.common; + +import org.xutils.common.task.AbsTask; + +/** + * Created by wyouflf on 15/6/11. + * 任务管理接口 + */ +public interface TaskController { + + /** + * 在UI线程执行runnable. + * 如果已在UI线程, 则直接执行. + */ + void autoPost(Runnable runnable); + + /** + * 在UI线程执行runnable. + * post到msg queue. + */ + void post(Runnable runnable); + + /** + * 在UI线程执行runnable. + * + * @param delayMillis 延迟时间(单位毫秒) + */ + void postDelayed(Runnable runnable, long delayMillis); + + /** + * 在后台线程执行runnable + */ + void run(Runnable runnable); + + /** + * 移除post或postDelayed提交的, 未执行的runnable + */ + void removeCallbacks(Runnable runnable); + + /** + * 开始一个异步任务 + */ + AbsTask start(AbsTask task); + + /** + * 同步执行一个任务 + */ + T startSync(AbsTask task) throws Throwable; + + /** + * 批量执行异步任务 + */ + > Callback.Cancelable startTasks(Callback.GroupCallback groupCallback, T... tasks); +} diff --git a/app/src/main/java/org/xutils/common/task/AbsTask.java b/app/src/main/java/org/xutils/common/task/AbsTask.java new file mode 100644 index 0000000..37cf580 --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/AbsTask.java @@ -0,0 +1,154 @@ +package org.xutils.common.task; + +import android.os.Looper; + +import org.xutils.common.Callback; + +import java.util.concurrent.Executor; + + +/** + * Created by wyouflf on 15/6/5. + * 异步任务基类 + * + * @param 任务返回值类型 + */ +public abstract class AbsTask implements Callback.Cancelable { + + private TaskProxy taskProxy = null; + private final Callback.Cancelable cancelHandler; + + private volatile boolean isCancelled = false; + private volatile State state = State.IDLE; + private ResultType result; + + public AbsTask() { + this(null); + } + + public AbsTask(Callback.Cancelable cancelHandler) { + this.cancelHandler = cancelHandler; + } + + protected abstract ResultType doBackground() throws Throwable; + + protected abstract void onSuccess(ResultType result); + + protected abstract void onError(Throwable ex, boolean isCallbackError); + + protected void onWaiting() { + } + + protected void onStarted() { + } + + protected void onUpdate(int flag, Object... args) { + } + + protected void onCancelled(Callback.CancelledException cex) { + } + + protected void onFinished() { + } + + public Priority getPriority() { + return null; + } + + public Executor getExecutor() { + return null; + } + + public Looper customLooper() { + return null; + } + + protected final void update(int flag, Object... args) { + if (taskProxy != null) { + taskProxy.onUpdate(flag, args); + } + } + + /** + * invoked via cancel() + */ + protected void cancelWorks() { + } + + /** + * 取消任务时是否不等待任务彻底结束, 立即收到取消的通知. + * + * @return 是否立即响应取消回调 + */ + protected boolean isCancelFast() { + return false; + } + + @Override + public final void cancel() { + if (this.isCancelled) return; + synchronized (this) { + if (this.isCancelled) return; + this.isCancelled = true; + cancelWorks(); + if (cancelHandler != null && !cancelHandler.isCancelled()) { + cancelHandler.cancel(); + } + if (this.state == State.WAITING || (this.state == State.STARTED && isCancelFast())) { + if (taskProxy != null) { + taskProxy.onCancelled(new Callback.CancelledException("cancelled by user")); + taskProxy.onFinished(); + } else if (this instanceof TaskProxy) { + this.onCancelled(new Callback.CancelledException("cancelled by user")); + this.onFinished(); + } + } + } + } + + @Override + public final boolean isCancelled() { + return isCancelled || state == State.CANCELLED || + (cancelHandler != null && cancelHandler.isCancelled()); + } + + public final boolean isFinished() { + return this.state.value() > State.STARTED.value(); + } + + public final State getState() { + return state; + } + + public final ResultType getResult() { + return result; + } + + /*package*/ + void setState(State state) { + this.state = state; + } + + /*package*/ + final void setTaskProxy(TaskProxy taskProxy) { + this.taskProxy = taskProxy; + } + + /*package*/ + final void setResult(ResultType result) { + this.result = result; + } + + public enum State { + IDLE(0), WAITING(1), STARTED(2), SUCCESS(3), CANCELLED(4), ERROR(5); + private final int value; + + private State(int value) { + this.value = value; + } + + public int value() { + return value; + } + } +} diff --git a/app/src/main/java/org/xutils/common/task/Priority.java b/app/src/main/java/org/xutils/common/task/Priority.java new file mode 100644 index 0000000..ce61044 --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/Priority.java @@ -0,0 +1,9 @@ +package org.xutils.common.task; + +/** + * Created by wyouflf on 15/6/5. + * 任务的优先级 + */ +public enum Priority { + UI_TOP, UI_NORMAL, UI_LOW, DEFAULT, BG_TOP, BG_NORMAL, BG_LOW; +} diff --git a/app/src/main/java/org/xutils/common/task/PriorityExecutor.java b/app/src/main/java/org/xutils/common/task/PriorityExecutor.java new file mode 100644 index 0000000..e342b1e --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/PriorityExecutor.java @@ -0,0 +1,113 @@ +package org.xutils.common.task; + +import java.util.Comparator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by wyouflf on 15/6/5. + * 支持优先级的线程池管理类 + */ +public class PriorityExecutor implements Executor { + + private static final int CORE_POOL_SIZE = 5; + private static final int MAXIMUM_POOL_SIZE = 256; + private static final int KEEP_ALIVE = 1; + private static final AtomicLong SEQ_SEED = new AtomicLong(0); + + private static final ThreadFactory sThreadFactory = new ThreadFactory() { + private final AtomicInteger mCount = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable runnable) { + return new Thread(runnable, "xTID#" + mCount.getAndIncrement()); + } + }; + + private static final Comparator FIFO_CMP = new Comparator() { + @Override + public int compare(Runnable lhs, Runnable rhs) { + if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) { + PriorityRunnable lpr = ((PriorityRunnable) lhs); + PriorityRunnable rpr = ((PriorityRunnable) rhs); + int result = lpr.priority.ordinal() - rpr.priority.ordinal(); + return result == 0 ? (int) (lpr.SEQ - rpr.SEQ) : result; + } else { + return 0; + } + } + }; + + private static final Comparator FILO_CMP = new Comparator() { + @Override + public int compare(Runnable lhs, Runnable rhs) { + if (lhs instanceof PriorityRunnable && rhs instanceof PriorityRunnable) { + PriorityRunnable lpr = ((PriorityRunnable) lhs); + PriorityRunnable rpr = ((PriorityRunnable) rhs); + int result = lpr.priority.ordinal() - rpr.priority.ordinal(); + return result == 0 ? (int) (rpr.SEQ - lpr.SEQ) : result; + } else { + return 0; + } + } + }; + + private final ThreadPoolExecutor mThreadPoolExecutor; + + /** + * 默认工作线程数5 + * + * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务. + */ + public PriorityExecutor(boolean fifo) { + this(CORE_POOL_SIZE, fifo); + } + + /** + * @param poolSize 工作线程数 + * @param fifo 优先级相同时, 等待队列的是否优先执行先加入的任务. + */ + public PriorityExecutor(int poolSize, boolean fifo) { + BlockingQueue mPoolWorkQueue = + new PriorityBlockingQueue(MAXIMUM_POOL_SIZE, fifo ? FIFO_CMP : FILO_CMP); + mThreadPoolExecutor = new ThreadPoolExecutor( + poolSize, + MAXIMUM_POOL_SIZE, + KEEP_ALIVE, + TimeUnit.SECONDS, + mPoolWorkQueue, + sThreadFactory); + } + + public int getPoolSize() { + return mThreadPoolExecutor.getCorePoolSize(); + } + + public void setPoolSize(int poolSize) { + if (poolSize > 0) { + mThreadPoolExecutor.setCorePoolSize(poolSize); + } + } + + public ThreadPoolExecutor getThreadPoolExecutor() { + return mThreadPoolExecutor; + } + + public boolean isBusy() { + return mThreadPoolExecutor.getActiveCount() >= mThreadPoolExecutor.getCorePoolSize(); + } + + @Override + public void execute(Runnable runnable) { + if (runnable instanceof PriorityRunnable) { + ((PriorityRunnable) runnable).SEQ = SEQ_SEED.getAndIncrement(); + } + mThreadPoolExecutor.execute(runnable); + } +} diff --git a/app/src/main/java/org/xutils/common/task/PriorityRunnable.java b/app/src/main/java/org/xutils/common/task/PriorityRunnable.java new file mode 100644 index 0000000..0fd3521 --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/PriorityRunnable.java @@ -0,0 +1,23 @@ +package org.xutils.common.task; + +/** + * Created by wyouflf on 15/6/5. + * 带有优先级的Runnable类型(仅在task包内可用) + */ +/*package*/ class PriorityRunnable implements Runnable { + + /*package*/ long SEQ; + + public final Priority priority; + private final Runnable runnable; + + public PriorityRunnable(Priority priority, Runnable runnable) { + this.priority = priority == null ? Priority.DEFAULT : priority; + this.runnable = runnable; + } + + @Override + public final void run() { + this.runnable.run(); + } +} diff --git a/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java b/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java new file mode 100644 index 0000000..71baf7d --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/TaskControllerImpl.java @@ -0,0 +1,258 @@ +package org.xutils.common.task; + +import android.os.Looper; + +import org.xutils.common.Callback; +import org.xutils.common.TaskController; +import org.xutils.common.util.LogUtil; +import org.xutils.x; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/6/5. + * 异步任务的管理类 + */ +public final class TaskControllerImpl implements TaskController { + + private TaskControllerImpl() { + } + + private static volatile TaskController instance; + + public static void registerInstance() { + if (instance == null) { + synchronized (TaskController.class) { + if (instance == null) { + instance = new TaskControllerImpl(); + } + } + } + x.Ext.setTaskController(instance); + } + + /** + * run task + */ + @Override + public AbsTask start(AbsTask task) { + TaskProxy proxy = null; + if (task instanceof TaskProxy) { + proxy = (TaskProxy) task; + } else { + proxy = new TaskProxy(task); + } + try { + proxy.doBackground(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return proxy; + } + + @Override + public T startSync(AbsTask task) throws Throwable { + T result = null; + try { + task.onWaiting(); + task.onStarted(); + result = task.doBackground(); + task.onSuccess(result); + } catch (Callback.CancelledException cex) { + task.onCancelled(cex); + } catch (Throwable ex) { + task.onError(ex, false); + throw ex; + } finally { + task.onFinished(); + } + return result; + } + + @Override + @SuppressWarnings("unchecked") + public > Callback.Cancelable startTasks( + final Callback.GroupCallback groupCallback, final T... tasks) { + + if (tasks == null) { + throw new IllegalArgumentException("task must not be null"); + } + + final Runnable callIfOnAllFinished = new Runnable() { + private final int total = tasks.length; + private final AtomicInteger count = new AtomicInteger(0); + + @Override + public void run() { + if (count.incrementAndGet() == total) { + if (groupCallback != null) { + try { + groupCallback.onAllFinished(); + } catch (Throwable ex) { + try { + groupCallback.onError(null, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + } + }; + + for (final T task : tasks) { + start(new TaskProxy(task) { + @Override + protected void onSuccess(Object result) { + super.onSuccess(result); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onSuccess(task); + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + + @Override + protected void onCancelled(final Callback.CancelledException cex) { + super.onCancelled(cex); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onCancelled(task, cex); + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + + @Override + protected void onError(final Throwable ex, final boolean isCallbackError) { + super.onError(ex, isCallbackError); + post(new Runnable() { + @Override + public void run() { + if (groupCallback != null) { + try { + groupCallback.onError(task, ex, isCallbackError); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + }); + } + + @Override + protected void onFinished() { + super.onFinished(); + post(new Runnable() { + @Override + public void run() { + try { + if (groupCallback != null) { + groupCallback.onFinished(task); + } + } catch (Throwable ex) { + try { + groupCallback.onError(task, ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } finally { + callIfOnAllFinished.run(); + } + } + }); + } + }); + } + + return new Callback.Cancelable() { + + @Override + public void cancel() { + for (T task : tasks) { + task.cancel(); + } + } + + @Override + public boolean isCancelled() { + boolean isCancelled = true; + for (T task : tasks) { + if (!task.isCancelled()) { + isCancelled = false; + } + } + return isCancelled; + } + }; + } + + @Override + public void autoPost(Runnable runnable) { + if (runnable == null) return; + if (Thread.currentThread() == Looper.getMainLooper().getThread()) { + runnable.run(); + } else { + TaskProxy.sHandler.post(runnable); + } + } + + /** + * run in UI thread + */ + @Override + public void post(Runnable runnable) { + if (runnable == null) return; + TaskProxy.sHandler.post(runnable); + } + + /** + * run in UI thread + */ + @Override + public void postDelayed(Runnable runnable, long delayMillis) { + if (runnable == null) return; + TaskProxy.sHandler.postDelayed(runnable, delayMillis); + } + + /** + * run in background thread + */ + @Override + public void run(Runnable runnable) { + if (!TaskProxy.sDefaultExecutor.isBusy()) { + TaskProxy.sDefaultExecutor.execute(runnable); + } else { + new Thread(runnable).start(); + } + } + + /** + * 移除post或postDelayed提交的, 未执行的runnable + */ + @Override + public void removeCallbacks(Runnable runnable) { + TaskProxy.sHandler.removeCallbacks(runnable); + } +} diff --git a/app/src/main/java/org/xutils/common/task/TaskProxy.java b/app/src/main/java/org/xutils/common/task/TaskProxy.java new file mode 100644 index 0000000..4d23154 --- /dev/null +++ b/app/src/main/java/org/xutils/common/task/TaskProxy.java @@ -0,0 +1,254 @@ +package org.xutils.common.task; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import org.xutils.common.Callback; +import org.xutils.common.util.LogUtil; +import org.xutils.x; + +import java.util.concurrent.Executor; + +/** + * 异步任务的代理类(仅在task包内可用) + * + * @param + */ +/*package*/ class TaskProxy extends AbsTask { + + /*package*/ static final InternalHandler sHandler = new InternalHandler(); + /*package*/ static final PriorityExecutor sDefaultExecutor = new PriorityExecutor(true); + + private final AbsTask task; + private final Executor executor; + private final Handler handler; + private volatile boolean callOnCanceled = false; + private volatile boolean callOnFinished = false; + + /*package*/ TaskProxy(AbsTask task) { + super(task); + this.task = task; + this.task.setTaskProxy(this); + this.setTaskProxy(null); + + // set handler + Looper looper = task.customLooper(); + if (looper != null) { + handler = new InternalHandler(looper); + } else { + handler = sHandler; + } + + // set executor + Executor taskExecutor = task.getExecutor(); + if (taskExecutor == null) { + taskExecutor = sDefaultExecutor; + } + this.executor = taskExecutor; + } + + @Override + protected final ResultType doBackground() throws Throwable { + this.onWaiting(); + PriorityRunnable runnable = new PriorityRunnable( + task.getPriority(), + new Runnable() { + @Override + public void run() { + try { + // 等待过程中取消 + if (callOnCanceled || TaskProxy.this.isCancelled()) { + throw new Callback.CancelledException(""); + } + + // start running + TaskProxy.this.onStarted(); + + if (TaskProxy.this.isCancelled()) { // 开始时取消 + throw new Callback.CancelledException(""); + } + + // 执行task, 得到结果. + task.setResult(task.doBackground()); + TaskProxy.this.setResult(task.getResult()); + + // 未在doBackground过程中取消成功 + if (TaskProxy.this.isCancelled()) { + throw new Callback.CancelledException(""); + } + + // 执行成功 + TaskProxy.this.onSuccess(task.getResult()); + } catch (Callback.CancelledException cex) { + TaskProxy.this.onCancelled(cex); + } catch (Throwable ex) { + TaskProxy.this.onError(ex, false); + } finally { + TaskProxy.this.onFinished(); + } + } + }); + this.executor.execute(runnable); + return null; + } + + @Override + protected void onWaiting() { + this.setState(State.WAITING); + handler.obtainMessage(MSG_WHAT_ON_WAITING, this).sendToTarget(); + } + + @Override + protected void onStarted() { + this.setState(State.STARTED); + handler.obtainMessage(MSG_WHAT_ON_START, this).sendToTarget(); + } + + @Override + protected void onSuccess(ResultType result) { + this.setState(State.SUCCESS); + handler.obtainMessage(MSG_WHAT_ON_SUCCESS, this).sendToTarget(); + } + + @Override + protected void onError(Throwable ex, boolean isCallbackError) { + this.setState(State.ERROR); + handler.obtainMessage(MSG_WHAT_ON_ERROR, new ArgsObj(this, ex)).sendToTarget(); + } + + @Override + protected void onUpdate(int flag, Object... args) { + // obtainMessage(int what, int arg1, int arg2, Object obj), arg2 not be used. + handler.obtainMessage(MSG_WHAT_ON_UPDATE, flag, flag, new ArgsObj(this, args)).sendToTarget(); + } + + @Override + protected void onCancelled(Callback.CancelledException cex) { + this.setState(State.CANCELLED); + handler.obtainMessage(MSG_WHAT_ON_CANCEL, new ArgsObj(this, cex)).sendToTarget(); + } + + @Override + protected void onFinished() { + handler.obtainMessage(MSG_WHAT_ON_FINISHED, this).sendToTarget(); + } + + @Override + /*package*/ final void setState(State state) { + super.setState(state); + this.task.setState(state); + } + + @Override + public final Priority getPriority() { + return task.getPriority(); + } + + @Override + public final Executor getExecutor() { + return this.executor; + } + + // ########################### inner type ############################# + private static class ArgsObj { + final TaskProxy taskProxy; + final Object[] args; + + public ArgsObj(TaskProxy taskProxy, Object... args) { + this.taskProxy = taskProxy; + this.args = args; + } + } + + private final static int MSG_WHAT_BASE = 1000000000; + private final static int MSG_WHAT_ON_WAITING = MSG_WHAT_BASE + 1; + private final static int MSG_WHAT_ON_START = MSG_WHAT_BASE + 2; + private final static int MSG_WHAT_ON_SUCCESS = MSG_WHAT_BASE + 3; + private final static int MSG_WHAT_ON_ERROR = MSG_WHAT_BASE + 4; + private final static int MSG_WHAT_ON_UPDATE = MSG_WHAT_BASE + 5; + private final static int MSG_WHAT_ON_CANCEL = MSG_WHAT_BASE + 6; + private final static int MSG_WHAT_ON_FINISHED = MSG_WHAT_BASE + 7; + + /*package*/ final static class InternalHandler extends Handler { + + private InternalHandler() { + super(Looper.getMainLooper()); + } + + private InternalHandler(Looper looper) { + super(looper); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message msg) { + if (msg.obj == null) { + throw new IllegalArgumentException("msg must not be null"); + } + TaskProxy taskProxy = null; + Object[] args = null; + if (msg.obj instanceof TaskProxy) { + taskProxy = (TaskProxy) msg.obj; + } else if (msg.obj instanceof ArgsObj) { + ArgsObj argsObj = (ArgsObj) msg.obj; + taskProxy = argsObj.taskProxy; + args = argsObj.args; + } + if (taskProxy == null) { + throw new RuntimeException("msg.obj not instanceof TaskProxy"); + } + + try { + switch (msg.what) { + case MSG_WHAT_ON_WAITING: { + taskProxy.task.onWaiting(); + break; + } + case MSG_WHAT_ON_START: { + taskProxy.task.onStarted(); + break; + } + case MSG_WHAT_ON_SUCCESS: { + taskProxy.task.onSuccess(taskProxy.getResult()); + break; + } + case MSG_WHAT_ON_ERROR: { + assert args != null; + Throwable throwable = (Throwable) args[0]; + LogUtil.d(throwable.getMessage(), throwable); + taskProxy.task.onError(throwable, false); + break; + } + case MSG_WHAT_ON_UPDATE: { + taskProxy.task.onUpdate(msg.arg1, args); + break; + } + case MSG_WHAT_ON_CANCEL: { + if (taskProxy.callOnCanceled) return; + taskProxy.callOnCanceled = true; + assert args != null; + taskProxy.task.onCancelled((org.xutils.common.Callback.CancelledException) args[0]); + break; + } + case MSG_WHAT_ON_FINISHED: { + if (taskProxy.callOnFinished) return; + taskProxy.callOnFinished = true; + taskProxy.task.onFinished(); + break; + } + default: { + break; + } + } + } catch (Throwable ex) { + taskProxy.setState(State.ERROR); + if (msg.what != MSG_WHAT_ON_ERROR) { + taskProxy.task.onError(ex, true); + } else if (x.isDebug()) { + throw new RuntimeException(ex); + } + } + } + } +} diff --git a/app/src/main/java/org/xutils/common/util/DensityUtil.java b/app/src/main/java/org/xutils/common/util/DensityUtil.java new file mode 100644 index 0000000..c32a5fa --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/DensityUtil.java @@ -0,0 +1,44 @@ +package org.xutils.common.util; + +import org.xutils.x; + + +public final class DensityUtil { + + private static float density = -1F; + private static int widthPixels = -1; + private static int heightPixels = -1; + + private DensityUtil() { + } + + public static float getDensity() { + if (density <= 0F) { + density = x.app().getResources().getDisplayMetrics().density; + } + return density; + } + + public static int dip2px(float dpValue) { + return (int) (dpValue * getDensity() + 0.5F); + } + + public static int px2dip(float pxValue) { + return (int) (pxValue / getDensity() + 0.5F); + } + + public static int getScreenWidth() { + if (widthPixels <= 0) { + widthPixels = x.app().getResources().getDisplayMetrics().widthPixels; + } + return widthPixels; + } + + + public static int getScreenHeight() { + if (heightPixels <= 0) { + heightPixels = x.app().getResources().getDisplayMetrics().heightPixels; + } + return heightPixels; + } +} diff --git a/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java b/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java new file mode 100644 index 0000000..76fee5f --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/DoubleKeyValueMap.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.common.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created with IntelliJ IDEA. + * User: wyouflf + * Date: 13-6-19 + * Time: PM 1:18 + */ +public class DoubleKeyValueMap { + + private final ConcurrentHashMap> k1_k2V_map; + + public DoubleKeyValueMap() { + this.k1_k2V_map = new ConcurrentHashMap>(); + } + + public void put(K1 key1, K2 key2, V value) { + if (key1 == null || key2 == null || value == null) return; + if (k1_k2V_map.containsKey(key1)) { + ConcurrentHashMap k2V_map = k1_k2V_map.get(key1); + if (k2V_map != null) { + k2V_map.put(key2, value); + } else { + k2V_map = new ConcurrentHashMap(); + k2V_map.put(key2, value); + k1_k2V_map.put(key1, k2V_map); + } + } else { + ConcurrentHashMap k2V_map = new ConcurrentHashMap(); + k2V_map.put(key2, value); + k1_k2V_map.put(key1, k2V_map); + } + } + + public Set getFirstKeys() { + return k1_k2V_map.keySet(); + } + + public ConcurrentHashMap get(K1 key1) { + return k1_k2V_map.get(key1); + } + + public V get(K1 key1, K2 key2) { + ConcurrentHashMap k2_v = k1_k2V_map.get(key1); + return k2_v == null ? null : k2_v.get(key2); + } + + public Collection getAllValues(K1 key1) { + ConcurrentHashMap k2_v = k1_k2V_map.get(key1); + return k2_v == null ? null : k2_v.values(); + } + + public Collection getAllValues() { + Collection result = null; + Set k1Set = k1_k2V_map.keySet(); + if (k1Set != null) { + result = new ArrayList(); + for (K1 k1 : k1Set) { + ConcurrentHashMap value1 = k1_k2V_map.get(k1); + if (value1 != null) { + Collection values = value1.values(); + if (values != null) { + result.addAll(values); + } + } + } + } + return result; + } + + public boolean containsKey(K1 key1, K2 key2) { + if (k1_k2V_map.containsKey(key1)) { + ConcurrentHashMap value1 = k1_k2V_map.get(key1); + if (value1 != null) { + return value1.containsKey(key2); + } + } + return false; + } + + public boolean containsKey(K1 key1) { + return k1_k2V_map.containsKey(key1); + } + + public int size() { + if (k1_k2V_map.size() == 0) return 0; + + int result = 0; + for (ConcurrentHashMap k2V_map : k1_k2V_map.values()) { + result += k2V_map.size(); + } + return result; + } + + public void remove(K1 key1) { + k1_k2V_map.remove(key1); + } + + public void remove(K1 key1, K2 key2) { + ConcurrentHashMap k2_v = k1_k2V_map.get(key1); + if (k2_v != null) { + k2_v.remove(key2); + } + if (k2_v == null || k2_v.isEmpty()) { + k1_k2V_map.remove(key1); + } + } + + public void clear() { + if (k1_k2V_map.size() > 0) { + for (ConcurrentHashMap k2V_map : k1_k2V_map.values()) { + k2V_map.clear(); + } + k1_k2V_map.clear(); + } + } +} diff --git a/app/src/main/java/org/xutils/common/util/FileUtil.java b/app/src/main/java/org/xutils/common/util/FileUtil.java new file mode 100644 index 0000000..829988d --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/FileUtil.java @@ -0,0 +1,136 @@ +package org.xutils.common.util; + +import android.os.Environment; +import android.os.StatFs; + +import org.xutils.x; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class FileUtil { + + private FileUtil() { + } + + public static File getCacheDir(String dirName) { + File result = null; + if (isDiskAvailable()) { + File cacheDir = x.app().getExternalCacheDir(); + if (cacheDir != null) { + result = new File(cacheDir, dirName); + } + } + if (result == null) { + result = new File(x.app().getCacheDir(), dirName); + } + if (result.exists() || result.mkdirs()) { + return result; + } else { + return null; + } + } + + /** + * 检查磁盘空间是否大于10mb + * + * @return true 大于 + */ + public static boolean isDiskAvailable() { + long size = getDiskAvailableSize(); + return size > 10 * 1024 * 1024L; // > 10bm + } + + /** + * 获取磁盘可用空间 + * + * @return byte + */ + public static long getDiskAvailableSize() { + if (!existsSdcard()) return 0; + File path = Environment.getExternalStorageDirectory(); // 取得sdcard文件路径 + StatFs stat = new StatFs(path.getAbsolutePath()); + long blockSize = 0; + long availableBlocks = 0; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = stat.getBlockSizeLong(); + availableBlocks = stat.getAvailableBlocksLong(); + } else { + blockSize = stat.getBlockSize(); + availableBlocks = stat.getAvailableBlocks(); + } + return availableBlocks * blockSize; + } + + public static Boolean existsSdcard() { + return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + } + + public static long getFileOrDirSize(File file) { + if (!file.exists()) return 0; + if (!file.isDirectory()) return file.length(); + + long length = 0; + File[] list = file.listFiles(); + if (list != null) { // 文件夹被删除时, 子文件正在被写入, 文件属性异常返回null. + for (File item : list) { + length += getFileOrDirSize(item); + } + } + + return length; + } + + /** + * 复制文件到指定文件 + * + * @param fromPath 源文件 + * @param toPath 复制到的文件 + * @return true 成功,false 失败 + */ + public static boolean copy(String fromPath, String toPath) { + boolean result = false; + File from = new File(fromPath); + if (!from.exists()) { + return result; + } + + File toFile = new File(toPath); + IOUtil.deleteFileOrDir(toFile); + File toDir = toFile.getParentFile(); + if (toDir.exists() || toDir.mkdirs()) { + FileInputStream in = null; + FileOutputStream out = null; + try { + in = new FileInputStream(from); + out = new FileOutputStream(toFile); + IOUtil.copy(in, out); + result = true; + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + result = false; + } finally { + IOUtil.closeQuietly(in); + IOUtil.closeQuietly(out); + } + } + return result; + } + + public static boolean deleteFileOrDir(File path) { + if (path == null || !path.exists()) { + return true; + } + if (path.isFile()) { + return path.delete(); + } + File[] files = path.listFiles(); + if (files != null) { + for (File file : files) { + deleteFileOrDir(file); + } + } + return path.delete(); + } +} diff --git a/app/src/main/java/org/xutils/common/util/IOUtil.java b/app/src/main/java/org/xutils/common/util/IOUtil.java new file mode 100644 index 0000000..7d6b7b3 --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/IOUtil.java @@ -0,0 +1,127 @@ +package org.xutils.common.util; + +import android.database.Cursor; +import android.text.TextUtils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; + +public class IOUtil { + + private IOUtil() { + } + + public static void closeQuietly(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + } + } + } + + public static void closeQuietly(Cursor cursor) { + if (cursor != null) { + try { + cursor.close(); + } catch (Throwable ex) { + LogUtil.d(ex.getMessage(), ex); + } + } + } + + public static byte[] readBytes(InputStream in) throws IOException { + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + ByteArrayOutputStream out = null; + try { + out = new ByteArrayOutputStream(); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) != -1) { + out.write(buf, 0, len); + } + return out.toByteArray(); + } finally { + closeQuietly(out); + } + } + + public static byte[] readBytes(InputStream in, long skip, int size) throws IOException { + byte[] result = null; + if (skip > 0) { + long skipped = 0; + while (skip > 0 && (skipped = in.skip(skip)) > 0) { + skip -= skipped; + } + } + result = new byte[size]; + for (int i = 0; i < size; i++) { + result[i] = (byte) in.read(); + } + return result; + } + + public static String readStr(InputStream in) throws IOException { + return readStr(in, "UTF-8"); + } + + public static String readStr(InputStream in, String charset) throws IOException { + if (TextUtils.isEmpty(charset)) charset = "UTF-8"; + + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + Reader reader = new InputStreamReader(in, charset); + StringBuilder sb = new StringBuilder(); + char[] buf = new char[1024]; + int len; + while ((len = reader.read(buf)) >= 0) { + sb.append(buf, 0, len); + } + return sb.toString(); + } + + public static void writeStr(OutputStream out, String str) throws IOException { + writeStr(out, str, "UTF-8"); + } + + public static void writeStr(OutputStream out, String str, String charset) throws IOException { + if (TextUtils.isEmpty(charset)) charset = "UTF-8"; + + Writer writer = new OutputStreamWriter(out, charset); + writer.write(str); + writer.flush(); + } + + public static void copy(InputStream in, OutputStream out) throws IOException { + if (!(in instanceof BufferedInputStream)) { + in = new BufferedInputStream(in); + } + if (!(out instanceof BufferedOutputStream)) { + out = new BufferedOutputStream(out); + } + int len = 0; + byte[] buffer = new byte[1024]; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + out.flush(); + } + + public static boolean deleteFileOrDir(File path) { + return FileUtil.deleteFileOrDir(path); + } +} diff --git a/app/src/main/java/org/xutils/common/util/KeyValue.java b/app/src/main/java/org/xutils/common/util/KeyValue.java new file mode 100644 index 0000000..2cba986 --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/KeyValue.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.common.util; + +public class KeyValue { + public final String key; + public final Object value; + + public KeyValue(String key, Object value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public Object getValue() { + return value; + } + + /** + * 获取value的字符串值, 为null时返回空字符串 + */ + public String getValueStrOrEmpty() { + return value == null ? "" : value.toString(); + } + + /** + * 获取value的字符串值, 为null时返回null + */ + public String getValueStrOrNull() { + return value == null ? null : value.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + KeyValue keyValue = (KeyValue) o; + + return key == null ? keyValue.key == null : key.equals(keyValue.key); + + } + + @Override + public int hashCode() { + return key != null ? key.hashCode() : 0; + } + + @Override + public String toString() { + return "KeyValue{" + "key='" + key + '\'' + ", value=" + value + '}'; + } +} diff --git a/app/src/main/java/org/xutils/common/util/LogUtil.java b/app/src/main/java/org/xutils/common/util/LogUtil.java new file mode 100644 index 0000000..3914b0d --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/LogUtil.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.common.util; + +import android.text.TextUtils; +import android.util.Log; + +import org.xutils.x; + +import java.util.Locale; + +/** + * Log工具,类似android.util.Log。 + * tag自动产生,格式: customTagPrefix:className.methodName(L:lineNumber), + * customTagPrefix为空时只输出:className.methodName(L:lineNumber)。 + * Author: wyouflf + * Date: 13-7-24 + * Time: 下午12:23 + */ +public class LogUtil { + + public static String customTagPrefix = "x_log"; + + private LogUtil() { + } + + private static String generateTag() { + StackTraceElement caller = new Throwable().getStackTrace()[2]; + String tag = "%s.%s(L:%d)"; + String callerClazzName = caller.getClassName(); + callerClazzName = callerClazzName.substring(callerClazzName.lastIndexOf(".") + 1); + tag = String.format(Locale.getDefault(), tag, callerClazzName, caller.getMethodName(), caller.getLineNumber()); + tag = TextUtils.isEmpty(customTagPrefix) ? tag : customTagPrefix + ":" + tag; + return tag; + } + + public static void d(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.d(tag, content); + } + + public static void d(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.d(tag, content, tr); + } + + public static void e(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.e(tag, content); + } + + public static void e(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.e(tag, content, tr); + } + + public static void i(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.i(tag, content); + } + + public static void i(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.i(tag, content, tr); + } + + public static void v(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.v(tag, content); + } + + public static void v(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.v(tag, content, tr); + } + + public static void w(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.w(tag, content); + } + + public static void w(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.w(tag, content, tr); + } + + public static void w(Throwable tr) { + if (!x.isDebug()) return; + String tag = generateTag(); + + Log.w(tag, tr); + } + + + public static void wtf(String content) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.wtf(tag, content); + } + + public static void wtf(String content, Throwable tr) { + if (!x.isDebug() || TextUtils.isEmpty(content)) return; + String tag = generateTag(); + + Log.wtf(tag, content, tr); + } + + public static void wtf(Throwable tr) { + if (!x.isDebug()) return; + String tag = generateTag(); + + Log.wtf(tag, tr); + } + +} diff --git a/app/src/main/java/org/xutils/common/util/MD5.java b/app/src/main/java/org/xutils/common/util/MD5.java new file mode 100644 index 0000000..224de76 --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/MD5.java @@ -0,0 +1,64 @@ +package org.xutils.common.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public final class MD5 { + + private MD5() { + } + + private static final char[] hexDigits = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + public static String toHexString(byte[] bytes) { + if (bytes == null) return ""; + StringBuilder hex = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + hex.append(hexDigits[(b >> 4) & 0x0F]); + hex.append(hexDigits[b & 0x0F]); + } + return hex.toString(); + } + + public static String md5(File file) throws IOException { + MessageDigest messagedigest = null; + FileInputStream in = null; + FileChannel ch = null; + byte[] encodeBytes = null; + try { + messagedigest = MessageDigest.getInstance("MD5"); + in = new FileInputStream(file); + ch = in.getChannel(); + MappedByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); + messagedigest.update(byteBuffer); + encodeBytes = messagedigest.digest(); + } catch (NoSuchAlgorithmException neverHappened) { + throw new RuntimeException(neverHappened); + } finally { + IOUtil.closeQuietly(in); + IOUtil.closeQuietly(ch); + } + + return toHexString(encodeBytes); + } + + public static String md5(String string) { + byte[] encodeBytes = null; + try { + encodeBytes = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8")); + } catch (NoSuchAlgorithmException neverHappened) { + throw new RuntimeException(neverHappened); + } catch (UnsupportedEncodingException neverHappened) { + throw new RuntimeException(neverHappened); + } + + return toHexString(encodeBytes); + } +} diff --git a/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java b/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java new file mode 100644 index 0000000..f7ea273 --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/ParameterizedTypeUtil.java @@ -0,0 +1,97 @@ +package org.xutils.common.util; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +public class ParameterizedTypeUtil { + + private ParameterizedTypeUtil() { + } + + public static Type getParameterizedType( + + final Type ownerType, + final Class declaredClass, + int paramIndex) { + + Class clazz = null; + ParameterizedType pt = null; + Type[] ats = null; + TypeVariable[] tps = null; + if (ownerType instanceof ParameterizedType) { + pt = (ParameterizedType) ownerType; + clazz = (Class) pt.getRawType(); + ats = pt.getActualTypeArguments(); + tps = clazz.getTypeParameters(); + } else { + clazz = (Class) ownerType; + } + if (declaredClass == clazz) { + if (ats != null) { + return ats[paramIndex]; + } + return Object.class; + } + + Type[] types = clazz.getGenericInterfaces(); + if (types != null) { + for (int i = 0; i < types.length; i++) { + Type t = types[i]; + if (t instanceof ParameterizedType) { + Class cls = (Class) ((ParameterizedType) t).getRawType(); + if (declaredClass.isAssignableFrom(cls)) { + try { + return getTrueType(getParameterizedType(t, declaredClass, paramIndex), tps, ats); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + } + } + } + } + + Class superClass = clazz.getSuperclass(); + if (superClass != null) { + if (declaredClass.isAssignableFrom(superClass)) { + return getTrueType( + getParameterizedType(clazz.getGenericSuperclass(), + declaredClass, paramIndex), tps, ats); + } + } + + throw new IllegalArgumentException("FindGenericType:" + ownerType + + ", declaredClass: " + declaredClass + ", index: " + paramIndex); + + } + + + private static Type getTrueType( + + Type type, + TypeVariable[] typeVariables, + Type[] actualTypes) { + + if (type instanceof TypeVariable) { + TypeVariable tv = (TypeVariable) type; + String name = tv.getName(); + if (actualTypes != null) { + for (int i = 0; i < typeVariables.length; i++) { + if (name.equals(typeVariables[i].getName())) { + return actualTypes[i]; + } + } + } + return tv; + } else if (type instanceof GenericArrayType) { + Type ct = ((GenericArrayType) type).getGenericComponentType(); + if (ct instanceof Class) { + return Array.newInstance((Class) ct, 0).getClass(); + } + } + return type; + } + +} diff --git a/app/src/main/java/org/xutils/common/util/ProcessLock.java b/app/src/main/java/org/xutils/common/util/ProcessLock.java new file mode 100644 index 0000000..9d939fb --- /dev/null +++ b/app/src/main/java/org/xutils/common/util/ProcessLock.java @@ -0,0 +1,234 @@ +package org.xutils.common.util; + + +import android.content.Context; +import android.text.TextUtils; + +import org.xutils.x; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.text.DecimalFormat; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 进程间锁, 仅在同一个应用中有效. + */ +public final class ProcessLock implements Closeable { + + private final String mLockName; + private final FileLock mFileLock; + private final File mFile; + private final Closeable mStream; + private final boolean mWriteMode; + + private final static String LOCK_FILE_DIR = "process_lock"; + /** + * key1: lockName + * key2: fileLock.hashCode() + */ + private final static DoubleKeyValueMap LOCK_MAP = new DoubleKeyValueMap(); + + static { + File dir = x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE); + IOUtil.deleteFileOrDir(dir); + } + + private ProcessLock(String lockName, File file, FileLock fileLock, Closeable stream, boolean writeMode) { + mLockName = lockName; + mFileLock = fileLock; + mFile = file; + mStream = stream; + mWriteMode = writeMode; + } + + /** + * 获取进程锁 + * + * @param lockName 锁的名称, 相同的名称被认为是同一个锁. + * @param writeMode 是否写入模式(支持读并发). + * @return null 或 进程锁, 如果锁已经被占用, 返回null. + */ + public static ProcessLock tryLock(final String lockName, final boolean writeMode) { + return tryLockInternal(lockName, customHash(lockName), writeMode); + } + + /** + * 获取进程锁 + * + * @param lockName 锁的名称, 相同的名称被认为是同一个锁. + * @param writeMode 是否写入模式(支持读并发). + * @param maxWaitTimeMillis 最大值 1000 * 60 + * @return null 或 进程锁, 如果锁已经被占用, 则在超时时间内继续尝试获取该锁. + */ + public static ProcessLock tryLock(final String lockName, final boolean writeMode, final long maxWaitTimeMillis) throws InterruptedException { + ProcessLock lock = null; + long expiryTime = System.currentTimeMillis() + maxWaitTimeMillis; + String hash = customHash(lockName); + synchronized (LOCK_MAP) { + while (System.currentTimeMillis() < expiryTime) { + lock = tryLockInternal(lockName, hash, writeMode); + if (lock != null) { + break; + } else { + try { + LOCK_MAP.wait(10); + } catch (InterruptedException iex) { + throw iex; + } catch (Throwable ignored) { + } + } + } + } + + return lock; + } + + /** + * 锁是否有效 + */ + public boolean isValid() { + return isValid(mFileLock); + } + + /** + * 释放锁 + */ + public void release() { + release(mLockName, mFileLock, mFile, mStream); + } + + /** + * 释放锁 + */ + @Override + public void close() throws IOException { + release(); + } + + private static boolean isValid(FileLock fileLock) { + return fileLock != null && fileLock.isValid(); + } + + private static void release(String lockName, FileLock fileLock, File file, Closeable stream) { + synchronized (LOCK_MAP) { + if (fileLock != null) { + try { + LOCK_MAP.remove(lockName, fileLock.hashCode()); + ConcurrentHashMap locks = LOCK_MAP.get(lockName); + if (locks == null || locks.isEmpty()) { + IOUtil.deleteFileOrDir(file); + } + + if (fileLock.channel().isOpen()) { + fileLock.release(); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(fileLock.channel()); + } + } + + IOUtil.closeQuietly(stream); + + LOCK_MAP.notifyAll(); + } + } + + private final static DecimalFormat FORMAT = new DecimalFormat("0.##################"); + + // 取得字符串的自定义hash值, 尽量保证255字节内的hash不重复. + private static String customHash(String str) { + if (TextUtils.isEmpty(str)) return "0"; + double hash = 0.0; + byte[] bytes = str.getBytes(); + for (int i = 0; i < str.length(); i++) { + hash = (255.0 * hash + bytes[i]) * 0.005; + } + return FORMAT.format(hash); + } + + private static ProcessLock tryLockInternal(final String lockName, final String hash, final boolean writeMode) { + synchronized (LOCK_MAP) { + + ConcurrentHashMap locks = LOCK_MAP.get(lockName); + if (locks != null && !locks.isEmpty()) { + Iterator> itr = locks.entrySet().iterator(); + while (itr.hasNext()) { + Map.Entry entry = itr.next(); + ProcessLock value = entry.getValue(); + if (value != null) { + if (!value.isValid()) { + itr.remove(); + } else if (writeMode) { + return null; + } else if (value.mWriteMode) { + return null; + } + } else { + itr.remove(); + } + } + } + + FileChannel channel = null; + Closeable stream = null; + try { + File file = new File( + x.app().getDir(LOCK_FILE_DIR, Context.MODE_PRIVATE), + hash); + if (file.exists() || file.createNewFile()) { + + if (writeMode) { + FileOutputStream out = new FileOutputStream(file, false); + channel = out.getChannel(); + stream = out; + } else { + FileInputStream in = new FileInputStream(file); + channel = in.getChannel(); + stream = in; + } + if (channel != null) { + FileLock fileLock = channel.tryLock(0L, Long.MAX_VALUE, !writeMode); + if (isValid(fileLock)) { + ProcessLock result = new ProcessLock(lockName, file, fileLock, stream, writeMode); + LOCK_MAP.put(lockName, fileLock.hashCode(), result); + return result; + } else { + release(lockName, fileLock, file, stream); + } + } else { + throw new IOException("can not get file channel:" + file.getAbsolutePath()); + } + } + } catch (Throwable ex) { + LogUtil.d("tryLock: " + lockName + ", " + ex.getMessage()); + IOUtil.closeQuietly(stream); + IOUtil.closeQuietly(channel); + } + + LOCK_MAP.notifyAll(); + } + + return null; + } + + @Override + public String toString() { + return mLockName + ": " + mFile.getName(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + this.release(); + } +} diff --git a/app/src/main/java/org/xutils/config/DbConfigs.java b/app/src/main/java/org/xutils/config/DbConfigs.java new file mode 100644 index 0000000..e6453b8 --- /dev/null +++ b/app/src/main/java/org/xutils/config/DbConfigs.java @@ -0,0 +1,61 @@ +package org.xutils.config; + +import org.xutils.DbManager; +import org.xutils.common.util.LogUtil; +import org.xutils.ex.DbException; + +/** + * Created by wyouflf on 15/7/31. + * 全局db配置 + */ +public enum DbConfigs { + HTTP(new DbManager.DaoConfig() + .setDbName("xUtils_http_cache.db") + .setDbVersion(2) + .setDbOpenListener(new DbManager.DbOpenListener() { + @Override + public void onDbOpened(DbManager db) { + db.getDatabase().enableWriteAheadLogging(); + } + }) + .setDbUpgradeListener(new DbManager.DbUpgradeListener() { + @Override + public void onUpgrade(DbManager db, int oldVersion, int newVersion) { + try { + db.dropDb(); // 默认删除所有表 + } catch (DbException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + })), + + COOKIE(new DbManager.DaoConfig() + .setDbName("xUtils_http_cookie.db") + .setDbVersion(1) + .setDbOpenListener(new DbManager.DbOpenListener() { + @Override + public void onDbOpened(DbManager db) { + db.getDatabase().enableWriteAheadLogging(); + } + }) + .setDbUpgradeListener(new DbManager.DbUpgradeListener() { + @Override + public void onUpgrade(DbManager db, int oldVersion, int newVersion) { + try { + db.dropDb(); // 默认删除所有表 + } catch (DbException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + })); + + private DbManager.DaoConfig config; + + DbConfigs(DbManager.DaoConfig config) { + this.config = config; + } + + public DbManager.DaoConfig getConfig() { + return config; + } +} diff --git a/app/src/main/java/org/xutils/db/CursorUtils.java b/app/src/main/java/org/xutils/db/CursorUtils.java new file mode 100644 index 0000000..84296df --- /dev/null +++ b/app/src/main/java/org/xutils/db/CursorUtils.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db; + +import android.database.Cursor; + +import org.xutils.db.table.ColumnEntity; +import org.xutils.db.table.DbModel; +import org.xutils.db.table.TableEntity; + +import java.util.HashMap; + +/*package*/ final class CursorUtils { + + public static T getEntity(TableEntity table, final Cursor cursor) throws Throwable { + T entity = table.createEntity(); + HashMap columnMap = table.getColumnMap(); + int columnCount = cursor.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + String columnName = cursor.getColumnName(i); + ColumnEntity column = columnMap.get(columnName); + if (column != null) { + column.setValueFromCursor(entity, cursor, i); + } + } + return entity; + } + + public static DbModel getDbModel(final Cursor cursor) { + DbModel result = new DbModel(); + int columnCount = cursor.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + result.add(cursor.getColumnName(i), cursor.getString(i)); + } + return result; + } +} diff --git a/app/src/main/java/org/xutils/db/DbManagerImpl.java b/app/src/main/java/org/xutils/db/DbManagerImpl.java new file mode 100644 index 0000000..7266d85 --- /dev/null +++ b/app/src/main/java/org/xutils/db/DbManagerImpl.java @@ -0,0 +1,591 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; +import android.os.Build; + +import org.xutils.DbManager; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.KeyValue; +import org.xutils.common.util.LogUtil; +import org.xutils.db.sqlite.SqlInfo; +import org.xutils.db.sqlite.SqlInfoBuilder; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.db.table.ColumnEntity; +import org.xutils.db.table.DbBase; +import org.xutils.db.table.DbModel; +import org.xutils.db.table.TableEntity; +import org.xutils.ex.DbException; +import org.xutils.x; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public final class DbManagerImpl extends DbBase { + + //*************************************** create instance **************************************************** + + /** + * key: dbName + */ + private final static HashMap DAO_MAP = new HashMap(); + + private SQLiteDatabase database; + private DaoConfig daoConfig; + private boolean allowTransaction; + + private DbManagerImpl(DaoConfig config) throws DbException { + if (config == null) { + throw new IllegalArgumentException("daoConfig may not be null"); + } + + this.daoConfig = config; + this.allowTransaction = config.isAllowTransaction(); + try { + this.database = openOrCreateDatabase(config); + DbOpenListener dbOpenListener = config.getDbOpenListener(); + if (dbOpenListener != null) { + dbOpenListener.onDbOpened(this); + } + } catch (DbException ex) { + IOUtil.closeQuietly(this.database); + throw ex; + } catch (Throwable ex) { + IOUtil.closeQuietly(this.database); + throw new DbException(ex.getMessage(), ex); + } + } + + public synchronized static DbManager getInstance(DaoConfig daoConfig) throws DbException { + + if (daoConfig == null) {//使用默认配置 + daoConfig = new DaoConfig(); + } + + DbManagerImpl dao = DAO_MAP.get(daoConfig); + if (dao == null) { + dao = new DbManagerImpl(daoConfig); + DAO_MAP.put(daoConfig, dao); + } else { + dao.daoConfig = daoConfig; + } + + // update the database if needed + SQLiteDatabase database = dao.database; + int oldVersion = database.getVersion(); + int newVersion = daoConfig.getDbVersion(); + if (oldVersion != newVersion) { + if (oldVersion != 0) { + DbUpgradeListener upgradeListener = daoConfig.getDbUpgradeListener(); + if (upgradeListener != null) { + upgradeListener.onUpgrade(dao, oldVersion, newVersion); + } else { + dao.dropDb(); + } + } + database.setVersion(newVersion); + } + + return dao; + } + + @Override + public SQLiteDatabase getDatabase() { + return database; + } + + @Override + public DaoConfig getDaoConfig() { + return daoConfig; + } + + //*********************************************** operations ******************************************************** + + @Override + public void saveOrUpdate(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return; + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + saveOrUpdateWithoutTransaction(table, item); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + saveOrUpdateWithoutTransaction(table, entity); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void replace(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return; + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void save(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return; + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public boolean saveBindingId(Object entity) throws DbException { + boolean result = false; + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return false; + TableEntity table = this.getTable(entities.get(0).getClass()); + table.createTableIfNotExists(); + for (Object item : entities) { + if (!saveBindingIdWithoutTransaction(table, item)) { + throw new DbException("saveBindingId error, transaction will not commit!"); + } + } + } else { + TableEntity table = this.getTable(entity.getClass()); + table.createTableIfNotExists(); + result = saveBindingIdWithoutTransaction(table, entity); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + return result; + } + + @Override + public void deleteById(Class entityType, Object idValue) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) return; + try { + beginTransaction(); + + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfoById(table, idValue)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void delete(Object entity) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return; + TableEntity table = this.getTable(entities.get(0).getClass()); + if (!table.tableIsExists()) return; + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, item)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + if (!table.tableIsExists()) return; + execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(table, entity)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public void delete(Class entityType) throws DbException { + delete(entityType, null); + } + + @Override + public int delete(Class entityType, WhereBuilder whereBuilder) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) return 0; + int result = 0; + try { + beginTransaction(); + + result = executeUpdateDelete(SqlInfoBuilder.buildDeleteSqlInfo(table, whereBuilder)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + return result; + } + + @Override + public void update(Object entity, String... updateColumnNames) throws DbException { + try { + beginTransaction(); + + if (entity instanceof List) { + List entities = (List) entity; + if (entities.isEmpty()) return; + TableEntity table = this.getTable(entities.get(0).getClass()); + if (!table.tableIsExists()) return; + for (Object item : entities) { + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, item, updateColumnNames)); + } + } else { + TableEntity table = this.getTable(entity.getClass()); + if (!table.tableIsExists()) return; + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity, updateColumnNames)); + } + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + } + + @Override + public int update(Class entityType, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) return 0; + + int result = 0; + try { + beginTransaction(); + + result = executeUpdateDelete(SqlInfoBuilder.buildUpdateSqlInfo(table, whereBuilder, nameValuePairs)); + + setTransactionSuccessful(); + } finally { + endTransaction(); + } + + return result; + } + + @Override + public T findById(Class entityType, Object idValue) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) return null; + + Selector selector = Selector.from(table).where(table.getId().getName(), "=", idValue); + String sql = selector.limit(1).toString(); + Cursor cursor = execQuery(sql); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return CursorUtils.getEntity(table, cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + @Override + public T findFirst(Class entityType) throws DbException { + return this.selector(entityType).findFirst(); + } + + @Override + public List findAll(Class entityType) throws DbException { + return this.selector(entityType).findAll(); + } + + @Override + public Selector selector(Class entityType) throws DbException { + return Selector.from(this.getTable(entityType)); + } + + @Override + public DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException { + Cursor cursor = execQuery(sqlInfo); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return CursorUtils.getDbModel(cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + @Override + public List findDbModelAll(SqlInfo sqlInfo) throws DbException { + List dbModelList = new ArrayList(); + + Cursor cursor = execQuery(sqlInfo); + if (cursor != null) { + try { + while (cursor.moveToNext()) { + dbModelList.add(CursorUtils.getDbModel(cursor)); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return dbModelList; + } + + //******************************************** config ****************************************************** + + private SQLiteDatabase openOrCreateDatabase(DaoConfig config) { + SQLiteDatabase result = null; + + File dbDir = config.getDbDir(); + if (dbDir != null && (dbDir.exists() || dbDir.mkdirs())) { + File dbFile = new File(dbDir, config.getDbName()); + result = SQLiteDatabase.openOrCreateDatabase(dbFile, null); + } else { + result = x.app().openOrCreateDatabase(config.getDbName(), 0, null); + } + return result; + } + + //***************************** private operations with out transaction ***************************** + private void saveOrUpdateWithoutTransaction(TableEntity table, Object entity) throws DbException { + ColumnEntity id = table.getId(); + if (id.isAutoId()) { + if (id.getColumnValue(entity) != null) { + execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(table, entity)); + } else { + saveBindingIdWithoutTransaction(table, entity); + } + } else { + execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(table, entity)); + } + } + + private boolean saveBindingIdWithoutTransaction(TableEntity table, Object entity) throws DbException { + ColumnEntity id = table.getId(); + if (id.isAutoId()) { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + long idValue = getLastAutoIncrementId(table.getName()); + if (idValue == -1) { + return false; + } + id.setAutoIdValue(entity, idValue); + return true; + } else { + execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(table, entity)); + return true; + } + } + + //************************************************ tools *********************************** + + private long getLastAutoIncrementId(String tableName) throws DbException { + long id = -1; + Cursor cursor = execQuery("SELECT seq FROM sqlite_sequence WHERE name='" + tableName + "' LIMIT 1"); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + id = cursor.getLong(0); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return id; + } + + /** + * 关闭数据库. + * 同一个库的是单实例的, 尽量不要调用这个方法, 会自动释放. + */ + @Override + public void close() throws IOException { + if (DAO_MAP.containsKey(daoConfig)) { + DAO_MAP.remove(daoConfig); + this.database.close(); + } + } + + ///////////////////////////////////// exec sql ///////////////////////////////////////////////////// + + private void beginTransaction() { + if (allowTransaction) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && database.isWriteAheadLoggingEnabled()) { + database.beginTransactionNonExclusive(); + } else { + database.beginTransaction(); + } + } + } + + private void setTransactionSuccessful() { + if (allowTransaction) { + database.setTransactionSuccessful(); + } + } + + private void endTransaction() { + if (allowTransaction) { + database.endTransaction(); + } + } + + + @Override + public int executeUpdateDelete(SqlInfo sqlInfo) throws DbException { + SQLiteStatement statement = null; + try { + statement = sqlInfo.buildStatement(database); + return statement.executeUpdateDelete(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.releaseReference(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public int executeUpdateDelete(String sql) throws DbException { + SQLiteStatement statement = null; + try { + statement = database.compileStatement(sql); + return statement.executeUpdateDelete(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.releaseReference(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public void execNonQuery(SqlInfo sqlInfo) throws DbException { + SQLiteStatement statement = null; + try { + statement = sqlInfo.buildStatement(database); + statement.execute(); + } catch (Throwable e) { + throw new DbException(e); + } finally { + if (statement != null) { + try { + statement.releaseReference(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + @Override + public void execNonQuery(String sql) throws DbException { + try { + database.execSQL(sql); + } catch (Throwable e) { + throw new DbException(e); + } + } + + @Override + public Cursor execQuery(SqlInfo sqlInfo) throws DbException { + try { + return database.rawQuery(sqlInfo.getSql(), sqlInfo.getBindArgsAsStrArray()); + } catch (Throwable e) { + throw new DbException(e); + } + } + + @Override + public Cursor execQuery(String sql) throws DbException { + try { + return database.rawQuery(sql, null); + } catch (Throwable e) { + throw new DbException(e); + } + } + +} diff --git a/app/src/main/java/org/xutils/db/DbModelSelector.java b/app/src/main/java/org/xutils/db/DbModelSelector.java new file mode 100644 index 0000000..0fe2003 --- /dev/null +++ b/app/src/main/java/org/xutils/db/DbModelSelector.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db; + +import android.database.Cursor; +import android.text.TextUtils; + +import org.xutils.common.util.IOUtil; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.db.table.DbModel; +import org.xutils.db.table.TableEntity; +import org.xutils.ex.DbException; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-8-10 + * Time: 下午2:15 + */ +public final class DbModelSelector { + + private String[] columnExpressions; + private String groupByColumnName; + private WhereBuilder having; + + private Selector selector; + + private DbModelSelector(TableEntity table) { + selector = Selector.from(table); + } + + protected DbModelSelector(Selector selector, String groupByColumnName) { + this.selector = selector; + this.groupByColumnName = groupByColumnName; + } + + protected DbModelSelector(Selector selector, String[] columnExpressions) { + this.selector = selector; + this.columnExpressions = columnExpressions; + } + + /*package*/ + static DbModelSelector from(TableEntity table) { + return new DbModelSelector(table); + } + + public DbModelSelector where(WhereBuilder whereBuilder) { + selector.where(whereBuilder); + return this; + } + + public DbModelSelector where(String columnName, String op, Object value) { + selector.where(columnName, op, value); + return this; + } + + public DbModelSelector and(String columnName, String op, Object value) { + selector.and(columnName, op, value); + return this; + } + + public DbModelSelector and(WhereBuilder where) { + selector.and(where); + return this; + } + + public DbModelSelector or(String columnName, String op, Object value) { + selector.or(columnName, op, value); + return this; + } + + public DbModelSelector or(WhereBuilder where) { + selector.or(where); + return this; + } + + public DbModelSelector expr(String expr) { + selector.expr(expr); + return this; + } + + public DbModelSelector groupBy(String columnName) { + this.groupByColumnName = columnName; + return this; + } + + public DbModelSelector having(WhereBuilder whereBuilder) { + this.having = whereBuilder; + return this; + } + + public DbModelSelector select(String... columnExpressions) { + this.columnExpressions = columnExpressions; + return this; + } + + /** + * 排序条件, 默认ASC + */ + public DbModelSelector orderBy(String columnName) { + selector.orderBy(columnName); + return this; + } + + /** + * 排序条件, 默认ASC + */ + public DbModelSelector orderBy(String columnName, boolean desc) { + selector.orderBy(columnName, desc); + return this; + } + + public DbModelSelector limit(int limit) { + selector.limit(limit); + return this; + } + + public DbModelSelector offset(int offset) { + selector.offset(offset); + return this; + } + + public TableEntity getTable() { + return selector.getTable(); + } + + public DbModel findFirst() throws DbException { + TableEntity table = selector.getTable(); + if (!table.tableIsExists()) return null; + + this.limit(1); + Cursor cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return CursorUtils.getDbModel(cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + public List findAll() throws DbException { + TableEntity table = selector.getTable(); + if (!table.tableIsExists()) return null; + + List result = null; + + Cursor cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + result = new ArrayList(); + while (cursor.moveToNext()) { + DbModel entity = CursorUtils.getDbModel(cursor); + result.add(entity); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return result; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("SELECT "); + if (columnExpressions != null && columnExpressions.length > 0) { + for (String columnExpression : columnExpressions) { + result.append(columnExpression); + result.append(","); + } + result.deleteCharAt(result.length() - 1); + } else { + if (!TextUtils.isEmpty(groupByColumnName)) { + result.append(groupByColumnName); + } else { + result.append("*"); + } + } + result.append(" FROM ").append("\"").append(selector.getTable().getName()).append("\""); + WhereBuilder whereBuilder = selector.getWhereBuilder(); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + result.append(" WHERE ").append(whereBuilder.toString()); + } + if (!TextUtils.isEmpty(groupByColumnName)) { + result.append(" GROUP BY ").append("\"").append(groupByColumnName).append("\""); + if (having != null && having.getWhereItemSize() > 0) { + result.append(" HAVING ").append(having.toString()); + } + } + List orderByList = selector.getOrderByList(); + if (orderByList != null && orderByList.size() > 0) { + result.append(" ORDER BY "); + for (Selector.OrderBy orderBy : orderByList) { + result.append(orderBy.toString()).append(','); + } + result.deleteCharAt(result.length() - 1); + } + if (selector.getLimit() > 0) { + result.append(" LIMIT ").append(selector.getLimit()); + result.append(" OFFSET ").append(selector.getOffset()); + } + return result.toString(); + } +} diff --git a/app/src/main/java/org/xutils/db/Selector.java b/app/src/main/java/org/xutils/db/Selector.java new file mode 100644 index 0000000..95879fc --- /dev/null +++ b/app/src/main/java/org/xutils/db/Selector.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db; + +import android.database.Cursor; + +import org.xutils.common.util.IOUtil; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.db.table.DbModel; +import org.xutils.db.table.TableEntity; +import org.xutils.ex.DbException; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-8-9 + * Time: 下午10:19 + */ +public final class Selector { + + private final TableEntity table; + + private WhereBuilder whereBuilder; + private List orderByList; + private int limit = 0; + private int offset = 0; + + private Selector(TableEntity table) { + this.table = table; + } + + /*package*/ + static Selector from(TableEntity table) { + return new Selector(table); + } + + public Selector where(WhereBuilder whereBuilder) { + this.whereBuilder = whereBuilder; + return this; + } + + public Selector where(String columnName, String op, Object value) { + this.whereBuilder = WhereBuilder.b(columnName, op, value); + return this; + } + + public Selector and(String columnName, String op, Object value) { + this.whereBuilder.and(columnName, op, value); + return this; + } + + public Selector and(WhereBuilder where) { + this.whereBuilder.and(where); + return this; + } + + public Selector or(String columnName, String op, Object value) { + this.whereBuilder.or(columnName, op, value); + return this; + } + + public Selector or(WhereBuilder where) { + this.whereBuilder.or(where); + return this; + } + + public Selector expr(String expr) { + if (this.whereBuilder == null) { + this.whereBuilder = WhereBuilder.b(); + } + this.whereBuilder.expr(expr); + return this; + } + + public DbModelSelector groupBy(String columnName) { + return new DbModelSelector(this, columnName); + } + + public DbModelSelector select(String... columnExpressions) { + return new DbModelSelector(this, columnExpressions); + } + + /** + * 排序条件, 默认ASC + */ + public Selector orderBy(String columnName) { + if (orderByList == null) { + orderByList = new ArrayList(5); + } + orderByList.add(new OrderBy(columnName)); + return this; + } + + /** + * 排序条件, 默认ASC + */ + public Selector orderBy(String columnName, boolean desc) { + if (orderByList == null) { + orderByList = new ArrayList(5); + } + orderByList.add(new OrderBy(columnName, desc)); + return this; + } + + public Selector limit(int limit) { + this.limit = limit; + return this; + } + + public Selector offset(int offset) { + this.offset = offset; + return this; + } + + public TableEntity getTable() { + return table; + } + + public WhereBuilder getWhereBuilder() { + return whereBuilder; + } + + public List getOrderByList() { + return orderByList; + } + + public int getLimit() { + return limit; + } + + public int getOffset() { + return offset; + } + + public T findFirst() throws DbException { + if (!table.tableIsExists()) return null; + + this.limit(1); + Cursor cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + return CursorUtils.getEntity(table, cursor); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return null; + } + + public List findAll() throws DbException { + if (!table.tableIsExists()) return null; + + List result = null; + Cursor cursor = table.getDb().execQuery(this.toString()); + if (cursor != null) { + try { + result = new ArrayList(); + while (cursor.moveToNext()) { + T entity = CursorUtils.getEntity(table, cursor); + result.add(entity); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + return result; + } + + public long count() throws DbException { + if (!table.tableIsExists()) return 0; + + DbModelSelector dmSelector = this.select("count(\"" + table.getId().getName() + "\") as count"); + DbModel firstModel = dmSelector.findFirst(); + if (firstModel != null) { + return firstModel.getLong("count", 0); + } + return 0; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("SELECT "); + result.append("*"); + result.append(" FROM ").append("\"").append(table.getName()).append("\""); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + result.append(" WHERE ").append(whereBuilder.toString()); + } + if (orderByList != null && orderByList.size() > 0) { + result.append(" ORDER BY "); + for (OrderBy orderBy : orderByList) { + result.append(orderBy.toString()).append(','); + } + result.deleteCharAt(result.length() - 1); + } + if (limit > 0) { + result.append(" LIMIT ").append(limit); + result.append(" OFFSET ").append(offset); + } + return result.toString(); + } + + public static class OrderBy { + private String columnName; + private boolean desc; + + /** + * 排序条件, 默认ASC + */ + public OrderBy(String columnName) { + this.columnName = columnName; + } + + /** + * 排序条件, 默认ASC + */ + public OrderBy(String columnName, boolean desc) { + this.columnName = columnName; + this.desc = desc; + } + + @Override + public String toString() { + return "\"" + columnName + "\"" + (desc ? " DESC" : " ASC"); + } + } +} diff --git a/app/src/main/java/org/xutils/db/annotation/Column.java b/app/src/main/java/org/xutils/db/annotation/Column.java new file mode 100644 index 0000000..b36f28d --- /dev/null +++ b/app/src/main/java/org/xutils/db/annotation/Column.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Column { + + String name(); + + String property() default ""; + + boolean isId() default false; + + boolean autoGen() default true; +} diff --git a/app/src/main/java/org/xutils/db/annotation/Table.java b/app/src/main/java/org/xutils/db/annotation/Table.java new file mode 100644 index 0000000..0189dba --- /dev/null +++ b/app/src/main/java/org/xutils/db/annotation/Table.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Table { + + String name(); + + String onCreated() default ""; +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java b/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java new file mode 100644 index 0000000..cf3f5df --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/BooleanColumnConverter.java @@ -0,0 +1,28 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class BooleanColumnConverter implements ColumnConverter { + @Override + public Boolean getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getInt(index) == 1; + } + + @Override + public Object fieldValue2DbValue(Boolean fieldValue) { + if (fieldValue == null) return null; + return fieldValue ? 1 : 0; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java new file mode 100644 index 0000000..f9de5a0 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/ByteArrayColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class ByteArrayColumnConverter implements ColumnConverter { + @Override + public byte[] getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getBlob(index); + } + + @Override + public Object fieldValue2DbValue(byte[] fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.BLOB; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java new file mode 100644 index 0000000..e574bec --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/ByteColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class ByteColumnConverter implements ColumnConverter { + @Override + public Byte getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : (byte) cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Byte fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java b/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java new file mode 100644 index 0000000..4d39523 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/CharColumnConverter.java @@ -0,0 +1,28 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class CharColumnConverter implements ColumnConverter { + @Override + public Character getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : (char) cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Character fieldValue) { + if (fieldValue == null) return null; + return (int) fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/ColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ColumnConverter.java new file mode 100644 index 0000000..9b6b0ec --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/ColumnConverter.java @@ -0,0 +1,19 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午8:57 + */ +public interface ColumnConverter { + + T getFieldValue(final Cursor cursor, int index); + + Object fieldValue2DbValue(T fieldValue); + + ColumnDbType getColumnDbType(); +} diff --git a/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java b/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java new file mode 100644 index 0000000..68dc20a --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/ColumnConverterFactory.java @@ -0,0 +1,108 @@ +package org.xutils.db.converter; + +import org.xutils.common.util.LogUtil; + +import java.util.Date; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:27 + */ +public final class ColumnConverterFactory { + + private ColumnConverterFactory() { + } + + public static ColumnConverter getColumnConverter(Class columnType) { + ColumnConverter result = null; + if (columnType_columnConverter_map.containsKey(columnType.getName())) { + result = columnType_columnConverter_map.get(columnType.getName()); + } else if (ColumnConverter.class.isAssignableFrom(columnType)) { + try { + ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance(); + columnType_columnConverter_map.put(columnType.getName(), columnConverter); + result = columnConverter; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + if (result == null) { + throw new RuntimeException("Database Column Not Support: " + columnType.getName() + + ", please impl ColumnConverter or use ColumnConverterFactory#registerColumnConverter(...)"); + } + + return result; + } + + public static void registerColumnConverter(Class columnType, ColumnConverter columnConverter) { + columnType_columnConverter_map.put(columnType.getName(), columnConverter); + } + + public static boolean isSupportColumnConverter(Class columnType) { + if (columnType_columnConverter_map.containsKey(columnType.getName())) { + return true; + } else if (ColumnConverter.class.isAssignableFrom(columnType)) { + try { + ColumnConverter columnConverter = (ColumnConverter) columnType.newInstance(); + columnType_columnConverter_map.put(columnType.getName(), columnConverter); + return true; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return false; + } + + private static final ConcurrentHashMap columnType_columnConverter_map; + + static { + columnType_columnConverter_map = new ConcurrentHashMap(); + + BooleanColumnConverter booleanColumnConverter = new BooleanColumnConverter(); + columnType_columnConverter_map.put(boolean.class.getName(), booleanColumnConverter); + columnType_columnConverter_map.put(Boolean.class.getName(), booleanColumnConverter); + + ByteArrayColumnConverter byteArrayColumnConverter = new ByteArrayColumnConverter(); + columnType_columnConverter_map.put(byte[].class.getName(), byteArrayColumnConverter); + + ByteColumnConverter byteColumnConverter = new ByteColumnConverter(); + columnType_columnConverter_map.put(byte.class.getName(), byteColumnConverter); + columnType_columnConverter_map.put(Byte.class.getName(), byteColumnConverter); + + CharColumnConverter charColumnConverter = new CharColumnConverter(); + columnType_columnConverter_map.put(char.class.getName(), charColumnConverter); + columnType_columnConverter_map.put(Character.class.getName(), charColumnConverter); + + DateColumnConverter dateColumnConverter = new DateColumnConverter(); + columnType_columnConverter_map.put(Date.class.getName(), dateColumnConverter); + + DoubleColumnConverter doubleColumnConverter = new DoubleColumnConverter(); + columnType_columnConverter_map.put(double.class.getName(), doubleColumnConverter); + columnType_columnConverter_map.put(Double.class.getName(), doubleColumnConverter); + + FloatColumnConverter floatColumnConverter = new FloatColumnConverter(); + columnType_columnConverter_map.put(float.class.getName(), floatColumnConverter); + columnType_columnConverter_map.put(Float.class.getName(), floatColumnConverter); + + IntegerColumnConverter integerColumnConverter = new IntegerColumnConverter(); + columnType_columnConverter_map.put(int.class.getName(), integerColumnConverter); + columnType_columnConverter_map.put(Integer.class.getName(), integerColumnConverter); + + LongColumnConverter longColumnConverter = new LongColumnConverter(); + columnType_columnConverter_map.put(long.class.getName(), longColumnConverter); + columnType_columnConverter_map.put(Long.class.getName(), longColumnConverter); + + ShortColumnConverter shortColumnConverter = new ShortColumnConverter(); + columnType_columnConverter_map.put(short.class.getName(), shortColumnConverter); + columnType_columnConverter_map.put(Short.class.getName(), shortColumnConverter); + + SqlDateColumnConverter sqlDateColumnConverter = new SqlDateColumnConverter(); + columnType_columnConverter_map.put(java.sql.Date.class.getName(), sqlDateColumnConverter); + + StringColumnConverter stringColumnConverter = new StringColumnConverter(); + columnType_columnConverter_map.put(String.class.getName(), stringColumnConverter); + } +} diff --git a/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java b/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java new file mode 100644 index 0000000..3bc1dfa --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/DateColumnConverter.java @@ -0,0 +1,30 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +import java.util.Date; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class DateColumnConverter implements ColumnConverter { + @Override + public Date getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : new Date(cursor.getLong(index)); + } + + @Override + public Object fieldValue2DbValue(Date fieldValue) { + if (fieldValue == null) return null; + return fieldValue.getTime(); + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java b/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java new file mode 100644 index 0000000..13e5434 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/DoubleColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class DoubleColumnConverter implements ColumnConverter { + @Override + public Double getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getDouble(index); + } + + @Override + public Object fieldValue2DbValue(Double fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.REAL; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java b/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java new file mode 100644 index 0000000..884c4b8 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/FloatColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class FloatColumnConverter implements ColumnConverter { + @Override + public Float getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getFloat(index); + } + + @Override + public Object fieldValue2DbValue(Float fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.REAL; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java b/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java new file mode 100644 index 0000000..c3d684b --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/IntegerColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class IntegerColumnConverter implements ColumnConverter { + @Override + public Integer getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getInt(index); + } + + @Override + public Object fieldValue2DbValue(Integer fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java b/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java new file mode 100644 index 0000000..a4a760e --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/LongColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class LongColumnConverter implements ColumnConverter { + @Override + public Long getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getLong(index); + } + + @Override + public Object fieldValue2DbValue(Long fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java b/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java new file mode 100644 index 0000000..86bf131 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/ShortColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class ShortColumnConverter implements ColumnConverter { + @Override + public Short getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getShort(index); + } + + @Override + public Object fieldValue2DbValue(Short fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java b/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java new file mode 100644 index 0000000..b000674 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/SqlDateColumnConverter.java @@ -0,0 +1,28 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class SqlDateColumnConverter implements ColumnConverter { + @Override + public java.sql.Date getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : new java.sql.Date(cursor.getLong(index)); + } + + @Override + public Object fieldValue2DbValue(java.sql.Date fieldValue) { + if (fieldValue == null) return null; + return fieldValue.getTime(); + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.INTEGER; + } +} diff --git a/app/src/main/java/org/xutils/db/converter/StringColumnConverter.java b/app/src/main/java/org/xutils/db/converter/StringColumnConverter.java new file mode 100644 index 0000000..d3850c6 --- /dev/null +++ b/app/src/main/java/org/xutils/db/converter/StringColumnConverter.java @@ -0,0 +1,27 @@ +package org.xutils.db.converter; + +import android.database.Cursor; + +import org.xutils.db.sqlite.ColumnDbType; + +/** + * Author: wyouflf + * Date: 13-11-4 + * Time: 下午10:51 + */ +public class StringColumnConverter implements ColumnConverter { + @Override + public String getFieldValue(final Cursor cursor, int index) { + return cursor.isNull(index) ? null : cursor.getString(index); + } + + @Override + public Object fieldValue2DbValue(String fieldValue) { + return fieldValue; + } + + @Override + public ColumnDbType getColumnDbType() { + return ColumnDbType.TEXT; + } +} diff --git a/app/src/main/java/org/xutils/db/sqlite/ColumnDbType.java b/app/src/main/java/org/xutils/db/sqlite/ColumnDbType.java new file mode 100644 index 0000000..4bb8998 --- /dev/null +++ b/app/src/main/java/org/xutils/db/sqlite/ColumnDbType.java @@ -0,0 +1,20 @@ +package org.xutils.db.sqlite; + +/** + * Created by wyouflf on 14-2-20. + */ +public enum ColumnDbType { + + INTEGER("INTEGER"), REAL("REAL"), TEXT("TEXT"), BLOB("BLOB"); + + private String value; + + ColumnDbType(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/app/src/main/java/org/xutils/db/sqlite/SqlInfo.java b/app/src/main/java/org/xutils/db/sqlite/SqlInfo.java new file mode 100644 index 0000000..ce18aa6 --- /dev/null +++ b/app/src/main/java/org/xutils/db/sqlite/SqlInfo.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.sqlite; + +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; + +import org.xutils.common.util.KeyValue; +import org.xutils.db.converter.ColumnConverter; +import org.xutils.db.converter.ColumnConverterFactory; +import org.xutils.db.table.ColumnUtils; + +import java.util.ArrayList; +import java.util.List; + +public final class SqlInfo { + + private String sql; + private List bindArgs; + + public SqlInfo() { + } + + public SqlInfo(String sql) { + this.sql = sql; + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + public void addBindArg(KeyValue kv) { + if (bindArgs == null) { + bindArgs = new ArrayList(); + } + bindArgs.add(kv); + } + + public void addBindArgs(List bindArgs) { + if (this.bindArgs == null) { + this.bindArgs = bindArgs; + } else { + this.bindArgs.addAll(bindArgs); + } + } + + @SuppressWarnings("unchecked") + public SQLiteStatement buildStatement(SQLiteDatabase database) { + SQLiteStatement result = database.compileStatement(sql); + if (bindArgs != null) { + for (int i = 1; i < bindArgs.size() + 1; i++) { + KeyValue kv = bindArgs.get(i - 1); + if (kv.value == null) { + result.bindNull(i); + continue; + } + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(kv.value.getClass()); + Object value = converter.fieldValue2DbValue(kv.value); + ColumnDbType type = converter.getColumnDbType(); + switch (type) { + case INTEGER: + result.bindLong(i, ((Number) value).longValue()); + break; + case REAL: + result.bindDouble(i, ((Number) value).doubleValue()); + break; + case TEXT: + result.bindString(i, value.toString()); + break; + case BLOB: + result.bindBlob(i, (byte[]) value); + break; + default: + result.bindNull(i); + break; + } // end switch + } + } + return result; + } + + public Object[] getBindArgs() { + Object[] result = null; + if (bindArgs != null) { + result = new Object[bindArgs.size()]; + for (int i = 0; i < bindArgs.size(); i++) { + result[i] = ColumnUtils.convert2DbValueIfNeeded(bindArgs.get(i).value); + } + } + return result; + } + + public String[] getBindArgsAsStrArray() { + String[] result = null; + if (bindArgs != null) { + result = new String[bindArgs.size()]; + for (int i = 0; i < bindArgs.size(); i++) { + Object value = ColumnUtils.convert2DbValueIfNeeded(bindArgs.get(i).value); + result[i] = value == null ? null : value.toString(); + } + } + return result; + } +} diff --git a/app/src/main/java/org/xutils/db/sqlite/SqlInfoBuilder.java b/app/src/main/java/org/xutils/db/sqlite/SqlInfoBuilder.java new file mode 100644 index 0000000..f16436d --- /dev/null +++ b/app/src/main/java/org/xutils/db/sqlite/SqlInfoBuilder.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.sqlite; + +import org.xutils.common.util.KeyValue; +import org.xutils.db.table.ColumnEntity; +import org.xutils.db.table.TableEntity; +import org.xutils.ex.DbException; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Build "insert", "replace",,"update", "delete" and "create" sql. + */ +public final class SqlInfoBuilder { + + private static final ConcurrentHashMap, String> INSERT_SQL_CACHE = new ConcurrentHashMap, String>(); + private static final ConcurrentHashMap, String> REPLACE_SQL_CACHE = new ConcurrentHashMap, String>(); + + private SqlInfoBuilder() { + } + + //*********************************************** insert sql *********************************************** + + public static SqlInfo buildInsertSqlInfo(TableEntity table, Object entity) throws DbException { + + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) return null; + + SqlInfo result = new SqlInfo(); + String sql = INSERT_SQL_CACHE.get(table); + if (sql == null) { + StringBuilder builder = new StringBuilder(); + builder.append("INSERT INTO "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ("); + for (KeyValue kv : keyValueList) { + builder.append("\"").append(kv.key).append("\"").append(','); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(") VALUES ("); + + int length = keyValueList.size(); + for (int i = 0; i < length; i++) { + builder.append("?,"); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(")"); + + sql = builder.toString(); + result.setSql(sql); + result.addBindArgs(keyValueList); + INSERT_SQL_CACHE.put(table, sql); + } else { + result.setSql(sql); + result.addBindArgs(keyValueList); + } + + return result; + } + + //*********************************************** replace sql *********************************************** + + public static SqlInfo buildReplaceSqlInfo(TableEntity table, Object entity) throws DbException { + + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) return null; + + SqlInfo result = new SqlInfo(); + String sql = REPLACE_SQL_CACHE.get(table); + if (sql == null) { + StringBuilder builder = new StringBuilder(); + builder.append("REPLACE INTO "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ("); + for (KeyValue kv : keyValueList) { + builder.append("\"").append(kv.key).append("\"").append(','); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(") VALUES ("); + + int length = keyValueList.size(); + for (int i = 0; i < length; i++) { + builder.append("?,"); + } + builder.deleteCharAt(builder.length() - 1); + builder.append(")"); + + sql = builder.toString(); + result.setSql(sql); + result.addBindArgs(keyValueList); + REPLACE_SQL_CACHE.put(table, sql); + } else { + result.setSql(sql); + result.addBindArgs(keyValueList); + } + + return result; + } + + //*********************************************** delete sql *********************************************** + + public static SqlInfo buildDeleteSqlInfo(TableEntity table, Object entity) throws DbException { + SqlInfo result = new SqlInfo(); + + ColumnEntity id = table.getId(); + Object idValue = id.getColumnValue(entity); + + if (idValue == null) { + throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null"); + } + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + + return result; + } + + public static SqlInfo buildDeleteSqlInfoById(TableEntity table, Object idValue) throws DbException { + SqlInfo result = new SqlInfo(); + + ColumnEntity id = table.getId(); + + if (idValue == null) { + throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null"); + } + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + + return result; + } + + public static SqlInfo buildDeleteSqlInfo(TableEntity table, WhereBuilder whereBuilder) throws DbException { + StringBuilder builder = new StringBuilder("DELETE FROM "); + builder.append("\"").append(table.getName()).append("\""); + + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + builder.append(" WHERE ").append(whereBuilder.toString()); + } + + return new SqlInfo(builder.toString()); + } + + //*********************************************** update sql *********************************************** + + public static SqlInfo buildUpdateSqlInfo(TableEntity table, Object entity, String... updateColumnNames) throws DbException { + + List keyValueList = entity2KeyValueList(table, entity); + if (keyValueList.size() == 0) return null; + + HashSet updateColumnNameSet = null; + if (updateColumnNames != null && updateColumnNames.length > 0) { + updateColumnNameSet = new HashSet(updateColumnNames.length); + Collections.addAll(updateColumnNameSet, updateColumnNames); + } + + ColumnEntity id = table.getId(); + Object idValue = id.getColumnValue(entity); + + if (idValue == null) { + throw new DbException("this entity[" + table.getEntityType() + "]'s id value is null"); + } + + SqlInfo result = new SqlInfo(); + StringBuilder builder = new StringBuilder("UPDATE "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" SET "); + for (KeyValue kv : keyValueList) { + if (updateColumnNameSet == null || updateColumnNameSet.contains(kv.key)) { + builder.append("\"").append(kv.key).append("\"").append("=?,"); + result.addBindArg(kv); + } + } + builder.deleteCharAt(builder.length() - 1); + builder.append(" WHERE ").append(WhereBuilder.b(id.getName(), "=", idValue)); + + result.setSql(builder.toString()); + return result; + } + + public static SqlInfo buildUpdateSqlInfo(TableEntity table, WhereBuilder whereBuilder, KeyValue... nameValuePairs) throws DbException { + + if (nameValuePairs == null || nameValuePairs.length == 0) return null; + + SqlInfo result = new SqlInfo(); + StringBuilder builder = new StringBuilder("UPDATE "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" SET "); + for (KeyValue kv : nameValuePairs) { + builder.append("\"").append(kv.key).append("\"").append("=?,"); + result.addBindArg(kv); + } + builder.deleteCharAt(builder.length() - 1); + if (whereBuilder != null && whereBuilder.getWhereItemSize() > 0) { + builder.append(" WHERE ").append(whereBuilder.toString()); + } + + result.setSql(builder.toString()); + return result; + } + + //*********************************************** others *********************************************** + + public static SqlInfo buildCreateTableSqlInfo(TableEntity table) throws DbException { + ColumnEntity id = table.getId(); + + StringBuilder builder = new StringBuilder(); + builder.append("CREATE TABLE IF NOT EXISTS "); + builder.append("\"").append(table.getName()).append("\""); + builder.append(" ( "); + + if (id.isAutoId()) { + builder.append("\"").append(id.getName()).append("\"").append(" INTEGER PRIMARY KEY AUTOINCREMENT, "); + } else { + builder.append("\"").append(id.getName()).append("\"").append(id.getColumnDbType()).append(" PRIMARY KEY, "); + } + + Collection columns = table.getColumnMap().values(); + for (ColumnEntity column : columns) { + if (column.isId()) continue; + builder.append("\"").append(column.getName()).append("\""); + builder.append(' ').append(column.getColumnDbType()); + builder.append(' ').append(column.getProperty()); + builder.append(','); + } + + builder.deleteCharAt(builder.length() - 1); + builder.append(" )"); + return new SqlInfo(builder.toString()); + } + + public static List entity2KeyValueList(TableEntity table, Object entity) { + + Collection columns = table.getColumnMap().values(); + List keyValueList = new ArrayList(columns.size()); + for (ColumnEntity column : columns) { + KeyValue kv = column2KeyValue(entity, column); + if (kv != null) { + keyValueList.add(kv); + } + } + + return keyValueList; + } + + private static KeyValue column2KeyValue(Object entity, ColumnEntity column) { + if (column.isAutoId()) { + return null; + } + + String key = column.getName(); + Object value = column.getFieldValue(entity); + return new KeyValue(key, value); + } +} diff --git a/app/src/main/java/org/xutils/db/sqlite/WhereBuilder.java b/app/src/main/java/org/xutils/db/sqlite/WhereBuilder.java new file mode 100644 index 0000000..52957f4 --- /dev/null +++ b/app/src/main/java/org/xutils/db/sqlite/WhereBuilder.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.sqlite; + +import android.text.TextUtils; + +import org.xutils.db.table.ColumnUtils; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Author: wyouflf + * Date: 13-7-29 + * Time: 上午9:35 + */ +public class WhereBuilder { + + private final List whereItems; + + private WhereBuilder() { + this.whereItems = new ArrayList(); + } + + /** + * create new instance + */ + public static WhereBuilder b() { + return new WhereBuilder(); + } + + /** + * create new instance + * + * @param op operator: "=","LIKE","IN","BETWEEN"... + */ + public static WhereBuilder b(String columnName, String op, Object value) { + WhereBuilder result = new WhereBuilder(); + result.appendCondition(null, columnName, op, value); + return result; + } + + /** + * add AND condition + * + * @param op operator: "=","LIKE","IN","BETWEEN"... + */ + public WhereBuilder and(String columnName, String op, Object value) { + appendCondition(whereItems.size() == 0 ? null : "AND", columnName, op, value); + return this; + } + + /** + * add AND condition + * + * @param where expr("[AND] (" + where.toString() + ")") + */ + public WhereBuilder and(WhereBuilder where) { + String condition = whereItems.size() == 0 ? " " : "AND "; + return expr(condition + "(" + where.toString() + ")"); + } + + /** + * add OR condition + * + * @param op operator: "=","LIKE","IN","BETWEEN"... + */ + public WhereBuilder or(String columnName, String op, Object value) { + appendCondition(whereItems.size() == 0 ? null : "OR", columnName, op, value); + return this; + } + + /** + * add OR condition + * + * @param where expr("[OR] (" + where.toString() + ")") + */ + public WhereBuilder or(WhereBuilder where) { + String condition = whereItems.size() == 0 ? " " : "OR "; + return expr(condition + "(" + where.toString() + ")"); + } + + public WhereBuilder expr(String expr) { + whereItems.add(" " + expr); + return this; + } + + public int getWhereItemSize() { + return whereItems.size(); + } + + @Override + public String toString() { + if (whereItems.size() == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (String item : whereItems) { + sb.append(item); + } + return sb.toString(); + } + + private void appendCondition(String conj, String columnName, String op, Object value) { + StringBuilder builder = new StringBuilder(); + + if (whereItems.size() > 0) { + builder.append(" "); + } + + // append conj + if (!TextUtils.isEmpty(conj)) { + builder.append(conj).append(" "); + } + + // append columnName + builder.append("\"").append(columnName).append("\""); + + // convert op + if ("!=".equals(op)) { + op = "<>"; + } else if ("==".equals(op)) { + op = "="; + } + + // append op & value + if (value == null) { + if ("=".equals(op)) { + builder.append(" IS NULL"); + } else if ("<>".equals(op)) { + builder.append(" IS NOT NULL"); + } else { + builder.append(" ").append(op).append(" NULL"); + } + } else { + builder.append(" ").append(op).append(" "); + + if ("IN".equalsIgnoreCase(op)) { + Iterable items = null; + if (value instanceof Iterable) { + items = (Iterable) value; + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + List arrayList = new ArrayList(len); + for (int i = 0; i < len; i++) { + arrayList.add(Array.get(value, i)); + } + items = arrayList; + } + if (items != null) { + StringBuilder inSb = new StringBuilder("("); + for (Object item : items) { + Object itemColValue = ColumnUtils.convert2DbValueIfNeeded(item); + if (ColumnUtils.isTextColumnDbType(itemColValue)) { + String valueStr = ColumnUtils.convert2SafeExpr(itemColValue); + inSb.append("'").append(valueStr).append("'"); + } else { + inSb.append(itemColValue); + } + inSb.append(","); + } + if (inSb.length() > 1) { + inSb.deleteCharAt(inSb.length() - 1); + } + inSb.append(")"); + builder.append(inSb.toString()); + } else { + throw new IllegalArgumentException("value must be an Array or an Iterable."); + } + } else if ("BETWEEN".equalsIgnoreCase(op)) { + Iterable items = null; + if (value instanceof Iterable) { + items = (Iterable) value; + } else if (value.getClass().isArray()) { + int len = Array.getLength(value); + List arrayList = new ArrayList(len); + for (int i = 0; i < len; i++) { + arrayList.add(Array.get(value, i)); + } + items = arrayList; + } + if (items != null) { + Iterator iterator = items.iterator(); + if (!iterator.hasNext()) + throw new IllegalArgumentException("value must contains tow items."); + Object start = iterator.next(); + if (!iterator.hasNext()) + throw new IllegalArgumentException("value must contains tow items."); + Object end = iterator.next(); + + Object startColValue = ColumnUtils.convert2DbValueIfNeeded(start); + Object endColValue = ColumnUtils.convert2DbValueIfNeeded(end); + + if (ColumnUtils.isTextColumnDbType(startColValue)) { + String startStr = ColumnUtils.convert2SafeExpr(startColValue); + String endStr = ColumnUtils.convert2SafeExpr(endColValue); + builder.append("'").append(startStr).append("'"); + builder.append(" AND "); + builder.append("'").append(endStr).append("'"); + } else { + builder.append(startColValue); + builder.append(" AND "); + builder.append(endColValue); + } + } else { + throw new IllegalArgumentException("value must be an Array or an Iterable."); + } + } else { + value = ColumnUtils.convert2DbValueIfNeeded(value); + if (ColumnUtils.isTextColumnDbType(value)) { + String valueStr = ColumnUtils.convert2SafeExpr(value); + builder.append("'").append(valueStr).append("'"); + } else { + builder.append(value); + } + } + } + whereItems.add(builder.toString()); + } +} diff --git a/app/src/main/java/org/xutils/db/table/ColumnEntity.java b/app/src/main/java/org/xutils/db/table/ColumnEntity.java new file mode 100644 index 0000000..12ea257 --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/ColumnEntity.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.table; + +import android.database.Cursor; + +import org.xutils.common.util.LogUtil; +import org.xutils.db.annotation.Column; +import org.xutils.db.converter.ColumnConverter; +import org.xutils.db.converter.ColumnConverterFactory; +import org.xutils.db.sqlite.ColumnDbType; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class ColumnEntity { + + protected final String name; + private final String property; + private final boolean isId; + private final boolean isAutoId; + + protected final Method getMethod; + protected final Method setMethod; + + protected final Field columnField; + protected final ColumnConverter columnConverter; + + /* package */ ColumnEntity(Class entityType, Field field, Column column) { + field.setAccessible(true); + + this.columnField = field; + this.name = column.name(); + this.property = column.property(); + this.isId = column.isId(); + + Class fieldType = field.getType(); + this.isAutoId = this.isId && column.autoGen() && ColumnUtils.isAutoIdType(fieldType); + this.columnConverter = ColumnConverterFactory.getColumnConverter(fieldType); + + + this.getMethod = ColumnUtils.findGetMethod(entityType, field); + if (this.getMethod != null && !this.getMethod.isAccessible()) { + this.getMethod.setAccessible(true); + } + this.setMethod = ColumnUtils.findSetMethod(entityType, field); + if (this.setMethod != null && !this.setMethod.isAccessible()) { + this.setMethod.setAccessible(true); + } + } + + public void setValueFromCursor(Object entity, Cursor cursor, int index) { + Object value = columnConverter.getFieldValue(cursor, index); + if (value == null) return; + + if (setMethod != null) { + try { + setMethod.invoke(entity, value); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + this.columnField.set(entity, value); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + + @SuppressWarnings("unchecked") + public Object getColumnValue(Object entity) { + Object fieldValue = getFieldValue(entity); + if (this.isAutoId && (fieldValue.equals(0L) || fieldValue.equals(0))) { + return null; + } + return columnConverter.fieldValue2DbValue(fieldValue); + } + + public void setAutoIdValue(Object entity, long value) { + Object idValue = value; + if (ColumnUtils.isInteger(columnField.getType())) { + idValue = (int) value; + } + + if (setMethod != null) { + try { + setMethod.invoke(entity, idValue); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + this.columnField.set(entity, idValue); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + + public Object getFieldValue(Object entity) { + Object fieldValue = null; + if (entity != null) { + if (getMethod != null) { + try { + fieldValue = getMethod.invoke(entity); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } else { + try { + fieldValue = this.columnField.get(entity); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + } + return fieldValue; + } + + public String getName() { + return name; + } + + public String getProperty() { + return property; + } + + public boolean isId() { + return isId; + } + + public boolean isAutoId() { + return isAutoId; + } + + public Field getColumnField() { + return columnField; + } + + public ColumnConverter getColumnConverter() { + return columnConverter; + } + + public ColumnDbType getColumnDbType() { + return columnConverter.getColumnDbType(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/app/src/main/java/org/xutils/db/table/ColumnUtils.java b/app/src/main/java/org/xutils/db/table/ColumnUtils.java new file mode 100644 index 0000000..c82a6a2 --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/ColumnUtils.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.table; + +import org.xutils.common.util.LogUtil; +import org.xutils.db.converter.ColumnConverter; +import org.xutils.db.converter.ColumnConverterFactory; +import org.xutils.db.sqlite.ColumnDbType; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashSet; + +public final class ColumnUtils { + + private ColumnUtils() { + } + + private static final HashSet> BOOLEAN_TYPES = new HashSet>(2); + private static final HashSet> INTEGER_TYPES = new HashSet>(2); + private static final HashSet> AUTO_INCREMENT_TYPES = new HashSet>(4); + + static { + BOOLEAN_TYPES.add(boolean.class); + BOOLEAN_TYPES.add(Boolean.class); + + INTEGER_TYPES.add(int.class); + INTEGER_TYPES.add(Integer.class); + + AUTO_INCREMENT_TYPES.addAll(INTEGER_TYPES); + AUTO_INCREMENT_TYPES.add(long.class); + AUTO_INCREMENT_TYPES.add(Long.class); + } + + public static boolean isAutoIdType(Class fieldType) { + return AUTO_INCREMENT_TYPES.contains(fieldType); + } + + public static boolean isInteger(Class fieldType) { + return INTEGER_TYPES.contains(fieldType); + } + + public static boolean isBoolean(Class fieldType) { + return BOOLEAN_TYPES.contains(fieldType); + } + + public static boolean isTextColumnDbType(Object value) { + if (value == null) return false; + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(value.getClass()); + return converter != null && ColumnDbType.TEXT.equals(converter.getColumnDbType()); + } + + public static String convert2SafeExpr(Object value) { + String result = String.valueOf(value); + if (result.indexOf('\'') != -1) { + result = result.replace("'", "''"); + } + return result; + } + + @SuppressWarnings("unchecked") + public static Object convert2DbValueIfNeeded(final Object value) { + Object result = value; + if (value != null) { + Class valueType = value.getClass(); + ColumnConverter converter = ColumnConverterFactory.getColumnConverter(valueType); + result = converter.fieldValue2DbValue(value); + } + return result; + } + + /* package */ + static Method findGetMethod(Class entityType, Field field) { + if (Object.class.equals(entityType)) return null; + + String fieldName = field.getName(); + Method getMethod = null; + if (isBoolean(field.getType())) { + getMethod = findBooleanGetMethod(entityType, fieldName); + } + if (getMethod == null) { + String methodName = "get" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + try { + getMethod = entityType.getDeclaredMethod(methodName); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + } + + if (getMethod == null) { + return findGetMethod(entityType.getSuperclass(), field); + } + return getMethod; + } + + /* package */ + static Method findSetMethod(Class entityType, Field field) { + if (Object.class.equals(entityType)) return null; + + String fieldName = field.getName(); + Class fieldType = field.getType(); + Method setMethod = null; + if (isBoolean(fieldType)) { + setMethod = findBooleanSetMethod(entityType, fieldName, fieldType); + } + if (setMethod == null) { + String methodName = "set" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + try { + setMethod = entityType.getDeclaredMethod(methodName, fieldType); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + } + + if (setMethod == null) { + return findSetMethod(entityType.getSuperclass(), field); + } + return setMethod; + } + + private static Method findBooleanGetMethod(Class entityType, final String fieldName) { + String methodName = null; + if (fieldName.startsWith("is")) { + methodName = fieldName; + } else { + methodName = "is" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + } + try { + return entityType.getDeclaredMethod(methodName); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + return null; + } + + private static Method findBooleanSetMethod(Class entityType, final String fieldName, Class fieldType) { + String methodName = null; + if (fieldName.startsWith("is") && fieldName.length() > 2) { + methodName = "set" + fieldName.substring(2, 3).toUpperCase(); + if (fieldName.length() > 3) { + methodName += fieldName.substring(3); + } + } else { + methodName = "set" + fieldName.substring(0, 1).toUpperCase(); + if (fieldName.length() > 1) { + methodName += fieldName.substring(1); + } + } + try { + return entityType.getDeclaredMethod(methodName, fieldType); + } catch (NoSuchMethodException e) { + LogUtil.d(entityType.getName() + "#" + methodName + " not exist"); + } + return null; + } + +} diff --git a/app/src/main/java/org/xutils/db/table/DbBase.java b/app/src/main/java/org/xutils/db/table/DbBase.java new file mode 100644 index 0000000..7b8f855 --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/DbBase.java @@ -0,0 +1,99 @@ +package org.xutils.db.table; + +import android.database.Cursor; + +import org.xutils.DbManager; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.ex.DbException; + +import java.util.HashMap; + +/** + * DbManager基类, 包含表结构的基本操作. + * Created by wyouflf on 16/1/22. + */ +public abstract class DbBase implements DbManager { + + private final HashMap, TableEntity> tableMap = new HashMap, TableEntity>(); + + @Override + @SuppressWarnings("unchecked") + public TableEntity getTable(Class entityType) throws DbException { + synchronized (tableMap) { + TableEntity table = (TableEntity) tableMap.get(entityType); + if (table == null) { + try { + table = new TableEntity(this, entityType); + } catch (DbException ex) { + throw ex; + } catch (Throwable ex) { + throw new DbException(ex); + } + tableMap.put(entityType, table); + } + + return table; + } + } + + @Override + public void dropTable(Class entityType) throws DbException { + TableEntity table = this.getTable(entityType); + if (!table.tableIsExists()) return; + execNonQuery("DROP TABLE \"" + table.getName() + "\""); + table.setTableCheckedStatus(false); + this.removeTable(entityType); + } + + @Override + public void dropDb() throws DbException { + Cursor cursor = execQuery("SELECT name FROM sqlite_master WHERE type='table' AND name<>'sqlite_sequence'"); + if (cursor != null) { + try { + while (cursor.moveToNext()) { + try { + String tableName = cursor.getString(0); + execNonQuery("DROP TABLE " + tableName); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } + + synchronized (tableMap) { + for (TableEntity table : tableMap.values()) { + table.setTableCheckedStatus(false); + } + tableMap.clear(); + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + } + + @Override + public void addColumn(Class entityType, String column) throws DbException { + TableEntity table = this.getTable(entityType); + ColumnEntity col = table.getColumnMap().get(column); + if (col != null) { + if (!table.tableIsExists()) return; // 不需要添加, 表创建时会自动添加 + StringBuilder builder = new StringBuilder(); + builder.append("ALTER TABLE ").append("\"").append(table.getName()).append("\""). + append(" ADD COLUMN ").append("\"").append(col.getName()).append("\""). + append(" ").append(col.getColumnDbType()). + append(" ").append(col.getProperty()); + execNonQuery(builder.toString()); + } else { + throw new DbException("the column(" + column + ") is not defined in table: " + table.getName()); + } + } + + protected void removeTable(Class entityType) { + synchronized (tableMap) { + tableMap.remove(entityType); + } + } +} diff --git a/app/src/main/java/org/xutils/db/table/DbModel.java b/app/src/main/java/org/xutils/db/table/DbModel.java new file mode 100644 index 0000000..714f42b --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/DbModel.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.table; + +import android.text.TextUtils; + +import java.util.Date; +import java.util.HashMap; + +public final class DbModel { + + /** + * key: columnName + * value: valueStr + */ + private final HashMap dataMap = new HashMap(); + + public String getString(String columnName) { + return dataMap.get(columnName); + } + + public int getInt(String columnName, int defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Integer.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + public boolean getBoolean(String columnName) { + String value = dataMap.get(columnName); + if (value != null) { + return value.length() == 1 ? "1".equals(value) : Boolean.valueOf(value); + } + return false; + } + + public double getDouble(String columnName, double defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Double.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + public float getFloat(String columnName, float defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Float.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + public long getLong(String columnName, long defaultValue) { + String value = dataMap.get(columnName); + if (TextUtils.isEmpty(value)) { + return defaultValue; + } else { + try { + return Long.valueOf(value); + } catch (Throwable ex) { + return defaultValue; + } + } + } + + public Date getDate(String columnName, long defaultTime) { + return new Date(getLong(columnName, defaultTime)); + } + + public java.sql.Date getSqlDate(String columnName, long defaultTime) { + return new java.sql.Date(getLong(columnName, defaultTime)); + } + + public void add(String columnName, String valueStr) { + dataMap.put(columnName, valueStr); + } + + /** + * @return key: columnName + */ + public HashMap getDataMap() { + return dataMap; + } + + /** + * 列数据是否空 + */ + public boolean isEmpty(String columnName) { + return TextUtils.isEmpty(dataMap.get(columnName)); + } +} diff --git a/app/src/main/java/org/xutils/db/table/TableEntity.java b/app/src/main/java/org/xutils/db/table/TableEntity.java new file mode 100644 index 0000000..5814650 --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/TableEntity.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.table; + +import android.database.Cursor; +import android.text.TextUtils; + +import org.xutils.DbManager; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.db.annotation.Table; +import org.xutils.db.sqlite.SqlInfo; +import org.xutils.db.sqlite.SqlInfoBuilder; +import org.xutils.ex.DbException; + +import java.lang.reflect.Constructor; +import java.util.LinkedHashMap; + + +public final class TableEntity { + + private final DbManager db; + private final String name; + private final String onCreated; + private final Class entityType; + private final Constructor constructor; + private ColumnEntity id; + private volatile Boolean tableCheckedStatus; + + /** + * key: columnName + */ + private final LinkedHashMap columnMap; + + /*package*/ TableEntity(DbManager db, Class entityType) throws Throwable { + this.db = db; + this.entityType = entityType; + + Table table = entityType.getAnnotation(Table.class); + if (table == null) { + throw new DbException("missing @Table on " + entityType.getName()); + } + this.name = table.name(); + this.onCreated = table.onCreated(); + + try { + this.constructor = entityType.getConstructor(); + this.constructor.setAccessible(true); + } catch (Throwable ex) { + throw new DbException("missing no-argument constructor for the table: " + this.name); + } + + this.columnMap = TableUtils.findColumnMap(entityType); + for (ColumnEntity column : columnMap.values()) { + if (column.isId()) { + this.id = column; + break; + } + } + } + + public T createEntity() throws Throwable { + return this.constructor.newInstance(); + } + + public boolean tableIsExists() throws DbException { + return tableIsExists(false); + } + + public boolean tableIsExists(boolean forceCheckFromDb) throws DbException { + if (tableCheckedStatus != null && (tableCheckedStatus || !forceCheckFromDb)) { + return tableCheckedStatus; + } + + Cursor cursor = db.execQuery("SELECT COUNT(*) AS c FROM sqlite_master WHERE type='table' AND name='" + name + "'"); + if (cursor != null) { + try { + if (cursor.moveToNext()) { + int count = cursor.getInt(0); + if (count > 0) { + tableCheckedStatus = true; + return tableCheckedStatus; + } + } + } catch (Throwable e) { + throw new DbException(e); + } finally { + IOUtil.closeQuietly(cursor); + } + } + + tableCheckedStatus = false; + return tableCheckedStatus; + } + + public void createTableIfNotExists() throws DbException { + if (tableCheckedStatus != null && tableCheckedStatus) return; + synchronized (entityType) { + if (!this.tableIsExists(true)) { + SqlInfo sqlInfo = SqlInfoBuilder.buildCreateTableSqlInfo(this); + db.execNonQuery(sqlInfo); + tableCheckedStatus = true; + + if (!TextUtils.isEmpty(onCreated)) { + db.execNonQuery(onCreated); + } + + DbManager.TableCreateListener listener = db.getDaoConfig().getTableCreateListener(); + if (listener != null) { + try { + listener.onTableCreated(db, this); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } + + public DbManager getDb() { + return db; + } + + public String getName() { + return name; + } + + public Class getEntityType() { + return entityType; + } + + public String getOnCreated() { + return onCreated; + } + + public ColumnEntity getId() { + return id; + } + + public LinkedHashMap getColumnMap() { + return columnMap; + } + + /*package*/ void setTableCheckedStatus(boolean tableCheckedStatus) { + this.tableCheckedStatus = tableCheckedStatus; + } + + @Override + public String toString() { + return name; + } +} diff --git a/app/src/main/java/org/xutils/db/table/TableUtils.java b/app/src/main/java/org/xutils/db/table/TableUtils.java new file mode 100644 index 0000000..3fae37b --- /dev/null +++ b/app/src/main/java/org/xutils/db/table/TableUtils.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.db.table; + +import org.xutils.common.util.LogUtil; +import org.xutils.db.annotation.Column; +import org.xutils.db.converter.ColumnConverterFactory; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.LinkedHashMap; + +/* package */ final class TableUtils { + + private TableUtils() { + } + + /* package */ + static LinkedHashMap findColumnMap(Class entityType) { + LinkedHashMap columnMap = new LinkedHashMap(); + addColumns2Map(entityType, columnMap); + return columnMap; + } + + private static void addColumns2Map(Class entityType, HashMap columnMap) { + if (Object.class.equals(entityType)) return; + + try { + Field[] fields = entityType.getDeclaredFields(); + for (Field field : fields) { + int modify = field.getModifiers(); + if (Modifier.isStatic(modify) || Modifier.isTransient(modify)) { + continue; + } + Column columnAnn = field.getAnnotation(Column.class); + if (columnAnn != null) { + if (ColumnConverterFactory.isSupportColumnConverter(field.getType())) { + ColumnEntity column = new ColumnEntity(entityType, field, columnAnn); + if (!columnMap.containsKey(column.getName())) { + columnMap.put(column.getName(), column); + } + } + } + } + + addColumns2Map(entityType.getSuperclass(), columnMap); + } catch (Throwable e) { + LogUtil.e(e.getMessage(), e); + } + } +} diff --git a/app/src/main/java/org/xutils/ex/BaseException.java b/app/src/main/java/org/xutils/ex/BaseException.java new file mode 100644 index 0000000..062ae71 --- /dev/null +++ b/app/src/main/java/org/xutils/ex/BaseException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.ex; + +import java.io.IOException; + +/** + * Author: wyouflf + * Date: 13-7-24 + * Time: 下午3:00 + */ +public class BaseException extends IOException { + private static final long serialVersionUID = 1L; + + public BaseException() { + super(); + } + + public BaseException(String detailMessage) { + super(detailMessage); + } + + public BaseException(String detailMessage, Throwable throwable) { + super(detailMessage); + this.initCause(throwable); + } + + public BaseException(Throwable throwable) { + super(throwable.getMessage()); + this.initCause(throwable); + } +} diff --git a/app/src/main/java/org/xutils/ex/DbException.java b/app/src/main/java/org/xutils/ex/DbException.java new file mode 100644 index 0000000..7a49641 --- /dev/null +++ b/app/src/main/java/org/xutils/ex/DbException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.ex; + +public class DbException extends BaseException { + private static final long serialVersionUID = 1L; + + public DbException() { + } + + public DbException(String detailMessage) { + super(detailMessage); + } + + public DbException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); + } + + public DbException(Throwable throwable) { + super(throwable); + } +} diff --git a/app/src/main/java/org/xutils/ex/FileLockedException.java b/app/src/main/java/org/xutils/ex/FileLockedException.java new file mode 100644 index 0000000..3e353ec --- /dev/null +++ b/app/src/main/java/org/xutils/ex/FileLockedException.java @@ -0,0 +1,12 @@ +package org.xutils.ex; + +/** + * Created by wyouflf on 15/10/9. + */ +public class FileLockedException extends BaseException { + private static final long serialVersionUID = 1L; + + public FileLockedException(String detailMessage) { + super(detailMessage); + } +} diff --git a/app/src/main/java/org/xutils/ex/HttpException.java b/app/src/main/java/org/xutils/ex/HttpException.java new file mode 100644 index 0000000..8fb7759 --- /dev/null +++ b/app/src/main/java/org/xutils/ex/HttpException.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.ex; + +import android.text.TextUtils; + +public class HttpException extends BaseException { + private static final long serialVersionUID = 1L; + + private int code; + private String errorCode; + private String customMessage; + private String result; + + /** + * @param code The http response status code, 0 if the http request error and has no response. + * @param detailMessage The http response message. + */ + public HttpException(int code, String detailMessage) { + super(detailMessage); + this.code = code; + } + + public void setCode(int code) { + this.code = code; + } + + public void setMessage(String message) { + this.customMessage = message; + } + + /** + * @return The http response status code, 0 if the http request error and has no response. + */ + public int getCode() { + return code; + } + + public String getErrorCode() { + return errorCode == null ? String.valueOf(code) : errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + @Override + public String getMessage() { + if (!TextUtils.isEmpty(customMessage)) { + return customMessage; + } else { + return super.getMessage(); + } + } + + public String getResult() { + return result; + } + + public void setResult(String result) { + this.result = result; + } + + @Override + public String toString() { + return "errorCode: " + getErrorCode() + ", msg: " + getMessage() + ", result: " + result; + } +} diff --git a/app/src/main/java/org/xutils/ex/HttpRedirectException.java b/app/src/main/java/org/xutils/ex/HttpRedirectException.java new file mode 100644 index 0000000..de7c3d1 --- /dev/null +++ b/app/src/main/java/org/xutils/ex/HttpRedirectException.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.ex; + +public class HttpRedirectException extends HttpException { + private static final long serialVersionUID = 1L; + + public HttpRedirectException(int code, String detailMessage, String result) { + super(code, detailMessage); + this.setResult(result); + } +} diff --git a/app/src/main/java/org/xutils/http/BaseParams.java b/app/src/main/java/org/xutils/http/BaseParams.java new file mode 100644 index 0000000..4d64b1b --- /dev/null +++ b/app/src/main/java/org/xutils/http/BaseParams.java @@ -0,0 +1,524 @@ +package org.xutils.http; + +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xutils.common.util.KeyValue; +import org.xutils.common.util.LogUtil; +import org.xutils.http.body.FileBody; +import org.xutils.http.body.InputStreamBody; +import org.xutils.http.body.MultipartBody; +import org.xutils.http.body.RequestBody; +import org.xutils.http.body.StringBody; +import org.xutils.http.body.UrlEncodedBody; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 请求的基础参数 + * Created by wyouflf on 16/1/23. + */ +public abstract class BaseParams { + + private String charset = "UTF-8"; + private HttpMethod method; + private String bodyContent; + private String bodyContentType; + private boolean multipart = false; // 是否使用multipart表单 + private boolean asJsonContent = false; // 用json形式的bodyParams上传 + private boolean asJsonArrayContent = false; // 用json array形式的bodyParams上传 + private RequestBody requestBody; // 生成的表单 + + private final List
headers = new ArrayList
(); + private final List queryStringParams = new ArrayList(); + private final List bodyParams = new ArrayList(); + + public void setCharset(String charset) { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + + public String getCharset() { + return charset; + } + + public void setMethod(HttpMethod method) { + this.method = method; + } + + public HttpMethod getMethod() { + return method; + } + + public boolean isMultipart() { + return multipart; + } + + public void setMultipart(boolean multipart) { + this.multipart = multipart; + } + + /** + * 以json形式提交body参数 + */ + public boolean isAsJsonContent() { + return asJsonContent; + } + + /** + * 以json形式提交body参数 + */ + public void setAsJsonContent(boolean asJsonContent) { + this.asJsonContent = asJsonContent; + } + + + /** + * 以 json array 形式提交body参数 + */ + public boolean isAsJsonArrayContent() { + return asJsonArrayContent; + } + + /** + * 以 json array 形式提交body参数 + */ + public void setAsJsonArrayContent(boolean asJsonArrayContent) { + this.asJsonArrayContent = asJsonArrayContent; + } + + /** + * 覆盖header + * + * @param name 为空时不添加该参数 + */ + public void setHeader(String name, String value) { + if (TextUtils.isEmpty(name)) return; + Header header = new Header(name, value, true); + Iterator
it = headers.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + if (name.equals(kv.key)) { + it.remove(); + } + } + this.headers.add(header); + } + + /** + * 添加header + * + * @param name 为空时不添加该参数 + */ + public void addHeader(String name, String value) { + if (TextUtils.isEmpty(name)) return; + this.headers.add(new Header(name, value, false)); + } + + /** + * 添加请求参数(根据请求谓词, 将参数加入QueryString或Body.) + * + * @param name 参数名(单个File/InputStream/byte[]数据表单允许name为空) + * @param value 可以是String, File, InputStream 或 byte[] + */ + public void addParameter(String name, Object value) { + if (HttpMethod.permitsRequestBody(method)) { + addBodyParameter(name, value, null, null); + } else { + addQueryStringParameter(name, value); + } + } + + /** + * 添加参数至Query String + * + * @param name 参数名, 为空时不添加该参数 + * @param value 字符串值, 也可以是String集合或数组 + */ + public void addQueryStringParameter(String name, Object value) { + if (TextUtils.isEmpty(name)) return; + if (value instanceof Iterable) { + for (Object item : (Iterable) value) { + this.queryStringParams.add(new ArrayItem(name, item)); + } + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + int len = array.length(); + for (int i = 0; i < len; i++) { + this.queryStringParams.add(new ArrayItem(name, array.opt(i))); + } + } else if (value != null && value.getClass().isArray()) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + this.queryStringParams.add(new ArrayItem(name, Array.get(value, i))); + } + } else { + this.queryStringParams.add(new KeyValue(name, value)); + } + } + + /** + * 添加body参数 + * + * @param name 参数名(单个File/InputStream/byte[]数据表单允许name为空) + * @param value 可以是String, File, InputStream 或 byte[] + */ + public void addBodyParameter(String name, Object value) { + addBodyParameter(name, value, null, null); + } + + /** + * 添加body参数 + * + * @param name 参数名(单个File/InputStream/byte[]数据表单允许name为空) + * @param value 可以是String, File, InputStream 或 byte[] + * @param contentType 可为空 + */ + public void addBodyParameter(String name, Object value, String contentType) { + addBodyParameter(name, value, contentType, null); + } + + /** + * 添加body参数 + * + * @param name 参数名(单个File/InputStream/byte[]数据表单允许name为空) + * @param value 可以是String, File, InputStream 或 byte[] + * @param contentType 可为空 + * @param fileName 服务端看到的文件名 + */ + public void addBodyParameter(String name, Object value, String contentType, String fileName) { + if (TextUtils.isEmpty(name) && value == null) return; + if (TextUtils.isEmpty(contentType) && TextUtils.isEmpty(fileName)) { + if (value instanceof Iterable) { + for (Object item : (Iterable) value) { + this.bodyParams.add(new ArrayItem(name, item)); + } + } else if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + int len = array.length(); + for (int i = 0; i < len; i++) { + this.bodyParams.add(new ArrayItem(name, array.opt(i))); + } + } else if (value instanceof byte[]) { + this.bodyParams.add(new KeyValue(name, value)); + } else if (value != null && value.getClass().isArray()) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + this.bodyParams.add(new ArrayItem(name, Array.get(value, i))); + } + } else { + this.bodyParams.add(new KeyValue(name, value)); + } + } else { + this.bodyParams.add(new BodyItemWrapper(name, value, contentType, fileName)); + } + } + + public void setBodyContent(String content) { + this.bodyContent = content; + } + + public String getBodyContent() { + checkBodyParams(); + return bodyContent; + } + + /** + * 设置POST等请求的 Content-Type + * + * @param bodyContentType multipart表单仅设置subType(例如:"form-data"(默认) or "related"); + * kv结构自定义设置会被忽略, 默认使用:"application/x-www-form-urlencoded;charset=" + charset; + * 字符串内容表单默认使用: "application/json;charset=" + charset; + * File表单默认尝试使用文件名匹配Content-Type, 匹配失败使用:"application/octet-stream"; + * InputStream表单默认使用: "application/octet-stream"; + */ + public void setBodyContentType(String bodyContentType) { + this.bodyContentType = bodyContentType; + } + + public List
getHeaders() { + return new ArrayList
(headers); + } + + public List getQueryStringParams() { + checkBodyParams(); + return new ArrayList(queryStringParams); + } + + public List getBodyParams() { + checkBodyParams(); + return new ArrayList(bodyParams); + } + + public List getParams(String name) { + List result = new ArrayList(); + for (KeyValue kv : queryStringParams) { + if (name != null && name.equals(kv.key)) { + result.add(kv); + } + } + for (KeyValue kv : bodyParams) { + if (name == null && kv.key == null) { + result.add(kv); + } else if (name != null && name.equals(kv.key)) { + result.add(kv); + } + } + return result; + } + + public void clearParams() { + queryStringParams.clear(); + bodyParams.clear(); + bodyContent = null; + bodyContentType = null; + requestBody = null; + } + + public void removeParameter(String name) { + if (TextUtils.isEmpty(name)) { + bodyContent = null; + bodyContentType = null; + } else { + Iterator it = queryStringParams.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + if (name.equals(kv.key)) { + it.remove(); + } + } + } + + Iterator it = bodyParams.iterator(); + while (it.hasNext()) { + KeyValue kv = it.next(); + if (name == null && kv.key == null) { + it.remove(); + } else if (name != null && name.equals(kv.key)) { + it.remove(); + } + } + } + + public void setRequestBody(RequestBody requestBody) { + this.requestBody = requestBody; + } + + public RequestBody getRequestBody() throws IOException { + checkBodyParams(); + if (this.requestBody != null) { + return this.requestBody; + } + + RequestBody result = null; + if (!TextUtils.isEmpty(bodyContent)) { + result = new StringBody(bodyContent, charset); + result.setContentType(bodyContentType); + } else if (multipart) { + result = new MultipartBody(bodyParams, charset); + result.setContentType(bodyContentType); + } else if (bodyParams.size() == 1) { + KeyValue kv = bodyParams.get(0); + String name = kv.key; + Object value = kv.value; + String contentType = null; + if (kv instanceof BodyItemWrapper) { + BodyItemWrapper wrapper = (BodyItemWrapper) kv; + contentType = wrapper.contentType; + } + if (TextUtils.isEmpty(contentType)) { + contentType = bodyContentType; + } + if (value instanceof File) { + result = new FileBody((File) value, contentType); + } else if (value instanceof InputStream) { + result = new InputStreamBody((InputStream) value, contentType); + } else if (value instanceof byte[]) { + result = new InputStreamBody(new ByteArrayInputStream((byte[]) value), contentType); + } else { + if (TextUtils.isEmpty(name)) { + result = new StringBody(kv.getValueStrOrEmpty(), charset); + result.setContentType(contentType); + } else { + result = new UrlEncodedBody(bodyParams, charset); + result.setContentType(contentType); + } + } + } else { + result = new UrlEncodedBody(bodyParams, charset); + result.setContentType(bodyContentType); + } + + return result; + } + + public String toJSONString() throws JSONException { + return toJSONString(true); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + if (!queryStringParams.isEmpty()) { + for (KeyValue kv : queryStringParams) { + sb.append(kv.key).append("=").append(kv.value).append("&"); + } + sb.deleteCharAt(sb.length() - 1); + } + + if (!TextUtils.isEmpty(bodyContent)) { + sb.append("<").append(bodyContent).append(">"); + } else if (!bodyParams.isEmpty()) { + sb.append("<"); + for (KeyValue kv : bodyParams) { + sb.append(kv.key).append("=").append(kv.value).append("&"); + } + sb.deleteCharAt(sb.length() - 1); + sb.append(">"); + } + return sb.toString(); + } + + private synchronized void checkBodyParams() { + if (bodyParams.isEmpty()) return; + + if (requestBody != null || !HttpMethod.permitsRequestBody(method)) { + queryStringParams.addAll(bodyParams); + bodyParams.clear(); + return; + } + + if (asJsonContent || asJsonArrayContent) { + try { + bodyContent = toJSONString(false); + bodyParams.clear(); + } catch (JSONException ex) { + ex.printStackTrace(); + } + } else if (!TextUtils.isEmpty(bodyContent)) { + queryStringParams.addAll(bodyParams); + bodyParams.clear(); + } + } + + private void params2Json(final JSONObject jsonObject, final List paramList) throws JSONException { + HashSet arraySet = new HashSet(paramList.size()); + LinkedHashMap tempData = new LinkedHashMap(paramList.size()); + for (int i = 0; i < paramList.size(); i++) { + KeyValue kv = paramList.get(i); + final String key = kv.key; + if (TextUtils.isEmpty(key)) continue; + + JSONArray ja = null; + if (tempData.containsKey(key)) { + ja = tempData.get(key); + } else { + ja = new JSONArray(); + tempData.put(key, ja); + } + + ja.put(RequestParamsHelper.parseJSONObject(kv.value)); + + if (kv instanceof ArrayItem) { + arraySet.add(key); + } + } + + for (Map.Entry entry : tempData.entrySet()) { + String key = entry.getKey(); + JSONArray ja = entry.getValue(); + if (ja.length() > 1 || arraySet.contains(key)) { + jsonObject.put(key, ja); + } else { + Object value = ja.get(0); + jsonObject.put(key, value); + } + } + } + + private String toJSONString(boolean withQueryString) throws JSONException { + JSONArray jsonArray = null; + JSONObject jsonObject = null; + if (!TextUtils.isEmpty(bodyContent)) { + if (bodyContent.trim().startsWith("[")) { + jsonArray = new JSONArray(bodyContent); + if (jsonArray.length() > 0) { + Object first = jsonArray.get(0); + if (first instanceof JSONObject) { + jsonObject = (JSONObject) first; + } else { + LogUtil.w("only contains bodyContent"); + return jsonArray.toString(); + } + } else { + jsonObject = new JSONObject(); + jsonArray.put(jsonObject); + } + } else { + jsonObject = new JSONObject(bodyContent); + } + } else { + jsonObject = new JSONObject(); + if (asJsonArrayContent) { + jsonArray = new JSONArray(); + jsonArray.put(jsonObject); + } + } + + if (withQueryString) { + List list = new ArrayList(queryStringParams.size() + bodyParams.size()); + list.addAll(queryStringParams); + list.addAll(bodyParams); + params2Json(jsonObject, list); + } else { + params2Json(jsonObject, bodyParams); + } + + return jsonArray != null ? jsonArray.toString() : jsonObject.toString(); + } + + public static final class ArrayItem extends KeyValue { + public ArrayItem(String key, Object value) { + super(key, value); + } + } + + public static final class Header extends KeyValue { + + public final boolean setHeader; + + public Header(String key, String value, boolean setHeader) { + super(key, value); + this.setHeader = setHeader; + } + } + + public static final class BodyItemWrapper extends KeyValue { + + public final String fileName; + public final String contentType; + + public BodyItemWrapper(String key, Object value, String contentType, String fileName) { + super(key, value); + if (TextUtils.isEmpty(contentType)) { + this.contentType = "application/octet-stream"; + } else { + this.contentType = contentType; + } + this.fileName = fileName; + } + } +} diff --git a/app/src/main/java/org/xutils/http/HttpManagerImpl.java b/app/src/main/java/org/xutils/http/HttpManagerImpl.java new file mode 100644 index 0000000..abeaa1d --- /dev/null +++ b/app/src/main/java/org/xutils/http/HttpManagerImpl.java @@ -0,0 +1,130 @@ +package org.xutils.http; + +import com.bbitcn.silk.utils.MyUtil; +import com.bbitcn.silk.utils.global.Global; +import com.bbitcn.silk.utils.log.MyLog; + +import org.xutils.HttpManager; +import org.xutils.common.Callback; +import org.xutils.x; + +import java.lang.reflect.Type; +import java.util.List; + +/** + * Created by wyouflf on 15/7/23. + * HttpManager实现 + */ +public final class HttpManagerImpl implements HttpManager { + + private static final Object lock = new Object(); + private static volatile HttpManagerImpl instance; + + private HttpManagerImpl() { + } + + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new HttpManagerImpl(); + } + } + } + x.Ext.setHttpManager(instance); + } + + @Override + public Callback.Cancelable get(RequestParams entity, Callback.CommonCallback callback) { + return request(HttpMethod.GET, entity, callback); + } + + @Override + public Callback.Cancelable post(RequestParams entity, Callback.CommonCallback callback) { + return request(HttpMethod.POST, entity, callback); + } + + @Override + public Callback.Cancelable put(RequestParams entity, Callback.CommonCallback callback) { + return request(HttpMethod.PUT, entity, callback); + } + + @Override + public Callback.Cancelable request(HttpMethod method, RequestParams entity, Callback.CommonCallback callback) { + MyLog.network("请求链接:" + entity.getUri()); + //输出参数 +// MyLog.network("请求参数: " + entity.getBodyContent()); + entity.setMethod(method); + Callback.Cancelable cancelable = null; + if (callback instanceof Callback.Cancelable) { + cancelable = (Callback.Cancelable) callback; + } + HttpTask task = new HttpTask(entity, cancelable, callback); + + List headers = entity.getHeaders(); + for (BaseParams.Header header : headers) { + if (header.getKey().equals("Authorization") && !Global.isTokenAvailable()) { + MyUtil.login(() -> x.task().start(task)); + return null; + } + } + return x.task().start(task); + } + + @Override + public T getSync(RequestParams entity, Class resultType) throws Throwable { + return requestSync(HttpMethod.GET, entity, resultType); + } + + @Override + public T postSync(RequestParams entity, Class resultType) throws Throwable { + return requestSync(HttpMethod.POST, entity, resultType); + } + + @Override + public T requestSync(HttpMethod method, RequestParams entity, Class resultType) throws Throwable { + DefaultSyncCallback callback = new DefaultSyncCallback(resultType); + return requestSync(method, entity, callback); + } + + @Override + public T requestSync(HttpMethod method, RequestParams entity, Callback.TypedCallback callback) throws Throwable { + entity.setMethod(method); + HttpTask task = new HttpTask(entity, null, callback); + return x.task().startSync(task); + } + + private class DefaultSyncCallback implements Callback.TypedCallback { + + private final Class resultType; + + public DefaultSyncCallback(Class resultType) { + this.resultType = resultType; + } + + @Override + public Type getLoadType() { + return resultType; + } + + @Override + public void onSuccess(T result) { + + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + + } + + @Override + public void onCancelled(CancelledException cex) { + + } + + @Override + public void onFinished() { + + } + } +} diff --git a/app/src/main/java/org/xutils/http/HttpMethod.java b/app/src/main/java/org/xutils/http/HttpMethod.java new file mode 100644 index 0000000..e521305 --- /dev/null +++ b/app/src/main/java/org/xutils/http/HttpMethod.java @@ -0,0 +1,46 @@ +package org.xutils.http; + +/** + * Created by wyouflf on 15/8/4. + * HTTP谓词枚举 + */ +public enum HttpMethod { + GET("GET"), + POST("POST"), + PUT("PUT"), + PATCH("PATCH"), + HEAD("HEAD"), + MOVE("MOVE"), + COPY("COPY"), + DELETE("DELETE"), + OPTIONS("OPTIONS"), + TRACE("TRACE"), + CONNECT("CONNECT"); + + private final String value; + + HttpMethod(String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public static boolean permitsRetry(HttpMethod method) { + return method == GET; + } + + public static boolean permitsCache(HttpMethod method) { + return method == GET || method == POST; + } + + public static boolean permitsRequestBody(HttpMethod method) { + return method == null + || method == POST + || method == PUT + || method == PATCH + || method == DELETE; + } +} diff --git a/app/src/main/java/org/xutils/http/HttpTask.java b/app/src/main/java/org/xutils/http/HttpTask.java new file mode 100644 index 0000000..435b918 --- /dev/null +++ b/app/src/main/java/org/xutils/http/HttpTask.java @@ -0,0 +1,645 @@ +package org.xutils.http; + +import android.text.TextUtils; + +import org.xutils.common.Callback; +import org.xutils.common.task.AbsTask; +import org.xutils.common.task.Priority; +import org.xutils.common.task.PriorityExecutor; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.common.util.ParameterizedTypeUtil; +import org.xutils.ex.HttpException; +import org.xutils.ex.HttpRedirectException; +import org.xutils.http.app.HttpRetryHandler; +import org.xutils.http.app.RedirectHandler; +import org.xutils.http.app.RequestInterceptListener; +import org.xutils.http.app.RequestTracker; +import org.xutils.http.request.UriRequest; +import org.xutils.http.request.UriRequestFactory; +import org.xutils.x; + +import java.io.Closeable; +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/7/23. + * http 请求任务 + */ +public class HttpTask extends AbsTask implements ProgressHandler { + + // 请求相关 + private RequestParams params; + private UriRequest request; + private Type loadType; + private volatile boolean hasException = false; + private final Callback.CommonCallback callback; + + // 缓存控制 + private Object rawResult = null; + private volatile Boolean trustCache = null; + private final Object cacheLock = new Object(); + + // 扩展callback + private Callback.CacheCallback cacheCallback; + private Callback.PrepareCallback prepareCallback; + private Callback.ProgressCallback progressCallback; + private RequestInterceptListener requestInterceptListener; + + // 日志追踪 + private RequestTracker tracker; + + // 文件下载任务 + private final static AtomicInteger sCurrFileLoadCount = new AtomicInteger(0); + private static final HashMap>> + DOWNLOAD_TASK = new HashMap>>(1); + + // 线程池 + private final Executor executor; + private static final PriorityExecutor HTTP_EXECUTOR = new PriorityExecutor(5, true); + private static final PriorityExecutor CACHE_EXECUTOR = new PriorityExecutor(5, true); + + + public HttpTask(RequestParams params, Callback.Cancelable cancelHandler, + Callback.CommonCallback callback) { + super(cancelHandler); + + assert params != null; + assert callback != null; + + // set params & callback + this.params = params; + this.callback = callback; + if (callback instanceof Callback.CacheCallback) { + this.cacheCallback = (Callback.CacheCallback) callback; + } + if (callback instanceof Callback.PrepareCallback) { + this.prepareCallback = (Callback.PrepareCallback) callback; + } + if (callback instanceof Callback.ProgressCallback) { + this.progressCallback = (Callback.ProgressCallback) callback; + } + if (callback instanceof RequestInterceptListener) { + this.requestInterceptListener = (RequestInterceptListener) callback; + } + + // init tracker + { + RequestTracker customTracker = params.getRequestTracker(); + if (customTracker == null) { + if (callback instanceof RequestTracker) { + customTracker = (RequestTracker) callback; + } else { + customTracker = UriRequestFactory.getDefaultTracker(); + } + } + if (customTracker != null) { + tracker = new RequestTrackerWrapper(customTracker); + } + } + + // init executor + if (params.getExecutor() != null) { + this.executor = params.getExecutor(); + } else { + if (cacheCallback != null) { + this.executor = CACHE_EXECUTOR; + } else { + this.executor = HTTP_EXECUTOR; + } + } + } + + // 解析loadType + private void resolveLoadType() { + Class callBackType = callback.getClass(); + if (callback instanceof Callback.TypedCallback) { + loadType = ((Callback.TypedCallback) callback).getLoadType(); + } else if (callback instanceof Callback.PrepareCallback) { + loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.PrepareCallback.class, 0); + } else { + loadType = ParameterizedTypeUtil.getParameterizedType(callBackType, Callback.CommonCallback.class, 0); + } + } + + // 初始化请求参数 + private UriRequest createNewRequest() throws Throwable { + // init request + params.init(); + UriRequest result = UriRequestFactory.getUriRequest(params, loadType); + result.setProgressHandler(this); + this.loadingUpdateMaxTimeSpan = params.getLoadingUpdateMaxTimeSpan(); + this.update(FLAG_REQUEST_CREATED, result); + return result; + } + + // 文件下载冲突检测 + private void checkDownloadTask() { + if (File.class == loadType) { + synchronized (DOWNLOAD_TASK) { + String downloadTaskKey = this.params.getSaveFilePath(); + /*{ + // 不处理缓存文件下载冲突, + // 缓存文件下载冲突会抛出FileLockedException异常, + // 回调方法中处理控制是否重新尝试下载. + }*/ + if (!TextUtils.isEmpty(downloadTaskKey)) { + WeakReference> taskRef = DOWNLOAD_TASK.get(downloadTaskKey); + if (taskRef != null) { + HttpTask task = taskRef.get(); + if (task != null) { + task.cancel(); + task.closeRequestSync(); + } + DOWNLOAD_TASK.remove(downloadTaskKey); + } + DOWNLOAD_TASK.put(downloadTaskKey, new WeakReference>(this)); + } // end if (!TextUtils.isEmpty(downloadTaskKey)) + + if (DOWNLOAD_TASK.size() > RequestParams.MAX_FILE_LOAD_WORKER) { + Iterator>>> + entryItr = DOWNLOAD_TASK.entrySet().iterator(); + while (entryItr.hasNext()) { + Map.Entry>> next = entryItr.next(); + WeakReference> value = next.getValue(); + if (value == null || value.get() == null) { + entryItr.remove(); + } + } + } + } // end synchronized + } + } + + @Override + @SuppressWarnings("unchecked") + protected ResultType doBackground() throws Throwable { + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + // 初始化请求参数 + ResultType result = null; + resolveLoadType(); + request = createNewRequest(); + checkDownloadTask(); + // retry 初始化 + boolean retry = true; + int retryCount = 0; + Throwable exception = null; + HttpRetryHandler retryHandler = this.params.getHttpRetryHandler(); + if (retryHandler == null) { + retryHandler = new HttpRetryHandler(); + } + retryHandler.setMaxRetryCount(this.params.getMaxRetryCount()); + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + // 检查缓存 + Object cacheResult = null; + if (cacheCallback != null && HttpMethod.permitsCache(params.getMethod())) { + // 尝试从缓存获取结果, 并为请求头加入缓存控制参数. + try { + clearRawResult(); + LogUtil.d("load cache: " + this.request.getRequestUri()); + rawResult = this.request.loadResultFromCache(); + } catch (Throwable ex) { + LogUtil.w("load disk cache error", ex); + } + + if (this.isCancelled()) { + clearRawResult(); + throw new Callback.CancelledException("cancelled before request"); + } + + if (rawResult != null) { + if (prepareCallback != null) { + try { + cacheResult = prepareCallback.prepare(rawResult); + } catch (Throwable ex) { + cacheResult = null; + LogUtil.w("prepare disk cache error", ex); + } finally { + clearRawResult(); + } + } else { + cacheResult = rawResult; + } + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + if (cacheResult != null) { + // 同步等待是否信任缓存 + this.update(FLAG_CACHE, cacheResult); + synchronized (cacheLock) { + while (trustCache == null) { + try { + cacheLock.wait(); + } catch (InterruptedException iex) { + throw new Callback.CancelledException("cancelled before request"); + } catch (Throwable ignored) { + } + } + } + + // 处理完成 + if (trustCache) { + return null; + } + } + } + } + + if (trustCache == null) { + trustCache = false; + } + + if (cacheResult == null) { + this.request.clearCacheHeader(); + } + + // 判断请求的缓存策略 + if (callback instanceof Callback.ProxyCacheCallback) { + if (((Callback.ProxyCacheCallback) callback).onlyCache()) { + return null; + } + } + + // 发起请求 + retry = true; + while (retry) { + retry = false; + + try { + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + // 由loader发起请求, 拿到结果. + this.request.close(); // retry 前关闭上次请求 + + try { + clearRawResult(); + // 开始请求工作 + LogUtil.d("load: " + this.request.getRequestUri()); + RequestWorker requestWorker = new RequestWorker(); + requestWorker.request(); + if (requestWorker.ex != null) { + throw requestWorker.ex; + } + rawResult = requestWorker.result; + } catch (Throwable ex) { + clearRawResult(); + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled during request"); + } else { + throw ex; + } + } + + if (prepareCallback != null) { + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request"); + } + + try { + result = (ResultType) prepareCallback.prepare(rawResult); + } finally { + clearRawResult(); + } + } else { + result = (ResultType) rawResult; + } + + // 保存缓存 + if (cacheCallback != null && HttpMethod.permitsCache(params.getMethod())) { + try { + this.request.save2Cache(); + } catch (Throwable ex) { + LogUtil.e("Error while storing the http cache.", ex); + } + } + + if (this.isCancelled()) { + throw new Callback.CancelledException("cancelled after request"); + } + } catch (HttpRedirectException redirectEx) { + retry = true; + LogUtil.w("Http Redirect:" + params.getUri()); + } catch (Throwable ex) { + switch (this.request.getResponseCode()) { + case 204: // empty content + case 205: // empty content + case 304: // disk cache is valid. + return null; + default: { + exception = ex; + if (this.isCancelled() && !(exception instanceof Callback.CancelledException)) { + exception = new Callback.CancelledException("canceled by user"); + } + retry = retryHandler.canRetry(this.request, exception, ++retryCount); + } + } + } + + } + + if (exception != null && result == null && !trustCache) { + hasException = true; + throw exception; + } + + return result; + } + + private static final int FLAG_REQUEST_CREATED = 1; + private static final int FLAG_CACHE = 2; + private static final int FLAG_PROGRESS = 3; + + @Override + @SuppressWarnings("unchecked") + protected void onUpdate(int flag, Object... args) { + switch (flag) { + case FLAG_REQUEST_CREATED: { + if (this.tracker != null) { + this.tracker.onRequestCreated((UriRequest) args[0]); + } + break; + } + case FLAG_CACHE: { + synchronized (cacheLock) { + try { + ResultType result = (ResultType) args[0]; + if (tracker != null) { + tracker.onCache(request, result); + } + trustCache = this.cacheCallback.onCache(result); + } catch (Throwable ex) { + trustCache = false; + callback.onError(ex, true); + } finally { + cacheLock.notifyAll(); + } + } + break; + } + case FLAG_PROGRESS: { + if (this.progressCallback != null && args.length == 3) { + try { + this.progressCallback.onLoading( + ((Number) args[0]).longValue(), + ((Number) args[1]).longValue(), + (Boolean) args[2]); + } catch (Throwable ex) { + callback.onError(ex, true); + } + } + break; + } + default: { + break; + } + } + } + + @Override + protected void onWaiting() { + if (tracker != null) { + tracker.onWaiting(params); + } + if (progressCallback != null) { + progressCallback.onWaiting(); + } + } + + @Override + protected void onStarted() { + if (tracker != null) { + tracker.onStart(params); + } + if (progressCallback != null) { + progressCallback.onStarted(); + } + } + + @Override + protected void onSuccess(ResultType result) { + if (hasException) return; + if (tracker != null) { + tracker.onSuccess(request, result); + } + callback.onSuccess(result); + } + + @Override + protected void onError(Throwable ex, boolean isCallbackError) { + if (tracker != null) { + tracker.onError(request, ex, isCallbackError); + } + callback.onError(ex, isCallbackError); + } + + + @Override + protected void onCancelled(Callback.CancelledException cex) { + if (tracker != null) { + tracker.onCancelled(request); + } + callback.onCancelled(cex); + } + + @Override + protected void onFinished() { + if (tracker != null) { + tracker.onFinished(request); + } + x.task().run(new Runnable() { + @Override + public void run() { + closeRequestSync(); + } + }); + callback.onFinished(); + } + + private void clearRawResult() { + if (rawResult instanceof Closeable) { + IOUtil.closeQuietly((Closeable) rawResult); + } + rawResult = null; + } + + @Override + protected void cancelWorks() { + x.task().run(new Runnable() { + @Override + public void run() { + closeRequestSync(); + } + }); + } + + @Override + protected boolean isCancelFast() { + return params.isCancelFast(); + } + + private void closeRequestSync() { + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + sCurrFileLoadCount.notifyAll(); + } + } + clearRawResult(); + IOUtil.closeQuietly(request); + } + + @Override + public Executor getExecutor() { + return this.executor; + } + + @Override + public Priority getPriority() { + return params.getPriority(); + } + + // ############################### start: region implements ProgressHandler + private long lastUpdateTime; + private long loadingUpdateMaxTimeSpan = 300; // 300ms + + /** + * @return continue + */ + @Override + public boolean updateProgress(long total, long current, boolean forceUpdateUI) { + + if (isCancelled() || isFinished()) { + return false; + } + + if (progressCallback != null && request != null && current > 0) { + if (total < 0) { + total = -1; + } else if (total < current) { + total = current; + } + if (forceUpdateUI) { + lastUpdateTime = System.currentTimeMillis(); + this.update(FLAG_PROGRESS, total, current, request.isLoading()); + } else { + long currTime = System.currentTimeMillis(); + if (currTime - lastUpdateTime >= loadingUpdateMaxTimeSpan) { + lastUpdateTime = currTime; + this.update(FLAG_PROGRESS, total, current, request.isLoading()); + } + } + } + + return !isCancelled() && !isFinished(); + } + + // ############################### end: region implements ProgressHandler + + @Override + public String toString() { + return params.toString(); + } + + + /** + * 请求发送和加载数据线程. + * 该线程被join到HttpTask的工作线程去执行. + * 它的主要作用是为了能强行中断请求的链接过程; + * 并辅助限制同时下载文件的线程数. + */ + private final class RequestWorker { + /*private*/ Object result; + /*private*/ Throwable ex; + + private RequestWorker() { + } + + public void request() { + try { + boolean interrupted = false; + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + while (sCurrFileLoadCount.get() >= RequestParams.MAX_FILE_LOAD_WORKER + && !HttpTask.this.isCancelled()) { + try { + sCurrFileLoadCount.wait(10); + } catch (InterruptedException iex) { + interrupted = true; + break; + } catch (Throwable ignored) { + } + } + } + sCurrFileLoadCount.incrementAndGet(); + } + + if (interrupted || HttpTask.this.isCancelled()) { + throw new Callback.CancelledException("cancelled before request" + (interrupted ? "(interrupted)" : "")); + } + + try { + request.setRequestInterceptListener(requestInterceptListener); + this.result = request.loadResult(); + } catch (Throwable ex) { + this.ex = ex; + } + + if (this.ex != null) { + throw this.ex; + } + } catch (Throwable ex) { + this.ex = ex; + if (ex instanceof HttpException) { + HttpException httpEx = (HttpException) ex; + int errorCode = httpEx.getCode(); + if (errorCode == 301 || errorCode == 302) { + RedirectHandler redirectHandler = params.getRedirectHandler(); + if (redirectHandler != null) { + try { + RequestParams redirectParams = redirectHandler.getRedirectParams(request); + if (redirectParams != null) { + if (redirectParams.getMethod() == null) { + redirectParams.setMethod(params.getMethod()); + } + // 开始重定向请求 + HttpTask.this.params = redirectParams; + HttpTask.this.request = createNewRequest(); + this.ex = new HttpRedirectException(errorCode, httpEx.getMessage(), httpEx.getResult()); + } + } catch (Throwable throwable) { + this.ex = ex; + } + } + } + } + } finally { + if (File.class == loadType) { + synchronized (sCurrFileLoadCount) { + sCurrFileLoadCount.decrementAndGet(); + sCurrFileLoadCount.notifyAll(); + } + } + } + } + } + +} diff --git a/app/src/main/java/org/xutils/http/ProgressHandler.java b/app/src/main/java/org/xutils/http/ProgressHandler.java new file mode 100644 index 0000000..fdaaa41 --- /dev/null +++ b/app/src/main/java/org/xutils/http/ProgressHandler.java @@ -0,0 +1,14 @@ +package org.xutils.http; + +/** + * 进度控制接口, updateProgress方式中ProgressCallback#onLoading. + * 默认最长间隔300毫秒调用一次. + * Author: wyouflf + * Time: 2014/05/23 + */ +public interface ProgressHandler { + /** + * @return continue + */ + boolean updateProgress(long total, long current, boolean forceUpdateUI); +} diff --git a/app/src/main/java/org/xutils/http/RequestParams.java b/app/src/main/java/org/xutils/http/RequestParams.java new file mode 100644 index 0000000..1d8a78c --- /dev/null +++ b/app/src/main/java/org/xutils/http/RequestParams.java @@ -0,0 +1,407 @@ +package org.xutils.http; + +import android.content.Context; +import android.text.TextUtils; + +import org.xutils.common.task.Priority; +import org.xutils.http.annotation.HttpRequest; +import org.xutils.http.app.DefaultParamsBuilder; +import org.xutils.http.app.DefaultRedirectHandler; +import org.xutils.http.app.HttpRetryHandler; +import org.xutils.http.app.ParamsBuilder; +import org.xutils.http.app.RedirectHandler; +import org.xutils.http.app.RequestTracker; +import org.xutils.x; + +import java.net.Proxy; +import java.util.concurrent.Executor; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by wyouflf on 15/7/17. + * 网络请求参数实体 + */ +public class RequestParams extends BaseParams { + + public final static int MAX_FILE_LOAD_WORKER = 10; + private final static DefaultRedirectHandler DEFAULT_REDIRECT_HANDLER = new DefaultRedirectHandler(); + + // 注解及其扩展参数 + private HttpRequest httpRequest; + private String uri; + private final String[] signs; + private final String[] cacheKeys; + private ParamsBuilder builder; + private String buildUri; + private String buildCacheKey; + private SSLSocketFactory sslSocketFactory; + + // 扩展参数 + private Context context; + private Proxy proxy; // 代理 + private HostnameVerifier hostnameVerifier; // https域名校验 + private boolean useCookie = true; // 是否在请求过程中启用cookie + private String cacheDirName; // 缓存文件夹名称 + private long cacheSize; // 缓存文件夹大小 + private long cacheMaxAge; // 默认缓存存活时间, 单位:毫秒.(如果服务没有返回有效的max-age或Expires) + private Executor executor; // 自定义线程池 + private Priority priority = Priority.DEFAULT; // 请求优先级 + private int connectTimeout = 1000 * 15; // 连接超时时间 + private int readTimeout = 1000 * 15; // 读取超时时间 + private boolean autoResume = true; // 是否在下载是自动断点续传 + private boolean autoRename = false; // 是否根据头信息自动命名文件 + private int maxRetryCount = 2; // 最大请求错误重试次数 + private String saveFilePath; // 下载文件时文件保存的路径和文件名 + private boolean cancelFast = false; // 是否可以被立即停止, true: 为请求创建新的线程, 取消时请求线程被立即中断. + private int loadingUpdateMaxTimeSpan = 300; // 进度刷新最大间隔时间(ms) + private HttpRetryHandler httpRetryHandler; // 自定义HttpRetryHandler + private RequestTracker requestTracker; // 自定义日志记录接口. + private RedirectHandler redirectHandler = DEFAULT_REDIRECT_HANDLER; + + /** + * 使用空构造创建时必须, 必须是带有@HttpRequest注解的子类. + */ + public RequestParams() { + this(null, null, null, null); + } + + /** + * @param uri 不可为空 + */ + public RequestParams(String uri) { + this(uri, null, null, null); + } + + /** + * @param uri 不可为空 + * @param builder 用于自定义参数构建过程, 为空时使用{@link DefaultParamsBuilder} + * @param signs 自定义需要签名的字段, 会传给ParamsBuilder + * @param cacheKeys 自定义缓存关键key信息, 会传给ParamsBuilder + */ + public RequestParams(String uri, ParamsBuilder builder, String[] signs, String[] cacheKeys) { + if (uri != null && builder == null) { + builder = new DefaultParamsBuilder(); + } + this.uri = uri; + this.signs = signs; + this.cacheKeys = cacheKeys; + this.builder = builder; + this.context = x.app(); + } + + // invoke via HttpTask#createNewRequest + /*package*/ void init() throws Throwable { + if (!TextUtils.isEmpty(buildUri)) return; + + if (TextUtils.isEmpty(uri) && getHttpRequest() == null) { + throw new IllegalStateException("uri is empty && @HttpRequest == null"); + } + + // init params from entity + initEntityParams(); + + // build uri & cacheKey + buildUri = uri; + HttpRequest httpRequest = this.getHttpRequest(); + if (httpRequest != null) { + builder = httpRequest.builder().newInstance(); + buildUri = builder.buildUri(this, httpRequest); + builder.buildParams(this); + builder.buildSign(this, httpRequest.signs()); + if (sslSocketFactory == null) { + sslSocketFactory = builder.getSSLSocketFactory(); + } + } else if (this.builder != null) { + builder.buildParams(this); + builder.buildSign(this, signs); + if (sslSocketFactory == null) { + sslSocketFactory = builder.getSSLSocketFactory(); + } + } + } + + public String getUri() { + return TextUtils.isEmpty(buildUri) ? uri : buildUri; + } + + public void setUri(String uri) { + if (TextUtils.isEmpty(buildUri)) { + this.uri = uri; + } else { + this.buildUri = uri; + } + } + + public String getCacheKey() { + if (TextUtils.isEmpty(buildCacheKey) && builder != null) { + HttpRequest httpRequest = this.getHttpRequest(); + if (httpRequest != null) { + buildCacheKey = builder.buildCacheKey(this, httpRequest.cacheKeys()); + } else { + buildCacheKey = builder.buildCacheKey(this, cacheKeys); + } + } + return buildCacheKey; + } + + public void setSslSocketFactory(SSLSocketFactory sslSocketFactory) { + this.sslSocketFactory = sslSocketFactory; + } + + public SSLSocketFactory getSslSocketFactory() { + return sslSocketFactory; + } + + public HostnameVerifier getHostnameVerifier() { + return hostnameVerifier; + } + + public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + + /** + * 是否在请求过程中启用cookie, 默认true. + */ + public boolean isUseCookie() { + return useCookie; + } + + /** + * 是否在请求过程中启用cookie, 默认true. + */ + public void setUseCookie(boolean useCookie) { + this.useCookie = useCookie; + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + + public Proxy getProxy() { + return proxy; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public Priority getPriority() { + return priority; + } + + public void setPriority(Priority priority) { + this.priority = priority; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + if (connectTimeout > 0) { + this.connectTimeout = connectTimeout; + } + } + + public int getReadTimeout() { + return readTimeout; + } + + /** + * 注意get请求失败后默认会重试2次, 可以通过setMaxRetryCount(0)来防止get请求自动重试. + */ + public void setReadTimeout(int readTimeout) { + if (readTimeout > 0) { + this.readTimeout = readTimeout; + } + } + + public String getCacheDirName() { + return cacheDirName; + } + + public void setCacheDirName(String cacheDirName) { + this.cacheDirName = cacheDirName; + } + + public long getCacheSize() { + return cacheSize; + } + + public void setCacheSize(long cacheSize) { + this.cacheSize = cacheSize; + } + + /** + * 默认缓存存活时间, 单位:毫秒.(如果服务没有返回有效的max-age或Expires) + */ + public long getCacheMaxAge() { + return cacheMaxAge; + } + + /** + * 默认缓存存活时间, 单位:毫秒.(如果服务没有返回有效的max-age或Expires) + */ + public void setCacheMaxAge(long cacheMaxAge) { + this.cacheMaxAge = cacheMaxAge; + } + + /** + * 自定义线程池 + */ + public Executor getExecutor() { + return executor; + } + + /** + * 自定义线程池 + */ + public void setExecutor(Executor executor) { + this.executor = executor; + } + + /** + * 是否在下载是自动断点续传 + */ + public boolean isAutoResume() { + return autoResume; + } + + /** + * 设置是否在下载是自动断点续传 + */ + public void setAutoResume(boolean autoResume) { + this.autoResume = autoResume; + } + + /** + * 是否根据头信息自动命名文件 + */ + public boolean isAutoRename() { + return autoRename; + } + + /** + * 设置是否根据头信息自动命名文件 + */ + public void setAutoRename(boolean autoRename) { + this.autoRename = autoRename; + } + + /** + * 获取下载文件时文件保存的路径和文件名 + */ + public String getSaveFilePath() { + return saveFilePath; + } + + /** + * 设置下载文件时文件保存的路径和文件名 + */ + public void setSaveFilePath(String saveFilePath) { + this.saveFilePath = saveFilePath; + } + + public int getMaxRetryCount() { + return maxRetryCount; + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + /** + * 是否可以被立即停止. + * + * @return true: 为请求创建新的线程, 取消时请求线程被立即中断; false: 请求建立过程可能不被立即终止. + */ + public boolean isCancelFast() { + return cancelFast; + } + + /** + * 是否可以被立即停止. + * + * @param cancelFast true: 为请求创建新的线程, 取消时请求线程被立即中断; false: 请求建立过程可能不被立即终止. + */ + public void setCancelFast(boolean cancelFast) { + this.cancelFast = cancelFast; + } + + public int getLoadingUpdateMaxTimeSpan() { + return loadingUpdateMaxTimeSpan; + } + + /** + * 进度刷新最大间隔时间(默认300毫秒) + */ + public void setLoadingUpdateMaxTimeSpan(int loadingUpdateMaxTimeSpan) { + this.loadingUpdateMaxTimeSpan = loadingUpdateMaxTimeSpan; + } + + public HttpRetryHandler getHttpRetryHandler() { + return httpRetryHandler; + } + + public void setHttpRetryHandler(HttpRetryHandler httpRetryHandler) { + this.httpRetryHandler = httpRetryHandler; + } + + public RedirectHandler getRedirectHandler() { + return redirectHandler; + } + + /** + * 自定义重定向接口, 默认系统自动重定向. + */ + public void setRedirectHandler(RedirectHandler redirectHandler) { + this.redirectHandler = redirectHandler; + } + + public RequestTracker getRequestTracker() { + return requestTracker; + } + + public void setRequestTracker(RequestTracker requestTracker) { + this.requestTracker = requestTracker; + } + + private void initEntityParams() { + RequestParamsHelper.parseKV(this, this.getClass(), new RequestParamsHelper.ParseKVListener() { + @Override + public void onParseKV(String name, Object value) { + addParameter(name, value); + } + }); + } + + private boolean invokedGetHttpRequest = false; + + private HttpRequest getHttpRequest() { + if (httpRequest == null && !invokedGetHttpRequest) { + invokedGetHttpRequest = true; + Class thisCls = this.getClass(); + if (thisCls != RequestParams.class) { + httpRequest = thisCls.getAnnotation(HttpRequest.class); + } + } + + return httpRequest; + } + + @Override + public String toString() { + String url = this.getUri(); + String baseParamsStr = super.toString(); + return TextUtils.isEmpty(url) ? + baseParamsStr : + url + (url.contains("?") ? "&" : "?") + baseParamsStr; + } +} diff --git a/app/src/main/java/org/xutils/http/RequestParamsHelper.java b/app/src/main/java/org/xutils/http/RequestParamsHelper.java new file mode 100644 index 0000000..d6cf77b --- /dev/null +++ b/app/src/main/java/org/xutils/http/RequestParamsHelper.java @@ -0,0 +1,116 @@ +package org.xutils.http; + +import android.os.Parcelable; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.xutils.common.util.LogUtil; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +/** + * Created by wyouflf on 16/1/23. + */ +/*package*/ final class RequestParamsHelper { + + private static final ClassLoader BOOT_CL = String.class.getClassLoader(); + + private RequestParamsHelper() { + } + + /*package*/ interface ParseKVListener { + void onParseKV(String name, Object value); + } + + /*package*/ + static void parseKV(Object entity, Class type, ParseKVListener listener) { + if (entity == null || type == null || type == RequestParams.class || type == Object.class) { + return; + } else { + ClassLoader cl = type.getClassLoader(); + if (cl == null || cl == BOOT_CL) { + return; + } + } + + Field[] fields = type.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + String name = field.getName(); + if (!Modifier.isTransient(field.getModifiers()) + && !"serialVersionUID".equals(name) + && field.getType() != Parcelable.Creator.class) { + try { + field.setAccessible(true); + Object value = field.get(entity); + if (value != null) { + listener.onParseKV(name, value); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + parseKV(entity, type.getSuperclass(), listener); + } + + /*package*/ + static Object parseJSONObject(Object value) throws JSONException { + if (value == null) return null; + + Object result = value; + Class cls = value.getClass(); + if (cls.isArray()) { + JSONArray array = new JSONArray(); + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + array.put(parseJSONObject(Array.get(value, i))); + } + result = array; + } else if (value instanceof Iterable) { + JSONArray array = new JSONArray(); + Iterable list = (Iterable) value; + for (Object item : list) { + array.put(parseJSONObject(item)); + } + result = array; + } else if (value instanceof Map) { + final JSONObject jo = new JSONObject(); + Map map = (Map) value; + for (Map.Entry entry : map.entrySet()) { + Object k = entry.getKey(); + Object v = entry.getValue(); + if (k != null && v != null) { + jo.put(String.valueOf(k), parseJSONObject(v)); + } + } + result = jo; + } else { + ClassLoader cl = cls.getClassLoader(); + if (cl != null && cl != BOOT_CL) { + final JSONObject jo = new JSONObject(); + parseKV(value, cls, new ParseKVListener() { + @Override + public void onParseKV(String name, Object value) { + try { + value = parseJSONObject(value); + jo.put(name, value); + } catch (JSONException ex) { + throw new IllegalArgumentException("parse RequestParams to json failed", ex); + } + } + }); + result = jo; + } + } + + return result; + } + +} diff --git a/app/src/main/java/org/xutils/http/RequestTrackerWrapper.java b/app/src/main/java/org/xutils/http/RequestTrackerWrapper.java new file mode 100644 index 0000000..3b2c30e --- /dev/null +++ b/app/src/main/java/org/xutils/http/RequestTrackerWrapper.java @@ -0,0 +1,90 @@ +package org.xutils.http; + +import org.xutils.common.util.LogUtil; +import org.xutils.http.app.RequestTracker; +import org.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/4. + * Wrapper for tracker + */ +/*package*/ final class RequestTrackerWrapper implements RequestTracker { + + private final RequestTracker base; + + public RequestTrackerWrapper(RequestTracker base) { + this.base = base; + } + + @Override + public void onWaiting(RequestParams params) { + try { + base.onWaiting(params); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onStart(RequestParams params) { + try { + base.onStart(params); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onRequestCreated(UriRequest request) { + try { + base.onRequestCreated(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onCache(UriRequest request, Object result) { + try { + base.onCache(request, result); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onSuccess(UriRequest request, Object result) { + try { + base.onSuccess(request, result); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onCancelled(UriRequest request) { + try { + base.onCancelled(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void onError(UriRequest request, Throwable ex, boolean isCallbackError) { + try { + base.onError(request, ex, isCallbackError); + } catch (Throwable exOnError) { + LogUtil.e(exOnError.getMessage(), exOnError); + } + } + + @Override + public void onFinished(UriRequest request) { + try { + base.onFinished(request); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } +} diff --git a/app/src/main/java/org/xutils/http/annotation/HttpRequest.java b/app/src/main/java/org/xutils/http/annotation/HttpRequest.java new file mode 100644 index 0000000..39e9626 --- /dev/null +++ b/app/src/main/java/org/xutils/http/annotation/HttpRequest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.http.annotation; + +import org.xutils.http.app.DefaultParamsBuilder; +import org.xutils.http.app.ParamsBuilder; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HttpRequest { + + String host() default ""; + + String path(); + + Class builder() default DefaultParamsBuilder.class; + + String[] signs() default ""; + + String[] cacheKeys() default ""; +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/http/annotation/HttpResponse.java b/app/src/main/java/org/xutils/http/annotation/HttpResponse.java new file mode 100644 index 0000000..e882d97 --- /dev/null +++ b/app/src/main/java/org/xutils/http/annotation/HttpResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.http.annotation; + +import org.xutils.http.app.ResponseParser; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HttpResponse { + + Class parser(); + +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/http/app/DefaultParamsBuilder.java b/app/src/main/java/org/xutils/http/app/DefaultParamsBuilder.java new file mode 100644 index 0000000..65256b7 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/DefaultParamsBuilder.java @@ -0,0 +1,120 @@ +package org.xutils.http.app; + +import org.xutils.common.util.KeyValue; +import org.xutils.common.util.LogUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.annotation.HttpRequest; + +import java.security.cert.X509Certificate; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * Created by wyouflf on 15/8/20. + * 默认参数构造器 + */ +public class DefaultParamsBuilder implements ParamsBuilder { + + public DefaultParamsBuilder() { + } + + /** + * 根据@HttpRequest构建请求的url + */ + @Override + public String buildUri(RequestParams params, HttpRequest httpRequest) throws Throwable { + return httpRequest.host() + "/" + httpRequest.path(); + } + + /** + * 根据注解的cacheKeys构建缓存的自定义key, + * 如果返回为空, 默认使用 url 和整个 query string 组成. + */ + @Override + public String buildCacheKey(RequestParams params, String[] cacheKeys) { + StringBuilder result = new StringBuilder(); + if (cacheKeys != null && cacheKeys.length > 0) { + result.append(params.getUri()).append("?"); + + // 添加cacheKeys对应的参数 + for (String key : cacheKeys) { + List kvList = params.getParams(key); + if (kvList != null && !kvList.isEmpty()) { + for (KeyValue kv : kvList) { + String value = kv.getValueStrOrNull(); + if (value != null) { + result.append(key).append("=").append(value).append("&"); + } + } + } + } + } + return result.toString(); + } + + /** + * 自定义SSLSocketFactory + */ + @Override + public SSLSocketFactory getSSLSocketFactory() throws Throwable { + return getTrustAllSSLSocketFactory(); + } + + /** + * 为请求添加通用参数或修改参数等操作 + */ + @Override + public void buildParams(RequestParams params) throws Throwable { + } + + /** + * 自定义参数签名 + */ + @Override + public void buildSign(RequestParams params, String[] signs) throws Throwable { + + } + + private static SSLSocketFactory trustAllSSlSocketFactory; + + public static SSLSocketFactory getTrustAllSSLSocketFactory() { + if (trustAllSSlSocketFactory == null) { + synchronized (DefaultParamsBuilder.class) { + if (trustAllSSlSocketFactory == null) { + + // 信任所有证书 + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) { + LogUtil.d("checkClientTrusted:" + authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) { + LogUtil.d("checkServerTrusted:" + authType); + } + }}; + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, null); + trustAllSSlSocketFactory = sslContext.getSocketFactory(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + + return trustAllSSlSocketFactory; + } + +} diff --git a/app/src/main/java/org/xutils/http/app/DefaultRedirectHandler.java b/app/src/main/java/org/xutils/http/app/DefaultRedirectHandler.java new file mode 100644 index 0000000..a3f2f79 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/DefaultRedirectHandler.java @@ -0,0 +1,56 @@ +package org.xutils.http.app; + +import android.text.TextUtils; +import android.webkit.URLUtil; + +import org.xutils.http.HttpMethod; +import org.xutils.http.RequestParams; +import org.xutils.http.request.HttpRequest; +import org.xutils.http.request.UriRequest; + +public class DefaultRedirectHandler implements RedirectHandler { + @Override + public RequestParams getRedirectParams(UriRequest request) throws Throwable { + if (request instanceof HttpRequest) { + HttpRequest httpRequest = (HttpRequest) request; + RequestParams params = httpRequest.getParams(); + String location = httpRequest.getResponseHeader("Location"); + if (!TextUtils.isEmpty(location)) { + if (!URLUtil.isHttpsUrl(location) && !URLUtil.isHttpUrl(location)) { + String url = params.getUri(); + if (location.startsWith("/")) { + int pathIndex = url.indexOf("/", 8); + if (pathIndex != -1) { + url = url.substring(0, pathIndex); + } + } else { + int pathIndex = url.lastIndexOf("/"); + if (pathIndex >= 8) { + url = url.substring(0, pathIndex + 1); + } else { + url += "/"; + } + } + location = url + location; + } + params.setUri(location); + + + /* http 1.0 301 302 + * http 1.1 303 307 308 + */ + int code = request.getResponseCode(); + if (code == 301 || code == 302 || code == 303) { + params.clearParams(); + params.setMethod(HttpMethod.GET); + } /*else if (code == 307 || code == 308) { + // don't change the request method or params + }*/ + + return params; + } + } + + return null; + } +} diff --git a/app/src/main/java/org/xutils/http/app/HttpRetryHandler.java b/app/src/main/java/org/xutils/http/app/HttpRetryHandler.java new file mode 100644 index 0000000..36a3219 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/HttpRetryHandler.java @@ -0,0 +1,76 @@ +package org.xutils.http.app; + + +import org.json.JSONException; +import org.xutils.common.Callback; +import org.xutils.common.util.LogUtil; +import org.xutils.ex.HttpException; +import org.xutils.http.HttpMethod; +import org.xutils.http.request.UriRequest; + +import java.io.FileNotFoundException; +import java.net.MalformedURLException; +import java.net.NoRouteToHostException; +import java.net.PortUnreachableException; +import java.net.ProtocolException; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.HashSet; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +public class HttpRetryHandler { + + protected int maxRetryCount = 2; + + protected static HashSet> blackList = new HashSet>(); + + static { + blackList.add(HttpException.class); + blackList.add(Callback.CancelledException.class); + blackList.add(MalformedURLException.class); + blackList.add(URISyntaxException.class); + blackList.add(NoRouteToHostException.class); + blackList.add(PortUnreachableException.class); + blackList.add(ProtocolException.class); + blackList.add(NullPointerException.class); + blackList.add(FileNotFoundException.class); + blackList.add(JSONException.class); + blackList.add(UnknownHostException.class); + blackList.add(IllegalArgumentException.class); + } + + public HttpRetryHandler() { + } + + public void setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + } + + public boolean canRetry(UriRequest request, Throwable ex, int count) { + + LogUtil.w(ex.getMessage(), ex); + + if (count > maxRetryCount) { + LogUtil.w(request.toString()); + LogUtil.w("The Max Retry times has been reached!"); + return false; + } + + if (!HttpMethod.permitsRetry(request.getParams().getMethod())) { + LogUtil.w(request.toString()); + LogUtil.w("The Request Method can not be retried."); + return false; + } + + if (blackList.contains(ex.getClass())) { + LogUtil.w(request.toString()); + LogUtil.w("The Exception can not be retried."); + return false; + } + + return true; + } +} diff --git a/app/src/main/java/org/xutils/http/app/ParamsBuilder.java b/app/src/main/java/org/xutils/http/app/ParamsBuilder.java new file mode 100644 index 0000000..64de428 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/ParamsBuilder.java @@ -0,0 +1,40 @@ +package org.xutils.http.app; + +import org.xutils.http.RequestParams; +import org.xutils.http.annotation.HttpRequest; + +import javax.net.ssl.SSLSocketFactory; + +/** + * Created by wyouflf on 15/8/20. + *

+ * {@link HttpRequest} 注解的参数构建的模板接口 + */ +public interface ParamsBuilder { + + /** + * 根据@HttpRequest构建请求的url + */ + String buildUri(RequestParams params, HttpRequest httpRequest) throws Throwable; + + /** + * 根据注解的cacheKeys构建缓存的自定义key, + * 如果返回为空, 默认使用 url 和整个 query string 组成. + */ + String buildCacheKey(RequestParams params, String[] cacheKeys); + + /** + * 自定义SSLSocketFactory + */ + SSLSocketFactory getSSLSocketFactory() throws Throwable; + + /** + * 为请求添加通用参数等操作 + */ + void buildParams(RequestParams params) throws Throwable; + + /** + * 自定义参数签名 + */ + void buildSign(RequestParams params, String[] signs) throws Throwable; +} diff --git a/app/src/main/java/org/xutils/http/app/RedirectHandler.java b/app/src/main/java/org/xutils/http/app/RedirectHandler.java new file mode 100644 index 0000000..c2c72a7 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/RedirectHandler.java @@ -0,0 +1,19 @@ +package org.xutils.http.app; + +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/12. + * 请求重定向控制接口 + */ +public interface RedirectHandler { + + /** + * 根据请求信息返回自定义重定向的请求参数 + * + * @param request 原始请求 + * @return 返回不为null时进行重定向 + */ + RequestParams getRedirectParams(UriRequest request) throws Throwable; +} diff --git a/app/src/main/java/org/xutils/http/app/RequestInterceptListener.java b/app/src/main/java/org/xutils/http/app/RequestInterceptListener.java new file mode 100644 index 0000000..adad613 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/RequestInterceptListener.java @@ -0,0 +1,25 @@ +package org.xutils.http.app; + + +import org.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/11/10. + * 拦截请求响应(在后台线程工作). + *

+ * 用法: + * 1. 请求的callback参数同时实现RequestInterceptListener + * 2. 或者使用 @HttpRequest 注解实现ParamsBuilder接口 + */ +public interface RequestInterceptListener { + + /** + * 检查请求参数等处理 + */ + void beforeRequest(UriRequest request) throws Throwable; + + /** + * 检查请求相应头等处理 + */ + void afterRequest(UriRequest request) throws Throwable; +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/http/app/RequestTracker.java b/app/src/main/java/org/xutils/http/app/RequestTracker.java new file mode 100644 index 0000000..124f0e9 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/RequestTracker.java @@ -0,0 +1,35 @@ +package org.xutils.http.app; + +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +/** + * Created by wyouflf on 15/9/10. + * 请求过程追踪, 适合用来记录请求日志. + * 所有回调方法都在主线程进行. + *

+ * 用法: + * 1. 将RequestTracker实例设置给请求参数RequestParams. + * 2. 请的callback参数同时实现RequestTracker接口; + * 3. 注册给UriRequestFactory的默认RequestTracker. + * 注意: 请求回调RequestTracker时优先级按照上面的顺序, + * 找到一个RequestTracker的实现会忽略其他. + */ +public interface RequestTracker { + + void onWaiting(RequestParams params); + + void onStart(RequestParams params); + + void onRequestCreated(UriRequest request); + + void onCache(UriRequest request, Object result); + + void onSuccess(UriRequest request, Object result); + + void onCancelled(UriRequest request); + + void onError(UriRequest request, Throwable ex, boolean isCallbackError); + + void onFinished(UriRequest request); +} diff --git a/app/src/main/java/org/xutils/http/app/ResponseParser.java b/app/src/main/java/org/xutils/http/app/ResponseParser.java new file mode 100644 index 0000000..f028988 --- /dev/null +++ b/app/src/main/java/org/xutils/http/app/ResponseParser.java @@ -0,0 +1,23 @@ +package org.xutils.http.app; + + +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/8/4. + * {@link org.xutils.http.annotation.HttpResponse} 注解的返回值转换模板 + * + * @param 支持String, byte[], JSONObject, JSONArray, InputStream + */ +public interface ResponseParser extends RequestInterceptListener { + + /** + * 转换result为resultType类型的对象 + * + * @param resultType 返回值类型(可能带有泛型信息) + * @param resultClass 返回值类型 + * @param result 网络返回数据(支持String, byte[], JSONObject, JSONArray, InputStream) + * @return 请求结果, 类型为resultType + */ + Object parse(Type resultType, Class resultClass, ResponseDataType result) throws Throwable; +} diff --git a/app/src/main/java/org/xutils/http/body/FileBody.java b/app/src/main/java/org/xutils/http/body/FileBody.java new file mode 100644 index 0000000..5170001 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/FileBody.java @@ -0,0 +1,60 @@ +package org.xutils.http.body; + +import android.net.Uri; +import android.text.TextUtils; + +import org.xutils.common.util.LogUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.HttpURLConnection; + +/** + * Created by wyouflf on 15/8/13. + */ +public class FileBody extends InputStreamBody { + + private File file; + private String contentType; + + public FileBody(File file) throws IOException { + this(file, null); + } + + public FileBody(File file, String contentType) throws IOException { + super(new FileInputStream(file)); + this.file = file; + this.contentType = contentType; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + if (TextUtils.isEmpty(contentType)) { + contentType = getFileContentType(file); + } + return contentType; + } + + public static String getFileContentType(File file) { + String filename = file.getName(); + String contentType = null; + try { + filename = Uri.encode(filename, "-![.:/,?&=]"); + contentType = HttpURLConnection.guessContentTypeFromName(filename); + } catch (Exception e) { + LogUtil.e(e.toString()); + } + if (TextUtils.isEmpty(contentType)) { + contentType = "application/octet-stream"; + } else { + contentType = contentType.replaceFirst("\\/jpg$", "/jpeg"); + } + return contentType; + } +} diff --git a/app/src/main/java/org/xutils/http/body/InputStreamBody.java b/app/src/main/java/org/xutils/http/body/InputStreamBody.java new file mode 100644 index 0000000..2081afa --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/InputStreamBody.java @@ -0,0 +1,98 @@ +package org.xutils.http.body; + +import android.text.TextUtils; + +import org.xutils.common.Callback; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.http.ProgressHandler; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +public class InputStreamBody implements ProgressBody { + + private InputStream content; + private String contentType; + + private final long total; + private long current = 0; + + private ProgressHandler callBackHandler; + + public InputStreamBody(InputStream inputStream) { + this(inputStream, null); + } + + public InputStreamBody(InputStream inputStream, String contentType) { + this.content = inputStream; + this.contentType = contentType; + this.total = getInputStreamLength(inputStream); + } + + @Override + public void setProgressHandler(ProgressHandler progressHandler) { + this.callBackHandler = progressHandler; + } + + @Override + public long getContentLength() { + return total; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + return TextUtils.isEmpty(contentType) ? "application/octet-stream" : contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("upload stopped!"); + } + + byte[] buffer = new byte[4096]; + try { + int len = 0; + while ((len = content.read(buffer)) != -1) { + out.write(buffer, 0, len); + current += len; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + out.flush(); + + if (callBackHandler != null) { + callBackHandler.updateProgress(total, current, true); + } + } finally { + IOUtil.closeQuietly(content); + } + } + + public static long getInputStreamLength(InputStream inputStream) { + try { + if (inputStream instanceof FileInputStream || + inputStream instanceof ByteArrayInputStream) { + return inputStream.available(); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + return -1L; + } +} diff --git a/app/src/main/java/org/xutils/http/body/MultipartBody.java b/app/src/main/java/org/xutils/http/body/MultipartBody.java new file mode 100644 index 0000000..befe738 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/MultipartBody.java @@ -0,0 +1,264 @@ +package org.xutils.http.body; + + +import android.text.TextUtils; + +import org.xutils.common.Callback; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.KeyValue; +import org.xutils.http.BaseParams.BodyItemWrapper; +import org.xutils.http.ProgressHandler; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +public class MultipartBody implements ProgressBody { + + private static byte[] BOUNDARY_PREFIX_BYTES = "--------7da3d81520810".getBytes(); + private static byte[] END_BYTES = "\r\n".getBytes(); + private static byte[] TWO_DASHES_BYTES = "--".getBytes(); + private byte[] boundaryPostfixBytes; + private String contentType; // multipart/subtype; boundary=xxx... + private String charset = "UTF-8"; + + private List multipartParams; + private long total = 0; + private long current = 0; + + public MultipartBody(List multipartParams, String charset) { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + this.multipartParams = multipartParams; + generateContentType(); + + // calc total + CounterOutputStream counter = new CounterOutputStream(); + try { + this.writeTo(counter); + this.total = counter.total.get(); + } catch (IOException e) { + this.total = -1; + } + } + + private ProgressHandler callBackHandler; + + @Override + public void setProgressHandler(ProgressHandler progressHandler) { + this.callBackHandler = progressHandler; + } + + private void generateContentType() { + String boundaryPostfix = Double.toHexString(Math.random() * 0xFFFF); + boundaryPostfixBytes = boundaryPostfix.getBytes(); + contentType = "multipart/form-data; boundary=" + new String(BOUNDARY_PREFIX_BYTES) + boundaryPostfix; + } + + @Override + public long getContentLength() { + return total; + } + + /** + * only change subType: + * "multipart/subType; boundary=xxx..." + * + * @param subType "form-data" or "related" + */ + @Override + public void setContentType(String subType) { + if (TextUtils.isEmpty(subType)) return; + int index = contentType.indexOf(";"); + this.contentType = "multipart/" + subType + contentType.substring(index); + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("upload stopped!"); + } + + for (KeyValue entry : multipartParams) { + writeEntry(out, entry); + } + writeLine(out, TWO_DASHES_BYTES, BOUNDARY_PREFIX_BYTES, boundaryPostfixBytes, TWO_DASHES_BYTES); + out.flush(); + + if (callBackHandler != null) { + callBackHandler.updateProgress(total, current, true); + } + } + + /** + * 写入multipart中的一项 + */ + private void writeEntry(OutputStream out, KeyValue entry) throws IOException { + String name = entry.key; + Object value = entry.value; + if (TextUtils.isEmpty(name) || value == null) return; + + writeLine(out, TWO_DASHES_BYTES, BOUNDARY_PREFIX_BYTES, boundaryPostfixBytes); + + String fileName = ""; + String contentType = null; + if (entry instanceof BodyItemWrapper) { + BodyItemWrapper wrapper = (BodyItemWrapper) entry; + fileName = wrapper.fileName; + contentType = wrapper.contentType; + } + + if (value instanceof File) { + File file = (File) value; + if (TextUtils.isEmpty(fileName)) { + fileName = file.getName(); + } + if (TextUtils.isEmpty(contentType)) { + contentType = FileBody.getFileContentType(file); + } + writeLine(out, buildContentDisposition(name, fileName, charset)); + writeLine(out, buildContentType(value, contentType, charset)); + writeLine(out); // 内容前空一行 + writeFile(out, file); + writeLine(out); + } else { + writeLine(out, buildContentDisposition(name, fileName, charset)); + writeLine(out, buildContentType(value, contentType, charset)); + writeLine(out); // 内容前空一行 + if (value instanceof InputStream) { + writeStreamAndCloseIn(out, (InputStream) value); + writeLine(out); + } else { + byte[] content; + if (value instanceof byte[]) { + content = (byte[]) value; + } else { + content = entry.getValueStrOrEmpty().getBytes(charset); + } + writeLine(out, content); + current += content.length; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + } + } + + private void writeLine(OutputStream out, byte[]... bs) throws IOException { + if (bs != null) { + for (byte[] b : bs) { + out.write(b); + } + } + out.write(END_BYTES); + } + + private void writeFile(OutputStream out, File file) throws IOException { + if (out instanceof CounterOutputStream) { + ((CounterOutputStream) out).addFile(file); + } else { + writeStreamAndCloseIn(out, new FileInputStream(file)); + } + } + + private void writeStreamAndCloseIn(OutputStream out, InputStream in) throws IOException { + if (out instanceof CounterOutputStream) { + ((CounterOutputStream) out).addStream(in); + } else { + try { + int len; + byte[] buf = new byte[4096]; + while ((len = in.read(buf)) >= 0) { + out.write(buf, 0, len); + current += len; + if (callBackHandler != null && !callBackHandler.updateProgress(total, current, false)) { + throw new Callback.CancelledException("upload stopped!"); + } + } + } finally { + IOUtil.closeQuietly(in); + } + } + } + + private static byte[] buildContentDisposition(String name, String fileName, String charset) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder("Content-Disposition: form-data"); + result.append("; name=\"").append(name.replace("\"", "\\\"")).append("\""); + if (!TextUtils.isEmpty(fileName)) { + result.append("; filename=\"").append(fileName.replace("\"", "\\\"")).append("\""); + } + return result.toString().getBytes(charset); + } + + private static byte[] buildContentType(Object value, String contentType, String charset) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder("Content-Type: "); + if (TextUtils.isEmpty(contentType)) { + if (value instanceof String) { + contentType = "text/plain; charset=" + charset; + } else { + contentType = "application/octet-stream"; + } + } else { + contentType = contentType.replaceFirst("\\/jpg$", "/jpeg"); + } + result.append(contentType); + return result.toString().getBytes(charset); + } + + private class CounterOutputStream extends OutputStream { + + final AtomicLong total = new AtomicLong(0L); + + public CounterOutputStream() { + } + + public void addFile(File file) { + if (total.get() == -1L) return; + total.addAndGet(file.length()); + } + + public void addStream(InputStream inputStream) { + if (total.get() == -1L) return; + long length = InputStreamBody.getInputStreamLength(inputStream); + if (length > 0) { + total.addAndGet(length); + } else { + total.set(-1L); + } + } + + @Override + public void write(int oneByte) throws IOException { + if (total.get() == -1L) return; + total.incrementAndGet(); + } + + @Override + public void write(byte[] buffer) throws IOException { + if (total.get() == -1L) return; + total.addAndGet(buffer.length); + } + + @Override + public void write(byte[] buffer, int offset, int count) throws IOException { + if (total.get() == -1L) return; + total.addAndGet(count); + } + } +} diff --git a/app/src/main/java/org/xutils/http/body/ProgressBody.java b/app/src/main/java/org/xutils/http/body/ProgressBody.java new file mode 100644 index 0000000..51be474 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/ProgressBody.java @@ -0,0 +1,11 @@ +package org.xutils.http.body; + + +import org.xutils.http.ProgressHandler; + +/** + * Created by wyouflf on 15/8/13. + */ +public interface ProgressBody extends RequestBody { + void setProgressHandler(ProgressHandler progressHandler); +} diff --git a/app/src/main/java/org/xutils/http/body/RequestBody.java b/app/src/main/java/org/xutils/http/body/RequestBody.java new file mode 100644 index 0000000..e698ea5 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/RequestBody.java @@ -0,0 +1,18 @@ +package org.xutils.http.body; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * Created by wyouflf on 15/10/29. + */ +public interface RequestBody { + + long getContentLength(); + + void setContentType(String contentType); + + String getContentType(); + + void writeTo(OutputStream out) throws IOException; +} diff --git a/app/src/main/java/org/xutils/http/body/StringBody.java b/app/src/main/java/org/xutils/http/body/StringBody.java new file mode 100644 index 0000000..51a8fd6 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/StringBody.java @@ -0,0 +1,46 @@ +package org.xutils.http.body; + +import android.text.TextUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +public class StringBody implements RequestBody { + + private byte[] content; + private String contentType; + private String charset = "UTF-8"; + + public StringBody(String str, String charset) throws UnsupportedEncodingException { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + this.content = str.getBytes(this.charset); + } + + @Override + public long getContentLength() { + return content.length; + } + + @Override + public void setContentType(String contentType) { + this.contentType = contentType; + } + + @Override + public String getContentType() { + return TextUtils.isEmpty(contentType) ? "application/json;charset=" + charset : contentType; + } + + @Override + public void writeTo(OutputStream out) throws IOException { + out.write(content); + out.flush(); + } +} diff --git a/app/src/main/java/org/xutils/http/body/UrlEncodedBody.java b/app/src/main/java/org/xutils/http/body/UrlEncodedBody.java new file mode 100644 index 0000000..999dad7 --- /dev/null +++ b/app/src/main/java/org/xutils/http/body/UrlEncodedBody.java @@ -0,0 +1,67 @@ +package org.xutils.http.body; + +import android.text.TextUtils; + +import org.xutils.common.util.KeyValue; +import org.xutils.common.util.LogUtil; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.List; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +public class UrlEncodedBody implements RequestBody { + + private byte[] content; + private String charset = "UTF-8"; + + public UrlEncodedBody(List params, String charset) throws IOException { + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + StringBuilder contentSb = new StringBuilder(); + if (params != null) { + for (KeyValue kv : params) { + String name = kv.key; + String value = kv.getValueStrOrNull(); + if (!TextUtils.isEmpty(name) && value != null) { + if (contentSb.length() > 0) { + contentSb.append("&"); + } + contentSb.append(URLEncoder.encode(name, this.charset).replaceAll("\\+", "%20")) + .append("=") + .append(URLEncoder.encode(value, this.charset).replaceAll("\\+", "%20")); + } + } + } + + this.content = contentSb.toString().getBytes(this.charset); + } + + @Override + public long getContentLength() { + return content.length; + } + + @Override + public void setContentType(String contentType) { + if (!TextUtils.isEmpty(contentType)) { + LogUtil.w("ignored Content-Type: " + contentType); + } + } + + @Override + public String getContentType() { + return "application/x-www-form-urlencoded;charset=" + charset; + } + + @Override + public void writeTo(OutputStream sink) throws IOException { + sink.write(this.content); + sink.flush(); + } +} diff --git a/app/src/main/java/org/xutils/http/cookie/CookieEntity.java b/app/src/main/java/org/xutils/http/cookie/CookieEntity.java new file mode 100644 index 0000000..0dc8905 --- /dev/null +++ b/app/src/main/java/org/xutils/http/cookie/CookieEntity.java @@ -0,0 +1,117 @@ +package org.xutils.http.cookie; + +import android.text.TextUtils; + +import org.xutils.db.annotation.Column; +import org.xutils.db.annotation.Table; + +import java.net.HttpCookie; +import java.net.URI; + +/** + * Created by wyouflf on 15/8/20. + * 数据库中的cookie实体 + */ +@Table(name = "cookie", + onCreated = "CREATE UNIQUE INDEX index_cookie_unique ON cookie(\"name\",\"domain\",\"path\")") +/*package*/ final class CookieEntity { + + // ~ 100 year + private static final long MAX_EXPIRY = System.currentTimeMillis() + 1000L * 60L * 60L * 24L * 30L * 12L * 100L; + + @Column(name = "id", isId = true) + private long id; + + @Column(name = "uri") + private String uri; // cookie add by this uri. + + @Column(name = "name") + private String name; + @Column(name = "value") + private String value; + @Column(name = "comment") + private String comment; + @Column(name = "commentURL") + private String commentURL; + @Column(name = "discard") + private boolean discard; + @Column(name = "domain") + private String domain; + @Column(name = "expiry") + private long expiry = MAX_EXPIRY; + @Column(name = "path") + private String path; + @Column(name = "portList") + private String portList; + @Column(name = "secure") + private boolean secure; + @Column(name = "version") + private int version = 1; + + public CookieEntity() { + } + + public CookieEntity(URI uri, HttpCookie cookie) { + this.uri = uri == null ? null : uri.toString(); + this.name = cookie.getName(); + this.value = cookie.getValue(); + this.comment = cookie.getComment(); + this.commentURL = cookie.getCommentURL(); + this.discard = cookie.getDiscard(); + this.domain = cookie.getDomain(); + long maxAge = cookie.getMaxAge(); + if (maxAge > 0) { + this.expiry = (maxAge * 1000L) + System.currentTimeMillis(); + if (this.expiry < 0L) { // 计算溢出? + this.expiry = MAX_EXPIRY; + } + } else { + this.expiry = -1L; + } + this.path = cookie.getPath(); + if (!TextUtils.isEmpty(path) && path.length() > 1 && path.endsWith("/")) { + this.path = path.substring(0, path.length() - 1); + } + this.portList = cookie.getPortlist(); + this.secure = cookie.getSecure(); + this.version = cookie.getVersion(); + } + + public HttpCookie toHttpCookie() { + HttpCookie cookie = new HttpCookie(name, value); + cookie.setComment(comment); + cookie.setCommentURL(commentURL); + cookie.setDiscard(discard); + cookie.setDomain(domain); + if (expiry == -1L) { + cookie.setMaxAge(-1L); + } else { + cookie.setMaxAge((expiry - System.currentTimeMillis()) / 1000L); + } + cookie.setPath(path); + cookie.setPortlist(portList); + cookie.setSecure(secure); + cookie.setVersion(version); + return cookie; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public boolean isExpired() { + return expiry != -1L && expiry < System.currentTimeMillis(); + } +} diff --git a/app/src/main/java/org/xutils/http/cookie/DbCookieStore.java b/app/src/main/java/org/xutils/http/cookie/DbCookieStore.java new file mode 100644 index 0000000..53ce008 --- /dev/null +++ b/app/src/main/java/org/xutils/http/cookie/DbCookieStore.java @@ -0,0 +1,328 @@ +package org.xutils.http.cookie; + +import android.text.TextUtils; + +import org.xutils.DbManager; +import org.xutils.common.task.PriorityExecutor; +import org.xutils.common.util.LogUtil; +import org.xutils.config.DbConfigs; +import org.xutils.db.Selector; +import org.xutils.db.sqlite.WhereBuilder; +import org.xutils.db.table.DbModel; +import org.xutils.x; + +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * Created by wyouflf on 15/8/20. + * 基于数据库的CookieStore实现. + */ +public enum DbCookieStore implements CookieStore { + + INSTANCE; + + private DbManager db; + private final Executor trimExecutor = new PriorityExecutor(1, true); + private static final int LIMIT_COUNT = 5000; // 限制最多5000条数据 + + private long lastTrimTime = 0L; + private static final long TRIM_TIME_SPAN = 1000; + + DbCookieStore() { + x.task().run(new Runnable() { + @Override + public void run() { + tryInit(); + } + }); + } + + private void tryInit() { + if (db == null) { + synchronized (this) { + if (db == null) { + try { + db = x.getDb(DbConfigs.COOKIE.getConfig()); + db.delete(CookieEntity.class, + WhereBuilder.b("expiry", "=", -1L)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + } + + /** + * Add one cookie into cookie store. + */ + @Override + public void add(URI uri, HttpCookie cookie) { + if (cookie == null) { + return; + } + + tryInit(); + + uri = getEffectiveURI(uri); + + try { + db.replace(new CookieEntity(uri, cookie)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + trimSize(); + } + + + /** + * Get all cookies, which: + * 1) given uri domain-matches with, or, associated with + * 2) given uri when added to the cookie store. + * 3) not expired. + * See RFC 2965 sec. 3.3.4 for more detail. + */ + @Override + public List get(URI uri) { + // argument can't be null + if (uri == null) { + throw new NullPointerException("uri is null"); + } + + tryInit(); + + uri = getEffectiveURI(uri); + + List rt = new ArrayList(); + + try { + + Selector selector = db.selector(CookieEntity.class); + + WhereBuilder where = WhereBuilder.b(); + + String host = uri.getHost(); + if (!TextUtils.isEmpty(host)) { + WhereBuilder subWhere = WhereBuilder.b("domain", "=", host).or("domain", "=", "." + host); + int firstDot = host.indexOf("."); + int lastDot = host.lastIndexOf("."); + if (firstDot > 0 && lastDot > firstDot) { + String domain = host.substring(firstDot, host.length()); + if (!TextUtils.isEmpty(domain)) { + subWhere.or("domain", "=", domain); + } + } + where.and(subWhere); + } + + String path = uri.getPath(); + if (!TextUtils.isEmpty(path)) { + WhereBuilder subWhere = WhereBuilder.b("path", "=", path) + .or("path", "=", "/").or("path", "=", null); + int lastSplit = path.lastIndexOf("/"); + while (lastSplit > 0) { + path = path.substring(0, lastSplit); + subWhere.or("path", "=", path); + lastSplit = path.lastIndexOf("/"); + } + + where.and(subWhere); + } + + where.or("uri", "=", uri.toString()); + + List cookieEntityList = selector.where(where).findAll(); + if (cookieEntityList != null) { + for (CookieEntity cookieEntity : cookieEntityList) { + if (!cookieEntity.isExpired()) { + rt.add(cookieEntity.toHttpCookie()); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return rt; + } + + /** + * Get all cookies in cookie store, except those have expired + */ + @Override + public List getCookies() { + tryInit(); + + List rt = new ArrayList(); + + try { + List cookieEntityList = db.findAll(CookieEntity.class); + if (cookieEntityList != null) { + for (CookieEntity cookieEntity : cookieEntityList) { + if (!cookieEntity.isExpired()) { + rt.add(cookieEntity.toHttpCookie()); + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + + return rt; + } + + /** + * Get all URIs, which are associated with at least one cookie + * of this cookie store. + */ + @Override + public List getURIs() { + tryInit(); + + List uris = new ArrayList(); + + try { + List uriList = + db.selector(CookieEntity.class).select("uri").findAll(); + if (uriList != null) { + for (DbModel model : uriList) { + String uri = model.getString("uri"); + if (!TextUtils.isEmpty(uri)) { + try { + uris.add(new URI(uri)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + try { + db.delete(CookieEntity.class, WhereBuilder.b("uri", "=", uri)); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + return uris; + } + + + /** + * Remove a cookie from store + */ + @Override + public boolean remove(URI uri, HttpCookie cookie) { + if (cookie == null) { + return true; + } + + tryInit(); + + boolean modified = false; + try { + WhereBuilder where = WhereBuilder.b("name", "=", cookie.getName()); + + String domain = cookie.getDomain(); + if (!TextUtils.isEmpty(domain)) { + where.and("domain", "=", domain); + } + + String path = cookie.getPath(); + if (!TextUtils.isEmpty(path)) { + if (path.length() > 1 && path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + where.and("path", "=", path); + } + + db.delete(CookieEntity.class, where); + + modified = true; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + return modified; + } + + + /** + * Remove all cookies in this cookie store. + */ + @Override + public boolean removeAll() { + tryInit(); + + try { + db.delete(CookieEntity.class); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return true; + } + + private void trimSize() { + trimExecutor.execute(new Runnable() { + @Override + public void run() { + tryInit(); + + long current = System.currentTimeMillis(); + if (current - lastTrimTime < TRIM_TIME_SPAN) { + return; + } else { + lastTrimTime = current; + } + + // delete expires + try { + db.delete(CookieEntity.class, WhereBuilder + .b("expiry", "<", System.currentTimeMillis()) + .and("expiry", "!=", -1L)); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + // trim by limit count + try { + int count = (int) db.selector(CookieEntity.class).count(); + if (count > LIMIT_COUNT + 10) { + List rmList = db.selector(CookieEntity.class) + .where("expiry", "!=", -1L).orderBy("expiry") + .limit(count - LIMIT_COUNT).findAll(); + if (rmList != null) { + db.delete(rmList); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + private URI getEffectiveURI(final URI uri) { + URI effectiveURI = null; + try { + effectiveURI = new URI("http", + uri.getHost(), + uri.getPath(), + null, // query component + null // fragment component + ); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + effectiveURI = uri; + } + + return effectiveURI; + } +} diff --git a/app/src/main/java/org/xutils/http/loader/BooleanLoader.java b/app/src/main/java/org/xutils/http/loader/BooleanLoader.java new file mode 100644 index 0000000..1b2729c --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/BooleanLoader.java @@ -0,0 +1,32 @@ +package org.xutils.http.loader; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +/*package*/ class BooleanLoader extends Loader { + + @Override + public Loader newInstance() { + return new BooleanLoader(); + } + + @Override + public Boolean load(final UriRequest request) throws Throwable { + request.sendRequest(); + return request.getResponseCode() < 300; + } + + @Override + public Boolean loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + + } +} diff --git a/app/src/main/java/org/xutils/http/loader/ByteArrayLoader.java b/app/src/main/java/org/xutils/http/loader/ByteArrayLoader.java new file mode 100644 index 0000000..43253aa --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/ByteArrayLoader.java @@ -0,0 +1,42 @@ +package org.xutils.http.loader; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.common.util.IOUtil; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +/*package*/ class ByteArrayLoader extends Loader { + + private byte[] resultData; + + @Override + public Loader newInstance() { + return new ByteArrayLoader(); + } + + @Override + public byte[] load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultData = IOUtil.readBytes(request.getInputStream()); + return resultData; + } + + @Override + public byte[] loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + byte[] data = cacheEntity.getBytesContent(); + if (data != null && data.length > 0) { + return data; + } + } + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + saveByteArrayCache(request, resultData); + } +} diff --git a/app/src/main/java/org/xutils/http/loader/FileLoader.java b/app/src/main/java/org/xutils/http/loader/FileLoader.java new file mode 100644 index 0000000..80c1e37 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/FileLoader.java @@ -0,0 +1,351 @@ +package org.xutils.http.loader; + +import android.text.TextUtils; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.cache.DiskCacheFile; +import org.xutils.cache.LruDiskCache; +import org.xutils.common.Callback; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.common.util.ProcessLock; +import org.xutils.ex.FileLockedException; +import org.xutils.ex.HttpException; +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.Arrays; +import java.util.Date; + +/** + * Author: wyouflf + * Time: 2014/05/30 + * 下载参数策略: + * 1. RequestParams#saveFilePath不为空时, 目标文件保存在saveFilePath; + * 否则由Cache策略分配文件下载路径. + * 2. 下载时临时目标文件路径为tempSaveFilePath, 下载完后进行a: CacheFile#commit; b:重命名等操作. + * 断点下载策略: + * 1. 要下载的目标文件不存在或小于 CHECK_SIZE 时删除目标文件, 重新下载. + * 2. 若文件存在且大于 CHECK_SIZE, range = fileLen - CHECK_SIZE , 校验check_buffer, 相同: 继续下载; + * 不相同: 删掉目标文件, 并抛出RuntimeException(HttpRetryHandler会使下载重新开始). + */ +public class FileLoader extends Loader { + + private static final int CHECK_SIZE = 512; + + private RequestParams params; + private String tempSaveFilePath; + private String saveFilePath; + private boolean isAutoResume; + private boolean isAutoRename; + private long contentLength; + private String responseFileName; + + private DiskCacheFile diskCacheFile; + + @Override + public Loader newInstance() { + return new FileLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + this.params = params; + isAutoResume = params.isAutoResume(); + isAutoRename = params.isAutoRename(); + } + } + + protected File load(final InputStream in) throws Throwable { + File targetFile = null; + BufferedInputStream bis = null; + BufferedOutputStream bos = null; + try { + targetFile = new File(tempSaveFilePath); + if (targetFile.isDirectory()) { + throw new IOException("could not create the file: " + tempSaveFilePath); + } + if (!targetFile.exists()) { + File dir = targetFile.getParentFile(); + if ((!dir.exists() && !dir.mkdirs()) || !dir.isDirectory()) { + throw new IOException("could not create the dir: " + dir.getAbsolutePath()); + } + } + + // 处理[断点逻辑2](见文件头doc) + long targetFileLen = targetFile.length(); + if (isAutoResume && targetFileLen > 0) { + FileInputStream fis = null; + try { + long filePos = targetFileLen - CHECK_SIZE; + if (filePos > 0) { + fis = new FileInputStream(targetFile); + byte[] fileCheckBuffer = IOUtil.readBytes(fis, filePos, CHECK_SIZE); + byte[] checkBuffer = IOUtil.readBytes(in, 0, CHECK_SIZE); + if (!Arrays.equals(checkBuffer, fileCheckBuffer)) { + IOUtil.closeQuietly(fis); // 先关闭文件流, 否则文件删除会失败. + IOUtil.deleteFileOrDir(targetFile); + throw new RuntimeException("need retry"); + } else { + contentLength -= CHECK_SIZE; + } + } else { + IOUtil.deleteFileOrDir(targetFile); + throw new RuntimeException("need retry"); + } + } finally { + IOUtil.closeQuietly(fis); + } + } + + // 开始下载 + long current = 0; + FileOutputStream fileOutputStream = null; + if (isAutoResume) { + current = targetFileLen; + fileOutputStream = new FileOutputStream(targetFile, true); + } else { + fileOutputStream = new FileOutputStream(targetFile); + } + + long total = contentLength + current; + bis = new BufferedInputStream(in); + bos = new BufferedOutputStream(fileOutputStream); + + if (progressHandler != null && !progressHandler.updateProgress(total, current, true)) { + throw new Callback.CancelledException("download stopped!"); + } + + byte[] tmp = new byte[4096]; + int len; + while ((len = bis.read(tmp)) != -1) { + + // 防止父文件夹被其他进程删除, 继续写入时造成父文件夹变为0字节文件的问题. + if (!targetFile.getParentFile().exists()) { + targetFile.getParentFile().mkdirs(); + throw new IOException("parent be deleted!"); + } + + bos.write(tmp, 0, len); + current += len; + if (progressHandler != null) { + if (!progressHandler.updateProgress(total, current, false)) { + bos.flush(); + throw new Callback.CancelledException("download stopped!"); + } + } + } + bos.flush(); + // 处理[下载逻辑2.a](见文件头doc) + if (diskCacheFile != null) { + targetFile = diskCacheFile.commit(); + } + + if (progressHandler != null) { + progressHandler.updateProgress(total, current, true); + } + } finally { + IOUtil.closeQuietly(bis); + IOUtil.closeQuietly(bos); + } + + return autoRename(targetFile); + } + + @Override + public File load(final UriRequest request) throws Throwable { + ProcessLock processLock = null; + File result = null; + try { + + // 处理[下载逻辑1](见文件头doc) + saveFilePath = params.getSaveFilePath(); + diskCacheFile = null; + if (TextUtils.isEmpty(saveFilePath)) { + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + // 保存路径为空, 存入磁盘缓存. + initDiskCacheFile(request); + } else { + tempSaveFilePath = saveFilePath + ".tmp"; + } + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + // 等待, 若不能下载则取消此次下载. + processLock = ProcessLock.tryLock(saveFilePath + "_lock", true); + if (processLock == null || !processLock.isValid()) { + throw new FileLockedException("download exists: " + saveFilePath); + } + + params = request.getParams(); + {// 处理[断点逻辑1](见文件头doc) + long range = 0; + if (isAutoResume) { + File tempFile = new File(tempSaveFilePath); + long fileLen = tempFile.length(); + if (fileLen <= CHECK_SIZE) { + IOUtil.deleteFileOrDir(tempFile); + range = 0; + } else { + range = fileLen - CHECK_SIZE; + } + } + // retry 时需要覆盖Range参数 + params.setHeader("Range", "bytes=" + range + "-"); + } + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + request.sendRequest(); // may be throw an HttpException + + contentLength = request.getContentLength(); + if (isAutoRename) { + responseFileName = getResponseFileName(request); + } + if (isAutoResume) { + isAutoResume = isSupportRange(request); + } + + if (progressHandler != null && !progressHandler.updateProgress(0, 0, false)) { + throw new Callback.CancelledException("download stopped!"); + } + + if (diskCacheFile != null) { + try { + DiskCacheEntity entity = diskCacheFile.getCacheEntity(); + entity.setLastAccess(System.currentTimeMillis()); + entity.setEtag(request.getETag()); + entity.setExpires(request.getExpiration()); + entity.setLastModify(new Date(request.getLastModified())); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + result = this.load(request.getInputStream()); + } catch (HttpException httpException) { + if (httpException.getCode() == 416) { + if (diskCacheFile != null) { + result = diskCacheFile.commit(); + } else { + result = new File(tempSaveFilePath); + } + // 从缓存获取文件, 不rename和断点, 直接退出. + if (result != null && result.exists()) { + if (isAutoRename) { + responseFileName = getResponseFileName(request); + } + result = autoRename(result); + } else { + IOUtil.deleteFileOrDir(result); + throw new IllegalStateException("cache file not found" + request.getCacheKey()); + } + } else { + throw httpException; + } + } finally { + IOUtil.closeQuietly(processLock); + IOUtil.closeQuietly(diskCacheFile); + } + return result; + } + + // 保存路径为空, 存入磁盘缓存. + private void initDiskCacheFile(final UriRequest request) throws Throwable { + + DiskCacheEntity entity = new DiskCacheEntity(); + entity.setKey(request.getCacheKey()); + diskCacheFile = LruDiskCache.getDiskCache(params.getCacheDirName()).createDiskCacheFile(entity); + + if (diskCacheFile != null) { + saveFilePath = diskCacheFile.getAbsolutePath(); + // diskCacheFile is a temp path, diskCacheFile.commit() return the dest file. + tempSaveFilePath = saveFilePath; + isAutoRename = false; + } else { + throw new IOException("create cache file error:" + request.getCacheKey()); + } + } + + // 处理[下载逻辑2.b](见文件头doc) + private File autoRename(File loadedFile) { + if (isAutoRename && loadedFile.exists() && !TextUtils.isEmpty(responseFileName)) { + File newFile = new File(loadedFile.getParent(), responseFileName); + while (newFile.exists()) { + newFile = new File(loadedFile.getParent(), System.currentTimeMillis() + responseFileName); + } + return loadedFile.renameTo(newFile) ? newFile : loadedFile; + } else if (!saveFilePath.equals(tempSaveFilePath)) { + File newFile = new File(saveFilePath); + return loadedFile.renameTo(newFile) ? newFile : loadedFile; + } else { + return loadedFile; + } + } + + private static String getResponseFileName(UriRequest request) { + if (request == null) return null; + String disposition = request.getResponseHeader("Content-Disposition"); + if (!TextUtils.isEmpty(disposition)) { + int startIndex = disposition.indexOf("filename="); + if (startIndex > 0) { + startIndex += 9; // "filename=".length() + int endIndex = disposition.indexOf(";", startIndex); + if (endIndex < 0) { + endIndex = disposition.length(); + } + if (endIndex > startIndex) { + try { + String name = URLDecoder.decode( + disposition.substring(startIndex, endIndex), + request.getParams().getCharset()); + if (name.startsWith("\"") && name.endsWith("\"")) { + name = name.substring(1, name.length() - 1); + } + return name; + } catch (UnsupportedEncodingException ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } + return null; + } + + private static boolean isSupportRange(UriRequest request) { + if (request == null) return false; + String ranges = request.getResponseHeader("Accept-Ranges"); + if (ranges != null) { + return ranges.contains("bytes"); + } + ranges = request.getResponseHeader("Content-Range"); + return ranges != null && ranges.contains("bytes"); + } + + @Override + public File loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return LruDiskCache.getDiskCache(params.getCacheDirName()).getDiskCacheFile(cacheEntity.getKey()); + } + + @Override + public void save2Cache(final UriRequest request) { + // the file caches already saved by diskCacheFile#commit + } +} diff --git a/app/src/main/java/org/xutils/http/loader/InputStreamLoader.java b/app/src/main/java/org/xutils/http/loader/InputStreamLoader.java new file mode 100644 index 0000000..b762481 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/InputStreamLoader.java @@ -0,0 +1,36 @@ +package org.xutils.http.loader; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.http.request.UriRequest; + +import java.io.InputStream; + +/** + * 建议配合 {@link org.xutils.common.Callback.PrepareCallback} 使用, + * 将PrepareType设置为InputStream, 以便在PrepareCallback#prepare中做耗时的数据任务处理. + *

+ * Author: wyouflf + * Time: 2014/05/30 + */ +/*package*/ class InputStreamLoader extends Loader { + + @Override + public Loader newInstance() { + return new InputStreamLoader(); + } + + @Override + public InputStream load(final UriRequest request) throws Throwable { + request.sendRequest(); + return request.getInputStream(); + } + + @Override + public InputStream loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(final UriRequest request) { + } +} diff --git a/app/src/main/java/org/xutils/http/loader/IntegerLoader.java b/app/src/main/java/org/xutils/http/loader/IntegerLoader.java new file mode 100644 index 0000000..2c97f88 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/IntegerLoader.java @@ -0,0 +1,31 @@ +package org.xutils.http.loader; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/10/17 + */ +/*package*/ class IntegerLoader extends Loader { + @Override + public Loader newInstance() { + return new IntegerLoader(); + } + + @Override + public Integer load(UriRequest request) throws Throwable { + request.sendRequest(); + return request.getResponseCode(); + } + + @Override + public Integer loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + return null; + } + + @Override + public void save2Cache(UriRequest request) { + + } +} diff --git a/app/src/main/java/org/xutils/http/loader/JSONArrayLoader.java b/app/src/main/java/org/xutils/http/loader/JSONArrayLoader.java new file mode 100644 index 0000000..52d7c3b --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/JSONArrayLoader.java @@ -0,0 +1,58 @@ +package org.xutils.http.loader; + +import android.text.TextUtils; + +import org.json.JSONArray; +import org.xutils.cache.DiskCacheEntity; +import org.xutils.common.util.IOUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/06/16 + */ +/*package*/ class JSONArrayLoader extends Loader { + + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new JSONArrayLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public JSONArray load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return new JSONArray(resultStr); + } + + @Override + public JSONArray loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + String text = cacheEntity.getTextContent(); + if (!TextUtils.isEmpty(text)) { + return new JSONArray(text); + } + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/app/src/main/java/org/xutils/http/loader/JSONObjectLoader.java b/app/src/main/java/org/xutils/http/loader/JSONObjectLoader.java new file mode 100644 index 0000000..02ee0c7 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/JSONObjectLoader.java @@ -0,0 +1,58 @@ +package org.xutils.http.loader; + +import android.text.TextUtils; + +import org.json.JSONObject; +import org.xutils.cache.DiskCacheEntity; +import org.xutils.common.util.IOUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/06/16 + */ +/*package*/ class JSONObjectLoader extends Loader { + + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new JSONObjectLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public JSONObject load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return new JSONObject(resultStr); + } + + @Override + public JSONObject loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + String text = cacheEntity.getTextContent(); + if (!TextUtils.isEmpty(text)) { + return new JSONObject(text); + } + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/app/src/main/java/org/xutils/http/loader/Loader.java b/app/src/main/java/org/xutils/http/loader/Loader.java new file mode 100644 index 0000000..b76fc39 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/Loader.java @@ -0,0 +1,58 @@ +package org.xutils.http.loader; + + +import android.text.TextUtils; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.cache.LruDiskCache; +import org.xutils.http.ProgressHandler; +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +import java.util.Date; + +/** + * Author: wyouflf + * Time: 2014/05/26 + */ +public abstract class Loader { + + protected ProgressHandler progressHandler; + + public void setParams(final RequestParams params) { + } + + public void setProgressHandler(final ProgressHandler callbackHandler) { + this.progressHandler = callbackHandler; + } + + protected void saveStringCache(UriRequest request, String resultStr) { + saveCacheInternal(request, resultStr, null); + } + + protected void saveByteArrayCache(UriRequest request, byte[] resultData) { + saveCacheInternal(request, null, resultData); + } + + public abstract Loader newInstance(); + + public abstract T load(final UriRequest request) throws Throwable; + + public abstract T loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable; + + public abstract void save2Cache(final UriRequest request); + + private void saveCacheInternal(UriRequest request, String resultStr, byte[] resultData) { + if (!TextUtils.isEmpty(resultStr) || (resultData != null && resultData.length > 0)) { + DiskCacheEntity entity = new DiskCacheEntity(); + entity.setKey(request.getCacheKey()); + entity.setLastAccess(System.currentTimeMillis()); + entity.setEtag(request.getETag()); + entity.setExpires(request.getExpiration()); + entity.setLastModify(new Date(request.getLastModified())); + entity.setTextContent(resultStr); + entity.setBytesContent(resultData); + LruDiskCache.getDiskCache(request.getParams().getCacheDirName()).put(entity); + } + } +} diff --git a/app/src/main/java/org/xutils/http/loader/LoaderFactory.java b/app/src/main/java/org/xutils/http/loader/LoaderFactory.java new file mode 100644 index 0000000..578de26 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/LoaderFactory.java @@ -0,0 +1,56 @@ +package org.xutils.http.loader; + + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.HashMap; + +/** + * Author: wyouflf + * Time: 2014/05/26 + */ +public final class LoaderFactory { + + private LoaderFactory() { + } + + /** + * key: loadType + */ + private static final HashMap converterHashMap = new HashMap(); + + static { + converterHashMap.put(JSONObject.class, new JSONObjectLoader()); + converterHashMap.put(JSONArray.class, new JSONArrayLoader()); + converterHashMap.put(String.class, new StringLoader()); + converterHashMap.put(File.class, new FileLoader()); + converterHashMap.put(byte[].class, new ByteArrayLoader()); + converterHashMap.put(InputStream.class, new InputStreamLoader()); + + BooleanLoader booleanLoader = new BooleanLoader(); + converterHashMap.put(boolean.class, booleanLoader); + converterHashMap.put(Boolean.class, booleanLoader); + + IntegerLoader integerLoader = new IntegerLoader(); + converterHashMap.put(int.class, integerLoader); + converterHashMap.put(Integer.class, integerLoader); + } + + public static Loader getLoader(Type type) { + Loader result = converterHashMap.get(type); + if (result == null) { + result = new ObjectLoader(type); + } else { + result = result.newInstance(); + } + return result; + } + + public static void registerLoader(Type type, Loader loader) { + converterHashMap.put(type, loader); + } +} diff --git a/app/src/main/java/org/xutils/http/loader/ObjectLoader.java b/app/src/main/java/org/xutils/http/loader/ObjectLoader.java new file mode 100644 index 0000000..046a104 --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/ObjectLoader.java @@ -0,0 +1,105 @@ +package org.xutils.http.loader; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.common.util.ParameterizedTypeUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.annotation.HttpResponse; +import org.xutils.http.app.ResponseParser; +import org.xutils.http.request.UriRequest; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; + +/** + * Created by lei.jiao on 2014/6/27. + * 其他对象的下载转换. + * 使用类型上的@HttpResponse注解信息进行数据转换. + */ +/*package*/ class ObjectLoader extends Loader { + + private final Type objectType; + private final Class objectClass; + private final ResponseParser parser; + private final Loader innerLoader; + + public ObjectLoader(Type objectType) { + this.objectType = objectType; + + // check loadType & resultType + if (objectType instanceof ParameterizedType) { + objectClass = (Class) ((ParameterizedType) objectType).getRawType(); + } else if (objectType instanceof TypeVariable) { + throw new IllegalArgumentException( + "not support callback type " + objectType.toString()); + } else { + objectClass = (Class) objectType; + } + + HttpResponse response = null; + Type itemType = objectType; + if (List.class.equals(objectClass)) { + itemType = ParameterizedTypeUtil.getParameterizedType(this.objectType, List.class, 0); + Class itemClass = null; + if (itemType instanceof ParameterizedType) { + itemClass = (Class) ((ParameterizedType) itemType).getRawType(); + } else if (itemType instanceof TypeVariable) { + throw new IllegalArgumentException( + "not support callback type " + itemType.toString()); + } else { + itemClass = (Class) itemType; + } + + response = itemClass.getAnnotation(HttpResponse.class); + } else { + response = objectClass.getAnnotation(HttpResponse.class); + } + if (response != null) { + try { + Class parserCls = response.parser(); + this.parser = parserCls.newInstance(); + this.innerLoader = LoaderFactory.getLoader( + ParameterizedTypeUtil.getParameterizedType(parserCls, ResponseParser.class, 0)); + } catch (Throwable ex) { + throw new RuntimeException("create parser error", ex); + } + } else { + throw new IllegalArgumentException("not found @HttpResponse from " + itemType); + } + + if (innerLoader instanceof ObjectLoader) { + throw new IllegalArgumentException("not support callback type " + itemType); + } + } + + @Override + public Loader newInstance() { + throw new IllegalAccessError("use constructor create ObjectLoader."); + } + + @Override + public void setParams(final RequestParams params) { + this.innerLoader.setParams(params); + } + + @Override + @SuppressWarnings("unchecked") + public Object load(final UriRequest request) throws Throwable { + request.setResponseParser(parser); + Object innerLoaderResult = innerLoader.load(request); + return parser.parse(objectType, objectClass, innerLoaderResult); + } + + @Override + @SuppressWarnings("unchecked") + public Object loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + Object innerLoaderResult = innerLoader.loadFromCache(cacheEntity); + return parser.parse(objectType, objectClass, innerLoaderResult); + } + + @Override + public void save2Cache(UriRequest request) { + innerLoader.save2Cache(request); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/xutils/http/loader/StringLoader.java b/app/src/main/java/org/xutils/http/loader/StringLoader.java new file mode 100644 index 0000000..a6a237d --- /dev/null +++ b/app/src/main/java/org/xutils/http/loader/StringLoader.java @@ -0,0 +1,54 @@ +package org.xutils.http.loader; + +import android.text.TextUtils; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.common.util.IOUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.request.UriRequest; + +/** + * Author: wyouflf + * Time: 2014/05/30 + */ +/*package*/ class StringLoader extends Loader { + + private String charset = "UTF-8"; + private String resultStr = null; + + @Override + public Loader newInstance() { + return new StringLoader(); + } + + @Override + public void setParams(final RequestParams params) { + if (params != null) { + String charset = params.getCharset(); + if (!TextUtils.isEmpty(charset)) { + this.charset = charset; + } + } + } + + @Override + public String load(final UriRequest request) throws Throwable { + request.sendRequest(); + resultStr = IOUtil.readStr(request.getInputStream(), charset); + return resultStr; + } + + @Override + public String loadFromCache(final DiskCacheEntity cacheEntity) throws Throwable { + if (cacheEntity != null) { + return cacheEntity.getTextContent(); + } + + return null; + } + + @Override + public void save2Cache(UriRequest request) { + saveStringCache(request, resultStr); + } +} diff --git a/app/src/main/java/org/xutils/http/request/AssetsRequest.java b/app/src/main/java/org/xutils/http/request/AssetsRequest.java new file mode 100644 index 0000000..545f765 --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/AssetsRequest.java @@ -0,0 +1,31 @@ +package org.xutils.http.request; + +import android.content.Context; + +import org.xutils.http.RequestParams; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; + +/** + * Created by wyouflf on 15/11/4. + * Assets资源文件请求 + */ +public class AssetsRequest extends ResRequest { + + public AssetsRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + Context context = params.getContext(); + String assetsPath = queryUrl.replace("assets://", ""); + inputStream = context.getResources().getAssets().open(assetsPath); + contentLength = inputStream.available(); + } + return inputStream; + } +} diff --git a/app/src/main/java/org/xutils/http/request/HttpRequest.java b/app/src/main/java/org/xutils/http/request/HttpRequest.java new file mode 100644 index 0000000..a47adbf --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/HttpRequest.java @@ -0,0 +1,466 @@ +package org.xutils.http.request; + +import android.annotation.TargetApi; +import android.os.Build; +import android.text.TextUtils; +import org.xutils.cache.DiskCacheEntity; +import org.xutils.cache.LruDiskCache; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.KeyValue; +import org.xutils.common.util.LogUtil; +import org.xutils.ex.HttpException; +import org.xutils.http.HttpMethod; +import org.xutils.http.RequestParams; +import org.xutils.http.body.ProgressBody; +import org.xutils.http.body.RequestBody; +import org.xutils.http.cookie.DbCookieStore; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.net.*; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Created by wyouflf on 15/7/23. + * Uri请求发送和数据接收 + */ +public class HttpRequest extends UriRequest { + + private String cacheKey = null; + private boolean isLoading = false; + private InputStream inputStream = null; + private HttpURLConnection connection = null; + private int responseCode = 0; + + // cookie manager + private static final CookieManager COOKIE_MANAGER = + new CookieManager(DbCookieStore.INSTANCE, CookiePolicy.ACCEPT_ALL); + + public HttpRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + // build query + @Override + protected String buildQueryUrl(RequestParams params) throws IOException { + String uri = params.getUri(); + StringBuilder queryBuilder = new StringBuilder(uri); + List queryParams = params.getQueryStringParams(); + + if (queryParams != null && !queryParams.isEmpty()) { + if (!uri.contains("?")) { + queryBuilder.append("?"); + } else if (!uri.endsWith("?")) { + queryBuilder.append("&"); + } + + for (KeyValue kv : queryParams) { + String name = kv.key; + String value = kv.getValueStrOrNull(); + if (!TextUtils.isEmpty(name) && value != null) { + queryBuilder.append(URLEncoder.encode(name, params.getCharset()).replaceAll("\\+", "%20")) + .append("=") + .append(URLEncoder.encode(value, params.getCharset()).replaceAll("\\+", "%20")) + .append("&"); + } + } + + if (queryBuilder.charAt(queryBuilder.length() - 1) == '&') { + queryBuilder.deleteCharAt(queryBuilder.length() - 1); + } + + if (queryBuilder.charAt(queryBuilder.length() - 1) == '?') { + queryBuilder.deleteCharAt(queryBuilder.length() - 1); + } + } + + return queryBuilder.toString(); + } + + @Override + public String getRequestUri() { + String result = queryUrl; + if (connection != null) { + URL url = connection.getURL(); + if (url != null) { + result = url.toString(); + } + } + return result; + } + + /** + * invoke via Loader + */ + @Override + @TargetApi(Build.VERSION_CODES.KITKAT) + public void sendRequest() throws Throwable { + isLoading = false; + responseCode = 0; + + URL url = new URL(queryUrl); + { // init connection + Proxy proxy = params.getProxy(); + if (proxy != null) { + connection = (HttpURLConnection) url.openConnection(proxy); + } else { + connection = (HttpURLConnection) url.openConnection(); + } + + // try to fix bug: accidental EOFException before API 19 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + connection.setRequestProperty("Connection", "close"); + } + + connection.setReadTimeout(params.getReadTimeout()); + connection.setConnectTimeout(params.getConnectTimeout()); + connection.setInstanceFollowRedirects(params.getRedirectHandler() == null); + if (connection instanceof HttpsURLConnection) { + SSLSocketFactory sslSocketFactory = params.getSslSocketFactory(); + if (sslSocketFactory != null) { + ((HttpsURLConnection) connection).setSSLSocketFactory(sslSocketFactory); + } + + HostnameVerifier hostnameVerifier = params.getHostnameVerifier(); + if (hostnameVerifier != null) { + ((HttpsURLConnection) connection).setHostnameVerifier(hostnameVerifier); + } + } + } + + if (params.isUseCookie()) {// add cookies + try { + Map> singleMap = + COOKIE_MANAGER.get(url.toURI(), new HashMap>(0)); + List cookies = singleMap.get("Cookie"); + if (cookies != null) { + connection.setRequestProperty("Cookie", TextUtils.join(";", cookies)); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + {// add headers + List headers = params.getHeaders(); + if (headers != null) { + for (RequestParams.Header header : headers) { + String name = header.key; + String value = header.getValueStrOrNull(); + if (!TextUtils.isEmpty(name)) { + if (header.setHeader) { + connection.setRequestProperty(name, value); + } else { + connection.addRequestProperty(name, value); + } + } + } + } + } + + // intercept response + if (responseParser != null) { + responseParser.beforeRequest(this); + } + if (requestInterceptListener != null) { + requestInterceptListener.beforeRequest(this); + } + + { // write body + HttpMethod method = params.getMethod(); + try { + connection.setRequestMethod(method.toString()); + } catch (ProtocolException ex) { + try { // fix: HttpURLConnection not support PATCH method. + Field methodField = HttpURLConnection.class.getDeclaredField("method"); + methodField.setAccessible(true); + methodField.set(connection, method.toString()); + } catch (Throwable ignored) { + throw ex; + } + } + if (HttpMethod.permitsRequestBody(method)) { + RequestBody body = params.getRequestBody(); + if (body != null) { + if (body instanceof ProgressBody) { + ((ProgressBody) body).setProgressHandler(progressHandler); + } + String contentType = body.getContentType(); + if (!TextUtils.isEmpty(contentType)) { + connection.setRequestProperty("Content-Type", contentType); + } + boolean isChunkedMode = false; + long contentLength = body.getContentLength(); + if (contentLength < 0) { + connection.setChunkedStreamingMode(256 * 1024); + isChunkedMode = true; + } else { + if (contentLength < Integer.MAX_VALUE) { + connection.setFixedLengthStreamingMode((int) contentLength); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + connection.setFixedLengthStreamingMode(contentLength); + } else { + connection.setChunkedStreamingMode(256 * 1024); + isChunkedMode = true; + } + } + + if (isChunkedMode) { + connection.setRequestProperty("Transfer-Encoding", "chunked"); + } else { + connection.setRequestProperty("Content-Length", String.valueOf(contentLength)); + } + + connection.setDoOutput(true); + body.writeTo(connection.getOutputStream()); + } + } + } + + if (params.isUseCookie()) { // save cookies + try { + Map> headers = connection.getHeaderFields(); + if (headers != null) { + COOKIE_MANAGER.put(url.toURI(), headers); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + // check response code + responseCode = connection.getResponseCode(); + { // intercept response + if (responseParser != null) { + responseParser.afterRequest(this); + } + if (requestInterceptListener != null) { + requestInterceptListener.afterRequest(this); + } + } + if (responseCode == 204 || responseCode == 205) { // empty content + throw new HttpException(responseCode, this.getResponseMessage()); + } else if (responseCode >= 300) { + HttpException httpException = new HttpException(responseCode, this.getResponseMessage()); + try { + httpException.setResult(IOUtil.readStr(this.getInputStream(), params.getCharset())); + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } + LogUtil.e(httpException.toString() + ", url: " + queryUrl); + throw httpException; + } + + isLoading = true; + } + + @Override + public boolean isLoading() { + return isLoading; + } + + @Override + public String getCacheKey() { + if (cacheKey == null) { + + cacheKey = params.getCacheKey(); + + if (TextUtils.isEmpty(cacheKey)) { + cacheKey = params.toString(); + } + } + return cacheKey; + } + + @Override + public Object loadResult() throws Throwable { + isLoading = true; + return super.loadResult(); + } + + /** + * 尝试从缓存获取结果, 并为请求头加入缓存控制参数. + */ + @Override + public Object loadResultFromCache() throws Throwable { + isLoading = true; + DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName()) + .setMaxSize(params.getCacheSize()) + .get(this.getCacheKey()); + + if (cacheEntity != null) { + if (HttpMethod.permitsCache(params.getMethod())) { + Date lastModified = cacheEntity.getLastModify(); + if (lastModified.getTime() > 0) { + params.setHeader("If-Modified-Since", toGMTString(lastModified)); + } + String eTag = cacheEntity.getEtag(); + if (!TextUtils.isEmpty(eTag)) { + params.setHeader("If-None-Match", eTag); + } + } + return loader.loadFromCache(cacheEntity); + } else { + return null; + } + } + + @Override + public void clearCacheHeader() { + params.setHeader("If-Modified-Since", null); + params.setHeader("If-None-Match", null); + } + + @Override + public InputStream getInputStream() throws IOException { + if (connection != null && inputStream == null) { + inputStream = connection.getResponseCode() >= 400 ? + connection.getErrorStream() : connection.getInputStream(); + } + return inputStream; + } + + @Override + public void close() throws IOException { + if (inputStream != null) { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + if (connection != null) { + connection.disconnect(); + //connection = null; + } + } + + @Override + public long getContentLength() { + long result = -1; + if (connection != null) { + try { + String value = connection.getHeaderField("content-length"); + if (value != null) { + result = Long.parseLong(value); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + if (result < 1) { + try { + result = this.getInputStream().available(); + } catch (Throwable ignored) { + } + } + return result; + } + + @Override + public int getResponseCode() throws IOException { + if (connection != null) { + return responseCode; + } else { + if (this.getInputStream() != null) { + return 200; + } else { + return 404; + } + } + } + + @Override + public String getResponseMessage() throws IOException { + if (connection != null) { + return URLDecoder.decode(connection.getResponseMessage(), params.getCharset()); + } else { + return null; + } + } + + @Override + public long getExpiration() { + if (connection == null) return -1L; + + long expiration = -1L; + + // from max-age + String cacheControl = connection.getHeaderField("Cache-Control"); + if (!TextUtils.isEmpty(cacheControl)) { + StringTokenizer tok = new StringTokenizer(cacheControl, ","); + while (tok.hasMoreTokens()) { + String token = tok.nextToken().trim().toLowerCase(); + if (token.startsWith("max-age")) { + int eqIdx = token.indexOf('='); + if (eqIdx > 0) { + try { + String value = token.substring(eqIdx + 1).trim(); + long seconds = Long.parseLong(value); + if (seconds > 0L) { + expiration = System.currentTimeMillis() + seconds * 1000L; + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + break; + } + } + } + + // from expires + if (expiration <= 0L) { + expiration = connection.getExpiration(); + } + + if (expiration <= 0L && params.getCacheMaxAge() > 0L) { + expiration = System.currentTimeMillis() + params.getCacheMaxAge(); + } + + if (expiration <= 0L) { + expiration = Long.MAX_VALUE; + } + return expiration; + } + + @Override + public long getLastModified() { + return getHeaderFieldDate("Last-Modified", System.currentTimeMillis()); + } + + @Override + public String getETag() { + if (connection == null) return null; + return connection.getHeaderField("ETag"); + } + + @Override + public String getResponseHeader(String name) { + if (connection == null) return null; + return connection.getHeaderField(name); + } + + @Override + public Map> getResponseHeaders() { + if (connection == null) return null; + return connection.getHeaderFields(); + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + if (connection == null) return defaultValue; + return connection.getHeaderFieldDate(name, defaultValue); + } + + private static String toGMTString(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat( + "EEE, dd MMM y HH:mm:ss 'GMT'", Locale.US); + TimeZone gmtZone = TimeZone.getTimeZone("GMT"); + sdf.setTimeZone(gmtZone); + return sdf.format(date); + } +} diff --git a/app/src/main/java/org/xutils/http/request/LocalFileRequest.java b/app/src/main/java/org/xutils/http/request/LocalFileRequest.java new file mode 100644 index 0000000..98e09a2 --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/LocalFileRequest.java @@ -0,0 +1,134 @@ +package org.xutils.http.request; + +import org.xutils.common.util.IOUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.loader.FileLoader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/11/4. + * 本地文件请求 + */ +public class LocalFileRequest extends UriRequest { + + private InputStream inputStream; + + public LocalFileRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public void sendRequest() throws Throwable { + + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public String getCacheKey() { + return queryUrl; + } + + @Override + public Object loadResult() throws Throwable { + if (loader instanceof FileLoader) { + return getFile(); + } + return this.loader.load(this); + } + + @Override + public Object loadResultFromCache() throws Throwable { + return null; + } + + @Override + public void clearCacheHeader() { + + } + + @Override + public void save2Cache() { + + } + + private File getFile() { + String filePath = null; + if (queryUrl.startsWith("file:")) { + filePath = queryUrl.substring("file:".length()); + } else { + filePath = queryUrl; + } + // filePath开始位置多余的"/"或被自动去掉 + return new File(filePath); + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + inputStream = new FileInputStream(getFile()); + } + return inputStream; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + + @Override + public long getContentLength() { + return getFile().length(); + } + + @Override + public int getResponseCode() throws IOException { + return getFile().exists() ? 200 : 404; + } + + @Override + public String getResponseMessage() throws IOException { + return null; + } + + @Override + public long getExpiration() { + return -1; + } + + @Override + public long getLastModified() { + return getFile().lastModified(); + } + + @Override + public String getETag() { + return null; + } + + @Override + public String getResponseHeader(String name) { + return null; + } + + @Override + public Map> getResponseHeaders() { + return null; + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + return defaultValue; + } +} diff --git a/app/src/main/java/org/xutils/http/request/ResRequest.java b/app/src/main/java/org/xutils/http/request/ResRequest.java new file mode 100644 index 0000000..9dd4a65 --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/ResRequest.java @@ -0,0 +1,175 @@ +package org.xutils.http.request; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.text.TextUtils; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.cache.LruDiskCache; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.http.RequestParams; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/11/4. + * 本地资源请求 + */ +public class ResRequest extends UriRequest { + + private static long lastModifiedTime = 0; + protected long contentLength = 0; + protected InputStream inputStream; + + public ResRequest(RequestParams params, Type loadType) throws Throwable { + super(params, loadType); + } + + @Override + public void sendRequest() throws Throwable { + + } + + @Override + public boolean isLoading() { + return true; + } + + @Override + public String getCacheKey() { + return queryUrl; + } + + @Override + public Object loadResult() throws Throwable { + return this.loader.load(this); + } + + @Override + public Object loadResultFromCache() throws Throwable { + DiskCacheEntity cacheEntity = LruDiskCache.getDiskCache(params.getCacheDirName()) + .setMaxSize(params.getCacheSize()) + .get(this.getCacheKey()); + + if (cacheEntity != null) { + Date lastModifiedDate = cacheEntity.getLastModify(); + if (lastModifiedDate == null || lastModifiedDate.getTime() < getLastModified()) { + return null; + } + return loader.loadFromCache(cacheEntity); + } else { + return null; + } + } + + @Override + public void clearCacheHeader() { + + } + + private int getResId() { + int resId = 0; + String resIdStr = queryUrl.substring("res:".length()); + resIdStr = resIdStr.replace("/", ""); + if (TextUtils.isDigitsOnly(resIdStr)) { + resId = Integer.parseInt(resIdStr); + } + + if (resId <= 0) { + throw new IllegalArgumentException("resId not found in url:" + queryUrl); + } + + return resId; + } + + @Override + public InputStream getInputStream() throws IOException { + if (inputStream == null) { + Context context = params.getContext(); + inputStream = context.getResources().openRawResource(getResId()); + contentLength = inputStream.available(); + } + return inputStream; + } + + @Override + public void close() throws IOException { + IOUtil.closeQuietly(inputStream); + inputStream = null; + } + + @Override + public long getContentLength() { + try { + getInputStream(); + return contentLength; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return -1; + } + + @Override + public int getResponseCode() throws IOException { + return getInputStream() != null ? 200 : 404; + } + + @Override + public String getResponseMessage() throws IOException { + return null; + } + + @Override + public long getExpiration() { + return Long.MAX_VALUE; + } + + @Override + public long getLastModified() { + if (lastModifiedTime == 0) { + try { + Context context = params.getContext(); + ApplicationInfo appInfo = context.getApplicationInfo(); + File appFile = new File(appInfo.sourceDir); + if (appFile.exists()) { + lastModifiedTime = appFile.lastModified(); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + lastModifiedTime = 0; + } finally { + if (lastModifiedTime == 0) { + lastModifiedTime = System.currentTimeMillis(); + } + } + } + return lastModifiedTime; + } + + @Override + public String getETag() { + return null; + } + + @Override + public String getResponseHeader(String name) { + return null; + } + + @Override + public Map> getResponseHeaders() { + return null; + } + + @Override + public long getHeaderFieldDate(String name, long defaultValue) { + return defaultValue; + } +} diff --git a/app/src/main/java/org/xutils/http/request/UriRequest.java b/app/src/main/java/org/xutils/http/request/UriRequest.java new file mode 100644 index 0000000..00ae40b --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/UriRequest.java @@ -0,0 +1,129 @@ +package org.xutils.http.request; + +import org.xutils.common.util.LogUtil; +import org.xutils.http.ProgressHandler; +import org.xutils.http.RequestParams; +import org.xutils.http.app.RequestInterceptListener; +import org.xutils.http.app.ResponseParser; +import org.xutils.http.loader.Loader; +import org.xutils.http.loader.LoaderFactory; +import org.xutils.x; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.List; +import java.util.Map; + +/** + * Created by wyouflf on 15/7/23. + * Uri请求发送和数据接收 + */ +public abstract class UriRequest implements Closeable { + + protected final String queryUrl; + protected final RequestParams params; + protected final Loader loader; + + protected ProgressHandler progressHandler = null; + protected ResponseParser responseParser = null; + protected RequestInterceptListener requestInterceptListener = null; + + public UriRequest(RequestParams params, Type loadType) throws Throwable { + this.params = params; + this.queryUrl = buildQueryUrl(params); + this.loader = LoaderFactory.getLoader(loadType); + this.loader.setParams(params); + } + + // build query + protected String buildQueryUrl(RequestParams params) throws IOException { + return params.getUri(); + } + + public void setProgressHandler(ProgressHandler progressHandler) { + this.progressHandler = progressHandler; + this.loader.setProgressHandler(progressHandler); + } + + public void setResponseParser(ResponseParser responseParser) { + this.responseParser = responseParser; + } + + public void setRequestInterceptListener(RequestInterceptListener requestInterceptListener) { + this.requestInterceptListener = requestInterceptListener; + } + + public RequestParams getParams() { + return params; + } + + public String getRequestUri() { + return queryUrl; + } + + /** + * invoke via Loader + */ + public abstract void sendRequest() throws Throwable; + + public abstract boolean isLoading(); + + public abstract String getCacheKey(); + + /** + * 由loader发起请求, 拿到结果. + */ + public Object loadResult() throws Throwable { + return this.loader.load(this); + } + + /** + * 尝试从缓存获取结果, 并为请求头加入缓存控制参数. + */ + public abstract Object loadResultFromCache() throws Throwable; + + public abstract void clearCacheHeader(); + + public void save2Cache() { + x.task().run(new Runnable() { + @Override + public void run() { + try { + loader.save2Cache(UriRequest.this); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + }); + } + + public abstract InputStream getInputStream() throws IOException; + + @Override + public abstract void close() throws IOException; + + public abstract long getContentLength(); + + public abstract int getResponseCode() throws IOException; + + public abstract String getResponseMessage() throws IOException; + + public abstract long getExpiration(); + + public abstract long getLastModified(); + + public abstract String getETag(); + + public abstract String getResponseHeader(String name); + + public abstract Map> getResponseHeaders(); + + public abstract long getHeaderFieldDate(String name, long defaultValue); + + @Override + public String toString() { + return getRequestUri(); + } +} diff --git a/app/src/main/java/org/xutils/http/request/UriRequestFactory.java b/app/src/main/java/org/xutils/http/request/UriRequestFactory.java new file mode 100644 index 0000000..3ac2c57 --- /dev/null +++ b/app/src/main/java/org/xutils/http/request/UriRequestFactory.java @@ -0,0 +1,81 @@ +package org.xutils.http.request; + +import android.text.TextUtils; + +import org.xutils.common.util.LogUtil; +import org.xutils.http.RequestParams; +import org.xutils.http.app.RequestTracker; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Type; +import java.util.HashMap; + +/** + * Created by wyouflf on 15/11/4. + * Uri请求创建工厂 + */ +public final class UriRequestFactory { + + private static Class defaultTrackerCls; + + private static final HashMap> + SCHEME_CLS_MAP = new HashMap>(); + + private UriRequestFactory() { + } + + public static UriRequest getUriRequest(RequestParams params, Type loadType) throws Throwable { + + // get scheme + String scheme = null; + String uri = params.getUri(); + int index = uri.indexOf(":"); + if (uri.startsWith("/")) { + scheme = "file"; + } else if (index > 0) { + scheme = uri.substring(0, index); + } + + // get UriRequest + if (!TextUtils.isEmpty(scheme)) { + scheme = scheme.toLowerCase(); + Class cls = SCHEME_CLS_MAP.get(scheme); + if (cls != null) { + Constructor constructor + = cls.getConstructor(RequestParams.class, Type.class); + return constructor.newInstance(params, loadType); + } else { + if (scheme.startsWith("http")) { + return new HttpRequest(params, loadType); + } else if (scheme.equals("assets")) { + return new AssetsRequest(params, loadType); + } else if (scheme.equals("file")) { + return new LocalFileRequest(params, loadType); + } else if (scheme.equals("res")) { + return new ResRequest(params, loadType); + } else { + throw new IllegalArgumentException("The url not be support: " + uri); + } + } + } else { + throw new IllegalArgumentException("The url not be support: " + uri); + } + } + + public static void registerDefaultTrackerClass(Class trackerCls) { + UriRequestFactory.defaultTrackerCls = trackerCls; + } + + public static RequestTracker getDefaultTracker() { + try { + return defaultTrackerCls == null ? null : defaultTrackerCls.newInstance(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return null; + } + + public static void registerRequestClass(String scheme, Class uriRequestCls) { + SCHEME_CLS_MAP.put(scheme, uriRequestCls); + } +} diff --git a/app/src/main/java/org/xutils/image/AsyncDrawable.java b/app/src/main/java/org/xutils/image/AsyncDrawable.java new file mode 100644 index 0000000..d037aad --- /dev/null +++ b/app/src/main/java/org/xutils/image/AsyncDrawable.java @@ -0,0 +1,221 @@ +package org.xutils.image; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; + +import java.lang.ref.WeakReference; + +/** + * Author: wyouflf + * Date: 13-11-17 + * Time: 上午11:42 + */ +public final class AsyncDrawable extends Drawable { + + private final WeakReference imageLoaderReference; + + private Drawable baseDrawable; + + public AsyncDrawable(ImageLoader imageLoader, Drawable drawable) { + if (imageLoader == null) { + throw new IllegalArgumentException("imageLoader may not be null"); + } + baseDrawable = drawable; + while (baseDrawable instanceof AsyncDrawable) { + baseDrawable = ((AsyncDrawable) baseDrawable).baseDrawable; + } + imageLoaderReference = new WeakReference(imageLoader); + } + + public ImageLoader getImageLoader() { + return imageLoaderReference.get(); + } + + public void setBaseDrawable(Drawable baseDrawable) { + this.baseDrawable = baseDrawable; + } + + public Drawable getBaseDrawable() { + return baseDrawable; + } + + @Override + public void draw(Canvas canvas) { + if (baseDrawable != null) { + baseDrawable.draw(canvas); + } + } + + @Override + public void setAlpha(int i) { + if (baseDrawable != null) { + baseDrawable.setAlpha(i); + } + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + if (baseDrawable != null) { + baseDrawable.setColorFilter(colorFilter); + } + } + + @Override + public int getOpacity() { + return baseDrawable == null ? PixelFormat.TRANSLUCENT : baseDrawable.getOpacity(); + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + if (baseDrawable != null) { + baseDrawable.setBounds(left, top, right, bottom); + } + } + + @Override + public void setBounds(Rect bounds) { + if (baseDrawable != null) { + baseDrawable.setBounds(bounds); + } + } + + @Override + public void setChangingConfigurations(int configs) { + if (baseDrawable != null) { + baseDrawable.setChangingConfigurations(configs); + } + } + + @Override + public int getChangingConfigurations() { + return baseDrawable == null ? 0 : baseDrawable.getChangingConfigurations(); + } + + @Override + public void setDither(boolean dither) { + if (baseDrawable != null) { + baseDrawable.setDither(dither); + } + } + + @Override + public void setFilterBitmap(boolean filter) { + if (baseDrawable != null) { + baseDrawable.setFilterBitmap(filter); + } + } + + @Override + public void invalidateSelf() { + if (baseDrawable != null) { + baseDrawable.invalidateSelf(); + } + } + + @Override + public void scheduleSelf(Runnable what, long when) { + if (baseDrawable != null) { + baseDrawable.scheduleSelf(what, when); + } + } + + @Override + public void unscheduleSelf(Runnable what) { + if (baseDrawable != null) { + baseDrawable.unscheduleSelf(what); + } + } + + @Override + public void setColorFilter(int color, PorterDuff.Mode mode) { + if (baseDrawable != null) { + baseDrawable.setColorFilter(color, mode); + } + } + + @Override + public void clearColorFilter() { + if (baseDrawable != null) { + baseDrawable.clearColorFilter(); + } + } + + @Override + public boolean isStateful() { + return baseDrawable != null && baseDrawable.isStateful(); + } + + @Override + public boolean setState(int[] stateSet) { + return baseDrawable != null && baseDrawable.setState(stateSet); + } + + @Override + public int[] getState() { + return baseDrawable == null ? null : baseDrawable.getState(); + } + + @Override + public Drawable getCurrent() { + return baseDrawable == null ? null : baseDrawable.getCurrent(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + return baseDrawable != null && baseDrawable.setVisible(visible, restart); + } + + @Override + public Region getTransparentRegion() { + return baseDrawable == null ? null : baseDrawable.getTransparentRegion(); + } + + @Override + public int getIntrinsicWidth() { + return baseDrawable == null ? 0 : baseDrawable.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return baseDrawable == null ? 0 : baseDrawable.getIntrinsicHeight(); + } + + @Override + public int getMinimumWidth() { + return baseDrawable == null ? 0 : baseDrawable.getMinimumWidth(); + } + + @Override + public int getMinimumHeight() { + return baseDrawable == null ? 0 : baseDrawable.getMinimumHeight(); + } + + @Override + public boolean getPadding(Rect padding) { + return baseDrawable != null && baseDrawable.getPadding(padding); + } + + @Override + public Drawable mutate() { + return baseDrawable == null ? null : baseDrawable.mutate(); + } + + @Override + public ConstantState getConstantState() { + return baseDrawable == null ? null : baseDrawable.getConstantState(); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + ImageLoader imageLoader = this.getImageLoader(); + if (imageLoader != null) { + imageLoader.cancel(); + } + } +} diff --git a/app/src/main/java/org/xutils/image/GifDrawable.java b/app/src/main/java/org/xutils/image/GifDrawable.java new file mode 100644 index 0000000..563265b --- /dev/null +++ b/app/src/main/java/org/xutils/image/GifDrawable.java @@ -0,0 +1,117 @@ +package org.xutils.image; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Movie; +import android.graphics.PixelFormat; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; + +import org.xutils.common.util.LogUtil; + +public class GifDrawable extends Drawable implements Runnable, Animatable { + + private int byteCount; + private int rate = 100; + private volatile boolean running; + + private final Movie movie; + private final int duration; + private final long begin = SystemClock.uptimeMillis(); + + public GifDrawable(Movie movie, int byteCount) { + this.movie = movie; + this.byteCount = byteCount; + this.duration = movie.duration(); + } + + public int getDuration() { + return duration; + } + + public Movie getMovie() { + return movie; + } + + public int getByteCount() { + if (byteCount == 0) { + byteCount = (movie.width() * movie.height() * 3) * (5/*fake frame count*/); + } + return byteCount; + } + + public int getRate() { + return rate; + } + + public void setRate(int rate) { + this.rate = rate; + } + + @Override + public void draw(Canvas canvas) { + try { + int time = duration > 0 ? (int) (SystemClock.uptimeMillis() - begin) % duration : 0; + movie.setTime(time); + movie.draw(canvas, 0, 0); + start(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + @Override + public void start() { + if (!isRunning()) { + running = true; + run(); + } + } + + @Override + public void stop() { + if (isRunning()) { + running = false; + this.unscheduleSelf(this); + } + } + + @Override + public boolean isRunning() { + return running && duration > 0; + } + + @Override + public void run() { + if (duration > 0) { + this.invalidateSelf(); + this.scheduleSelf(this, SystemClock.uptimeMillis() + rate); + } + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public int getIntrinsicWidth() { + return movie.width(); + } + + @Override + public int getIntrinsicHeight() { + return movie.height(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + } + + @Override + public int getOpacity() { + return movie.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; + } + +} diff --git a/app/src/main/java/org/xutils/image/ImageAnimationHelper.java b/app/src/main/java/org/xutils/image/ImageAnimationHelper.java new file mode 100644 index 0000000..52433a2 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ImageAnimationHelper.java @@ -0,0 +1,56 @@ +package org.xutils.image; + +import android.graphics.drawable.Drawable; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; + +import org.xutils.common.util.LogUtil; + +import java.lang.reflect.Method; + +/** + * Created by wyouflf on 15/10/13. + * ImageView Animation Helper + */ +public final class ImageAnimationHelper { + + private final static Method cloneMethod; + + static { + Method method = null; + try { + method = Animation.class.getDeclaredMethod("clone"); + method.setAccessible(true); + } catch (Throwable ex) { + method = null; + LogUtil.w(ex.getMessage(), ex); + } + cloneMethod = method; + } + + private ImageAnimationHelper() { + } + + public static void fadeInDisplay(final ImageView imageView, Drawable drawable) { + AlphaAnimation fadeAnimation = new AlphaAnimation(0F, 1F); + fadeAnimation.setDuration(300); + fadeAnimation.setInterpolator(new DecelerateInterpolator()); + imageView.setImageDrawable(drawable); + imageView.startAnimation(fadeAnimation); + } + + public static void animationDisplay(ImageView imageView, Drawable drawable, Animation animation) { + imageView.setImageDrawable(drawable); + if (cloneMethod != null && animation != null) { + try { + imageView.startAnimation((Animation) cloneMethod.invoke(animation)); + } catch (Throwable ex) { + imageView.startAnimation(animation); + } + } else { + imageView.startAnimation(animation); + } + } +} diff --git a/app/src/main/java/org/xutils/image/ImageDecoder.java b/app/src/main/java/org/xutils/image/ImageDecoder.java new file mode 100644 index 0000000..7cedcd5 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ImageDecoder.java @@ -0,0 +1,586 @@ +package org.xutils.image; + +import android.annotation.SuppressLint; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Movie; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +import org.xutils.cache.DiskCacheEntity; +import org.xutils.cache.DiskCacheFile; +import org.xutils.cache.LruDiskCache; +import org.xutils.common.Callback; +import org.xutils.common.task.PriorityExecutor; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.x; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Created by wyouflf on 15/10/9. + * ImageDecoder for ImageLoader + */ +public final class ImageDecoder { + + private final static int BITMAP_DECODE_MAX_WORKER; + private final static AtomicInteger bitmapDecodeWorker = new AtomicInteger(0); + private final static Object bitmapDecodeLock = new Object(); + + private final static Object gifDecodeLock = new Object(); + private final static byte[] GIF_HEADER = new byte[]{'G', 'I', 'F'}; + + private final static Executor THUMB_CACHE_EXECUTOR = new PriorityExecutor(1, true); + private final static LruDiskCache THUMB_CACHE = LruDiskCache.getDiskCache("xUtils_img_thumb"); + + static { + int cpuCount = Runtime.getRuntime().availableProcessors(); + BITMAP_DECODE_MAX_WORKER = cpuCount > 4 ? 2 : 1; + } + + private ImageDecoder() { + } + + /*package*/ + static void clearCacheFiles() { + THUMB_CACHE.clearCacheFiles(); + } + + /** + * decode image file for ImageLoader + */ + /*package*/ + static Drawable decodeFileWithLock(final File file, + final ImageOptions options, + final Callback.Cancelable cancelable) throws IOException { + if (file == null || !file.exists() || file.length() < 1) return null; + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + Drawable result = null; + if (!options.isIgnoreGif() && isGif(file)) { + Movie movie = null; + synchronized (gifDecodeLock) { // decode with lock + movie = decodeGif(file, options, cancelable); + } + if (movie != null) { + result = new GifDrawable(movie, (int) file.length()); + ((GifDrawable) result).setRate(options.getGifRate()); + } + } else { + Bitmap bitmap = null; + { // decode with lock + boolean decodeStarted = false; + try { + synchronized (bitmapDecodeLock) { + while (bitmapDecodeWorker.get() >= BITMAP_DECODE_MAX_WORKER + && (cancelable == null || !cancelable.isCancelled())) { + try { + bitmapDecodeLock.wait(); + } catch (InterruptedException iex) { + throw new Callback.CancelledException("cancelled during decode image"); + } catch (Throwable ignored) { + } + } + } + + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + decodeStarted = true; + bitmapDecodeWorker.incrementAndGet(); + // get from thumb cache + if (options.isCompress()) { + bitmap = getThumbCache(file, options); + } + if (bitmap == null) { + bitmap = decodeBitmap(file, options, cancelable); + // save to thumb cache + if (bitmap != null && options.isCompress()) { + final Bitmap finalBitmap = bitmap; + THUMB_CACHE_EXECUTOR.execute(new Runnable() { + @Override + public void run() { + saveThumbCache(file, options, finalBitmap); + } + }); + } + } + } finally { + if (decodeStarted) { + bitmapDecodeWorker.decrementAndGet(); + } + synchronized (bitmapDecodeLock) { + bitmapDecodeLock.notifyAll(); + } + } + } + if (bitmap != null) { + result = new ReusableBitmapDrawable(x.app().getResources(), bitmap); + } + } + return result; + } + + public static boolean isGif(File file) { + FileInputStream in = null; + try { + in = new FileInputStream(file); + byte[] header = IOUtil.readBytes(in, 0, 3); + return Arrays.equals(GIF_HEADER, header); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(in); + } + + return false; + } + + /** + * 转化文件为Bitmap. + */ + public static Bitmap decodeBitmap(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException { + {// check params + if (file == null || !file.exists() || file.length() < 1) return null; + if (options == null) { + options = ImageOptions.DEFAULT; + } + if (options.getMaxWidth() <= 0 || options.getMaxHeight() <= 0) { + options.optimizeMaxSize(null); + } + } + + Bitmap result = null; + try { + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + // prepare bitmap options + final BitmapFactory.Options bitmapOps = new BitmapFactory.Options(); + bitmapOps.inJustDecodeBounds = true; + bitmapOps.inPurgeable = true; + bitmapOps.inInputShareable = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOps); + bitmapOps.inJustDecodeBounds = false; + bitmapOps.inPreferredConfig = options.getConfig(); + int rotateAngle = 0; + int rawWidth = bitmapOps.outWidth; + int rawHeight = bitmapOps.outHeight; + int optionWith = options.getWidth(); + int optionHeight = options.getHeight(); + if (options.isAutoRotate()) { + rotateAngle = getRotateAngle(file.getAbsolutePath()); + if ((rotateAngle / 90) % 2 == 1) { + rawWidth = bitmapOps.outHeight; + rawHeight = bitmapOps.outWidth; + } + } + if (!options.isCrop() && optionWith > 0 && optionHeight > 0) { + if ((rotateAngle / 90) % 2 == 1) { + bitmapOps.outWidth = optionHeight; + bitmapOps.outHeight = optionWith; + } else { + bitmapOps.outWidth = optionWith; + bitmapOps.outHeight = optionHeight; + } + } + bitmapOps.inSampleSize = calculateSampleSize( + rawWidth, rawHeight, + options.getMaxWidth(), options.getMaxHeight()); + + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + + // decode file + Bitmap bitmap = null; + bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOps); + if (bitmap == null) { + throw new IOException("decode image error"); + } + + { // 旋转和缩放处理 + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + if (rotateAngle != 0) { + bitmap = rotate(bitmap, rotateAngle, true); + } + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + if (options.isCrop() && optionWith > 0 && optionHeight > 0) { + bitmap = cut2ScaleSize(bitmap, optionWith, optionHeight, true); + } + } + + if (bitmap == null) { + throw new IOException("decode image error"); + } + + { // 圆角和方块处理 + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + if (options.isCircular()) { + bitmap = cut2Circular(bitmap, true); + } else if (options.getRadius() > 0) { + bitmap = cut2RoundCorner(bitmap, options.getRadius(), options.isSquare(), true); + } else if (options.isSquare()) { + bitmap = cut2Square(bitmap, true); + } + } + + if (bitmap == null) { + throw new IOException("decode image error"); + } + + result = bitmap; + } catch (Callback.CancelledException ex) { + throw ex; + } catch (IOException ex) { + throw ex; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + result = null; + } + + return result; + } + + /** + * 转换文件为Movie, 可用于创建GifDrawable. + */ + public static Movie decodeGif(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException { + {// check params + if (file == null || !file.exists() || file.length() < 1) return null; + /*if (options == null) { + options = ImageOptions.DEFAULT; // not use + } + if (options.getMaxWidth() <= 0 || options.getMaxHeight() <= 0) { + options.optimizeMaxSize(null); + }*/ + } + + try { + if (cancelable != null && cancelable.isCancelled()) { + throw new Callback.CancelledException("cancelled during decode image"); + } + Movie movie = Movie.decodeFile(file.getAbsolutePath()); + if (movie == null) { + throw new IOException("decode image error"); + } + return movie; + } catch (Callback.CancelledException ex) { + throw ex; + } catch (IOException ex) { + throw ex; + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + return null; + } + } + + /** + * 计算压缩采样倍数 + * + * @param rawWidth 图片宽度 + * @param rawHeight 图片高度 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return 压缩采样倍数 + */ + public static int calculateSampleSize(final int rawWidth, final int rawHeight, + final int maxWidth, final int maxHeight) { + int sampleSize = 1; + + if (rawWidth > maxWidth || rawHeight > maxHeight) { + if (rawWidth > rawHeight) { + sampleSize = Math.round((float) rawHeight / (float) maxHeight); + } else { + sampleSize = Math.round((float) rawWidth / (float) maxWidth); + } + + if (sampleSize < 1) { + sampleSize = 1; + } + + final float totalPixels = rawWidth * rawHeight; + + final float maxTotalPixels = maxWidth * maxHeight * 2; + + while (totalPixels / (sampleSize * sampleSize) > maxTotalPixels) { + sampleSize++; + } + } + return sampleSize; + } + + /** + * 裁剪方形图片 + * + * @param recycleSource 是否裁剪成功后销毁原图 + */ + public static Bitmap cut2Square(Bitmap source, boolean recycleSource) { + int width = source.getWidth(); + int height = source.getHeight(); + if (width == height) { + return source; + } + + int squareWith = Math.min(width, height); + Bitmap result = Bitmap.createBitmap(source, (width - squareWith) / 2, + (height - squareWith) / 2, squareWith, squareWith); + if (result != null) { + if (recycleSource && result != source) { + source.recycle(); + source = null; + } + } else { + result = source; + } + return result; + } + + /** + * 裁剪圆形图片 + * + * @param recycleSource 是否裁剪成功后销毁原图 + */ + public static Bitmap cut2Circular(Bitmap source, boolean recycleSource) { + int width = source.getWidth(); + int height = source.getHeight(); + int diameter = Math.min(width, height); + Paint paint = new Paint(); + paint.setAntiAlias(true); + Bitmap result = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888); + if (result != null) { + Canvas canvas = new Canvas(result); + canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(source, (diameter - width) / 2, (diameter - height) / 2, paint); + if (recycleSource) { + source.recycle(); + source = null; + } + } else { + result = source; + } + return result; + } + + /** + * 裁剪圆角 + * + * @param recycleSource 是否裁剪成功后销毁原图 + */ + public static Bitmap cut2RoundCorner(Bitmap source, int radius, boolean isSquare, boolean recycleSource) { + if (radius <= 0) return source; + + int sourceWidth = source.getWidth(); + int sourceHeight = source.getHeight(); + int targetWidth = sourceWidth; + int targetHeight = sourceHeight; + if (isSquare) { + targetWidth = targetHeight = Math.min(sourceWidth, sourceHeight); + } + + Paint paint = new Paint(); + paint.setAntiAlias(true); + Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); + if (result != null) { + Canvas canvas = new Canvas(result); + RectF rect = new RectF(0, 0, targetWidth, targetHeight); + canvas.drawRoundRect(rect, radius, radius, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(source, + (targetWidth - sourceWidth) / 2, (targetHeight - sourceHeight) / 2, paint); + if (recycleSource) { + source.recycle(); + source = null; + } + } else { + result = source; + } + return result; + } + + /** + * 裁剪并缩放至指定大小 + * + * @param recycleSource 是否裁剪成功后销毁原图 + */ + public static Bitmap cut2ScaleSize(Bitmap source, int dstWidth, int dstHeight, boolean recycleSource) { + final int width = source.getWidth(); + final int height = source.getHeight(); + if (width == dstWidth && height == dstHeight) { + return source; + } + + // scale + Matrix m = new Matrix(); + int l = 0, t = 0, r = width, b = height; + { + float sx = dstWidth / (float) width; + float sy = dstHeight / (float) height; + + if (sx > sy) { + sy = sx; + l = 0; + r = width; + t = (int) ((height - dstHeight / sx) / 2); + b = (int) ((height + dstHeight / sx) / 2); + } else { + sx = sy; + l = (int) ((width - dstWidth / sx) / 2); + r = (int) ((width + dstWidth / sx) / 2); + t = 0; + b = height; + } + m.setScale(sx, sy); + } + + Bitmap result = Bitmap.createBitmap(source, l, t, r - l, b - t, m, true); + + if (result != null) { + if (recycleSource && result != source) { + source.recycle(); + source = null; + } + } else { + result = source; + } + return result; + } + + /** + * 旋转图片 + * + * @param recycleSource 是否旋转成功后销毁原图 + */ + public static Bitmap rotate(Bitmap source, int angle, boolean recycleSource) { + Bitmap result = null; + + if (angle != 0) { + + Matrix m = new Matrix(); + m.setRotate(angle); + try { + result = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), m, true); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + if (result != null) { + if (recycleSource && result != source) { + source.recycle(); + source = null; + } + } else { + result = source; + } + return result; + } + + /** + * 获取图片旋转角度 + * + * @param filePath 图片文件路径 + * @return 需要旋转的角度 + */ + @SuppressLint("ExifInterface") + public static int getRotateAngle(String filePath) { + int angle = 0; + try { + android.media.ExifInterface exif = new android.media.ExifInterface(filePath); + int orientation = exif.getAttributeInt( + android.media.ExifInterface.TAG_ORIENTATION, + android.media.ExifInterface.ORIENTATION_UNDEFINED); + switch (orientation) { + case android.media.ExifInterface.ORIENTATION_ROTATE_90: + angle = 90; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_180: + angle = 180; + break; + case android.media.ExifInterface.ORIENTATION_ROTATE_270: + angle = 270; + break; + default: + angle = 0; + break; + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + return angle; + } + + /** + * 根据文件的修改时间和图片的属性保存缩略图 + */ + private static void saveThumbCache(File file, ImageOptions options, Bitmap thumbBitmap) { + DiskCacheEntity entity = new DiskCacheEntity(); + entity.setKey( + file.getAbsolutePath() + "@" + file.lastModified() + options.toString()); + DiskCacheFile cacheFile = null; + OutputStream out = null; + try { + cacheFile = THUMB_CACHE.createDiskCacheFile(entity); + if (cacheFile != null) { + out = new FileOutputStream(cacheFile); + thumbBitmap.compress(Bitmap.CompressFormat.PNG, 80, out); + out.flush(); + cacheFile = cacheFile.commit(); + } + } catch (Throwable ex) { + IOUtil.deleteFileOrDir(cacheFile); + LogUtil.w(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(cacheFile); + IOUtil.closeQuietly(out); + } + } + + /** + * 根据文件的修改时间和图片的属性获取缩略图 + */ + private static Bitmap getThumbCache(File file, ImageOptions options) { + DiskCacheFile cacheFile = null; + try { + cacheFile = THUMB_CACHE.getDiskCacheFile( + file.getAbsolutePath() + "@" + file.lastModified() + options.toString()); + if (cacheFile != null && cacheFile.exists()) { + BitmapFactory.Options bitmapOps = new BitmapFactory.Options(); + bitmapOps.inJustDecodeBounds = false; + bitmapOps.inPurgeable = true; + bitmapOps.inInputShareable = true; + bitmapOps.inPreferredConfig = Bitmap.Config.ARGB_8888; + return BitmapFactory.decodeFile(cacheFile.getAbsolutePath(), bitmapOps); + } + } catch (Throwable ex) { + LogUtil.w(ex.getMessage(), ex); + } finally { + IOUtil.closeQuietly(cacheFile); + } + return null; + } +} diff --git a/app/src/main/java/org/xutils/image/ImageLoader.java b/app/src/main/java/org/xutils/image/ImageLoader.java new file mode 100644 index 0000000..12eb6f9 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ImageLoader.java @@ -0,0 +1,642 @@ +package org.xutils.image; + +import android.annotation.SuppressLint; +import android.app.ActivityManager; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.View; +import android.view.animation.Animation; +import android.widget.ImageView; + +import org.xutils.cache.LruCache; +import org.xutils.cache.LruDiskCache; +import org.xutils.common.Callback; +import org.xutils.common.task.Priority; +import org.xutils.common.task.PriorityExecutor; +import org.xutils.common.util.IOUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.ex.FileLockedException; +import org.xutils.http.RequestParams; +import org.xutils.x; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by wyouflf on 15/10/9. + * 图片加载控制 + */ +/*package*/ final class ImageLoader implements + Callback.PrepareCallback, + Callback.CacheCallback, + Callback.ProgressCallback, + Callback.TypedCallback, + Callback.Cancelable { + + private MemCacheKey key; + private ImageOptions options; + private WeakReference viewRef; + private int fileLockedExceptionRetryCount = 0; + + private final static AtomicLong SEQ_SEEK = new AtomicLong(0); + private final long seq = SEQ_SEEK.incrementAndGet(); + + private volatile boolean stopped = false; + private volatile boolean cancelled = false; + private volatile boolean skipOnWaitingCallback = false; + private volatile boolean skipOnFinishedCallback = false; + private Cancelable httpCancelable; + private CommonCallback callback; + private PrepareCallback prepareCallback; + private CacheCallback cacheCallback; + private ProgressCallback progressCallback; + + private final static String DISK_CACHE_DIR_NAME = "xUtils_img"; + private final static Executor EXECUTOR = new PriorityExecutor(10, false); + private final static int MEM_CACHE_MIN_SIZE = 1024 * 1024 * 4; // 4M + private final static LruCache MEM_CACHE = + new LruCache(MEM_CACHE_MIN_SIZE) { + private boolean deepClear = false; + + @Override + protected int sizeOf(MemCacheKey key, Drawable value) { + if (value instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable) value).getBitmap(); + return bitmap == null ? 0 : bitmap.getByteCount(); + } else if (value instanceof GifDrawable) { + return ((GifDrawable) value).getByteCount(); + } + return super.sizeOf(key, value); + } + + @Override + public void trimToSize(int maxSize) { + if (maxSize < 0) { + deepClear = true; + } + super.trimToSize(maxSize); + deepClear = false; + } + + @Override + protected void entryRemoved(boolean evicted, MemCacheKey key, Drawable oldValue, Drawable newValue) { + super.entryRemoved(evicted, key, oldValue, newValue); + if (evicted && deepClear && oldValue instanceof ReusableDrawable) { + ((ReusableDrawable) oldValue).setMemCacheKey(null); + } + } + }; + + static { + int memClass = ((ActivityManager) x.app() + .getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass(); + + // Use 1/8th of the available memory for this memory cache. + int cacheSize = 1024 * 1024 * memClass / 8; + if (cacheSize < MEM_CACHE_MIN_SIZE) { + cacheSize = MEM_CACHE_MIN_SIZE; + } + MEM_CACHE.resize(cacheSize); + } + + private ImageLoader() { + } + + /*package*/ + static void clearMemCache() { + MEM_CACHE.evictAll(); + } + + /*package*/ + static void clearCacheFiles() { + LruDiskCache.getDiskCache(DISK_CACHE_DIR_NAME).clearCacheFiles(); + } + + private final static HashMap FAKE_IMG_MAP = new HashMap(); + + /** + * load from Network or DiskCache, invoke in any thread. + */ + /*package*/ + static Cancelable doLoadDrawable(final String url, + final ImageOptions options, + final CommonCallback callback) { + if (TextUtils.isEmpty(url)) { + postArgsException(null, options, "url is null", callback); + return null; + } + + FakeImageView fakeImageView = new FakeImageView(); + return doBind(fakeImageView, url, options, 0, callback); + } + + /** + * load from Network or DiskCache, invoke in any thread. + */ + /*package*/ + static Cancelable doLoadFile(final String url, + final ImageOptions options, + final CacheCallback callback) { + if (TextUtils.isEmpty(url)) { + postArgsException(null, options, "url is null", callback); + return null; + } + + RequestParams params = createRequestParams(null, url, options); + return x.http().get(params, callback); + } + + /** + * load from Network or DiskCache, invoke in ui thread. + */ + /*package*/ + static Cancelable doBind(final ImageView view, + final String url, + final ImageOptions options, + final int fileLockedExceptionRetryCount, + final CommonCallback callback) { + + // check params + ImageOptions localOptions = options; + { + if (view == null) { + postArgsException(null, localOptions, "view is null", callback); + return null; + } + + if (TextUtils.isEmpty(url)) { + postArgsException(view, localOptions, "url is null", callback); + return null; + } + + if (localOptions == null) { + localOptions = ImageOptions.DEFAULT; + } + localOptions.optimizeMaxSize(view); + } + + // stop the old loader + MemCacheKey key = new MemCacheKey(url, localOptions); + Drawable oldDrawable = view.getDrawable(); + if (oldDrawable instanceof AsyncDrawable) { + ImageLoader loader = ((AsyncDrawable) oldDrawable).getImageLoader(); + if (loader != null && !loader.stopped) { + if (key.equals(loader.key)) { + // repetitive url and options binding to the same View. + // not need callback to ui. + return null; + } else { + loader.cancel(); + } + } + } else if (oldDrawable instanceof ReusableDrawable) { + MemCacheKey oldKey = ((ReusableDrawable) oldDrawable).getMemCacheKey(); + if (oldKey != null && oldKey.equals(key)) { + MEM_CACHE.put(key, oldDrawable); + } + } + + // load from Memory Cache + Drawable memDrawable = null; + if (localOptions.isUseMemCache()) { + memDrawable = MEM_CACHE.get(key); + if (memDrawable instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable) memDrawable).getBitmap(); + if (bitmap == null || bitmap.isRecycled()) { + memDrawable = null; + } + } + } + if (memDrawable != null) { // has mem cache + boolean trustMemCache = false; + try { + if (callback instanceof ProgressCallback) { + try { + ((ProgressCallback) callback).onWaiting(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + if (callback instanceof CacheCallback) { + try { + // 是否信任内存缓存. onStart 之后再次调用 onCache 时, 入参是磁盘缓存. + trustMemCache = ((CacheCallback) callback).onCache(memDrawable); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } else { + trustMemCache = true; + } + + // hit mem cache + if (trustMemCache) { + view.setScaleType(localOptions.getImageScaleType()); + view.setImageDrawable(memDrawable); + if (callback != null) { + try { + callback.onSuccess(memDrawable); + } catch (Throwable ex) { + callback.onError(ex, true); + } + } + // goto finally + } else { + // not trust the cache + // load from Network or DiskCache + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + loader.skipOnWaitingCallback = true; + return loader.doLoadRequest(view, url, localOptions, callback); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + // try load from Network or DiskCache + trustMemCache = false; + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + loader.skipOnWaitingCallback = true; + return loader.doLoadRequest(view, url, localOptions, callback); + } finally { + if (trustMemCache && callback != null) { + try { + callback.onFinished(); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } else { /* memDrawable == null */ + // load from Network or DiskCache + ImageLoader loader = new ImageLoader(); + loader.fileLockedExceptionRetryCount = fileLockedExceptionRetryCount; + return loader.doLoadRequest(view, url, localOptions, callback); + } + return null; + } + + /** + * load from Network or DiskCache + */ + private Cancelable doLoadRequest(ImageView view, + String url, + ImageOptions options, + CommonCallback callback) { + + this.viewRef = new WeakReference(view); + this.options = options; + this.key = new MemCacheKey(url, options); + this.callback = callback; + if (callback instanceof ProgressCallback) { + this.progressCallback = (ProgressCallback) callback; + } + if (callback instanceof PrepareCallback) { + this.prepareCallback = (PrepareCallback) callback; + } + if (callback instanceof CacheCallback) { + this.cacheCallback = (CacheCallback) callback; + } + + // set loadingDrawable + Drawable loadingDrawable = view.getDrawable(); + if (loadingDrawable == null || options.isForceLoadingDrawable()) { + loadingDrawable = options.getLoadingDrawable(view); + view.setScaleType(options.getPlaceholderScaleType()); + } + view.setImageDrawable(new AsyncDrawable(this, loadingDrawable)); + + // request + RequestParams params = createRequestParams(view.getContext(), url, options); + if (view instanceof FakeImageView) { + synchronized (FAKE_IMG_MAP) { + FAKE_IMG_MAP.put(view.hashCode() + url, (FakeImageView) view); + } + } + return httpCancelable = x.http().get(params, this); + } + + @Override + public void cancel() { + stopped = true; + cancelled = true; + if (httpCancelable != null) { + httpCancelable.cancel(); + } + } + + @Override + public boolean isCancelled() { + return cancelled || !validView4Callback(false); + } + + @Override + public void onWaiting() { + if (!skipOnWaitingCallback && progressCallback != null) { + progressCallback.onWaiting(); + } + } + + @Override + public void onStarted() { + if (validView4Callback(true) && progressCallback != null) { + progressCallback.onStarted(); + } + } + + @Override + public void onLoading(long total, long current, boolean isDownloading) { + if (validView4Callback(true) && progressCallback != null) { + progressCallback.onLoading(total, current, isDownloading); + } + } + + private static final Type loadType = File.class; + + @Override + public Type getLoadType() { + return loadType; + } + + @Override + public Drawable prepare(File rawData) throws Throwable { + if (!validView4Callback(true)) return null; + + if (!rawData.exists()) { + throw new FileNotFoundException(rawData.getAbsolutePath()); + } + + try { + Drawable result = null; + if (prepareCallback != null) { + result = prepareCallback.prepare(rawData); + } + if (result == null) { + result = ImageDecoder.decodeFileWithLock(rawData, options, this); + } + if (result != null) { + if (result instanceof ReusableDrawable) { + ((ReusableDrawable) result).setMemCacheKey(key); + MEM_CACHE.put(key, result); + } + } + return result; + } catch (IOException ex) { + IOUtil.deleteFileOrDir(rawData); + throw ex; + } + } + + private boolean hasCache = false; + + @Override + public boolean onCache(Drawable result) { + if (!validView4Callback(true)) return false; + + if (result != null) { + hasCache = true; + setSuccessDrawable4Callback(result); + if (cacheCallback != null) { + return cacheCallback.onCache(result); + } else if (callback != null) { + callback.onSuccess(result); + return true; + } + return true; + } + + return false; + } + + @Override + public void onSuccess(Drawable result) { + if (!validView4Callback(!hasCache)) return; + + if (result != null) { + setSuccessDrawable4Callback(result); + if (callback != null) { + callback.onSuccess(result); + } + } + } + + @Override + public void onError(Throwable ex, boolean isOnCallback) { + stopped = true; + if (!validView4Callback(false)) return; + + fileLockedExceptionRetryCount++; + if (ex instanceof FileLockedException && fileLockedExceptionRetryCount < 1000/*max*/) { + LogUtil.d("ImageFileLocked: " + key.url); + x.task().postDelayed(new Runnable() { + @Override + public void run() { + ImageView imageView = viewRef.get(); + if (imageView != null) { + doBind(imageView, key.url, options, fileLockedExceptionRetryCount, callback); + } else { + ImageLoader.this.onFinished(); + } + } + }, 10); + skipOnFinishedCallback = true; + } else { + LogUtil.e(key.url, ex); + setErrorDrawable4Callback(); + if (callback != null) { + callback.onError(ex, isOnCallback); + } + } + } + + @Override + public void onCancelled(CancelledException cex) { + stopped = true; + if (!validView4Callback(false)) return; + + if (callback != null) { + callback.onCancelled(cex); + } + } + + @Override + public void onFinished() { + stopped = true; + if (skipOnFinishedCallback) return; + + ImageView view = viewRef.get(); + if (view instanceof FakeImageView) { + synchronized (FAKE_IMG_MAP) { + FAKE_IMG_MAP.remove(view.hashCode() + key.url); + } + } + + if (callback != null) { + callback.onFinished(); + } + } + + private static RequestParams createRequestParams(Context context, String url, ImageOptions options) { + RequestParams params = new RequestParams(url); + if (context != null) { + params.setContext(context); + } + params.setCacheDirName(DISK_CACHE_DIR_NAME); + params.setConnectTimeout(1000 * 8); + params.setPriority(Priority.BG_LOW); + params.setExecutor(EXECUTOR); + params.setCancelFast(true); + params.setUseCookie(false); + if (options != null) { + ImageOptions.ParamsBuilder paramsBuilder = options.getParamsBuilder(); + if (paramsBuilder != null) { + params = paramsBuilder.buildParams(params, options); + } + } + return params; + } + + private boolean validView4Callback(boolean forceValidAsyncDrawable) { + final ImageView view = viewRef.get(); + if (view != null) { + Drawable otherDrawable = view.getDrawable(); + if (otherDrawable instanceof AsyncDrawable) { + ImageLoader otherLoader = ((AsyncDrawable) otherDrawable).getImageLoader(); + if (otherLoader != null) { + if (otherLoader == this) { + return true; + } else { + if (this.seq > otherLoader.seq) { + otherLoader.cancel(); + return true; + } else { + this.cancel(); + return false; + } + } + } + } else if (forceValidAsyncDrawable) { + this.cancel(); + return false; + } + return true; + } + return false; + } + + private void setSuccessDrawable4Callback(final Drawable drawable) { + final ImageView view = viewRef.get(); + if (view != null) { + view.setScaleType(options.getImageScaleType()); + if (drawable instanceof GifDrawable) { + if (view.getScaleType() == ImageView.ScaleType.CENTER) { + view.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + } + view.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + if (options.getAnimation() != null) { + ImageAnimationHelper.animationDisplay(view, drawable, options.getAnimation()); + } else if (options.isFadeIn()) { + ImageAnimationHelper.fadeInDisplay(view, drawable); + } else { + view.setImageDrawable(drawable); + } + } + } + + private void setErrorDrawable4Callback() { + final ImageView view = viewRef.get(); + if (view != null) { + Drawable drawable = options.getFailureDrawable(view); + view.setScaleType(options.getPlaceholderScaleType()); + view.setImageDrawable(drawable); + } + } + + private static void postArgsException( + final ImageView view, final ImageOptions options, + final String exMsg, final CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + try { + if (callback instanceof ProgressCallback) { + ((ProgressCallback) callback).onWaiting(); + } + if (view != null && options != null) { + view.setScaleType(options.getPlaceholderScaleType()); + view.setImageDrawable(options.getFailureDrawable(view)); + } + if (callback != null) { + callback.onError(new IllegalArgumentException(exMsg), false); + } + } catch (Throwable ex) { + if (callback != null) { + try { + callback.onError(ex, true); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } finally { + if (callback != null) { + try { + callback.onFinished(); + } catch (Throwable throwable) { + LogUtil.e(throwable.getMessage(), throwable); + } + } + } + } + }); + } + + @SuppressLint({"ViewConstructor", "AppCompatCustomView"}) + private final static class FakeImageView extends ImageView { + private final int hashCode; + private Drawable drawable; + private final static AtomicInteger hashCodeSeed = new AtomicInteger(0); + + public FakeImageView() { + super(x.app()); + hashCode = hashCodeSeed.incrementAndGet(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public void setImageDrawable(Drawable drawable) { + this.drawable = drawable; + } + + @Override + public Drawable getDrawable() { + return drawable; + } + + @Override + public void setLayerType(int layerType, Paint paint) { + } + + @Override + public void setScaleType(ScaleType scaleType) { + } + + @Override + public void startAnimation(Animation animation) { + } + } +} diff --git a/app/src/main/java/org/xutils/image/ImageManagerImpl.java b/app/src/main/java/org/xutils/image/ImageManagerImpl.java new file mode 100644 index 0000000..bde8914 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ImageManagerImpl.java @@ -0,0 +1,95 @@ +package org.xutils.image; + +import android.graphics.drawable.Drawable; +import android.widget.ImageView; + +import org.xutils.ImageManager; +import org.xutils.common.Callback; +import org.xutils.x; + +import java.io.File; + +/** + * Created by wyouflf on 15/10/9. + */ +public final class ImageManagerImpl implements ImageManager { + + private static final Object lock = new Object(); + private static volatile ImageManagerImpl instance; + + private ImageManagerImpl() { + } + + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new ImageManagerImpl(); + } + } + } + x.Ext.setImageManager(instance); + } + + + @Override + public void bind(final ImageView view, final String url) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, null, 0, null); + } + }); + } + + @Override + public void bind(final ImageView view, final String url, final ImageOptions options) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, options, 0, null); + } + }); + } + + @Override + public void bind(final ImageView view, final String url, final Callback.CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, null, 0, callback); + } + }); + } + + @Override + public void bind(final ImageView view, final String url, final ImageOptions options, final Callback.CommonCallback callback) { + x.task().autoPost(new Runnable() { + @Override + public void run() { + ImageLoader.doBind(view, url, options, 0, callback); + } + }); + } + + @Override + public Callback.Cancelable loadDrawable(String url, ImageOptions options, Callback.CommonCallback callback) { + return ImageLoader.doLoadDrawable(url, options, callback); + } + + @Override + public Callback.Cancelable loadFile(String url, ImageOptions options, Callback.CacheCallback callback) { + return ImageLoader.doLoadFile(url, options, callback); + } + + @Override + public void clearMemCache() { + ImageLoader.clearMemCache(); + } + + @Override + public void clearCacheFiles() { + ImageLoader.clearCacheFiles(); + ImageDecoder.clearCacheFiles(); + } +} diff --git a/app/src/main/java/org/xutils/image/ImageOptions.java b/app/src/main/java/org/xutils/image/ImageOptions.java new file mode 100644 index 0000000..9a33fbd --- /dev/null +++ b/app/src/main/java/org/xutils/image/ImageOptions.java @@ -0,0 +1,414 @@ +package org.xutils.image; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.ImageView; + +import org.xutils.common.util.DensityUtil; +import org.xutils.common.util.LogUtil; +import org.xutils.http.RequestParams; + +/** + * Created by wyouflf on 15/8/21. + * 图片加载参数 + */ +public class ImageOptions { + + public final static ImageOptions DEFAULT = new ImageOptions(); + + // region ###################### decode options (equals & hashcode prop) ################ + private int maxWidth = 0; + private int maxHeight = 0; + private int width = 0; // 小于0时不采样压缩. 等于0时自动识别ImageView的宽高和maxWidth. + private int height = 0; // 小于0时不采样压缩. 等于0时自动识别ImageView的宽高和maxHeight. + private boolean crop = false; // crop to (width, height) + + private int radius = 0; + private boolean square = false; + private boolean circular = false; + private boolean autoRotate = false; + private boolean compress = true; + private Bitmap.Config config = Bitmap.Config.RGB_565; + + // gif option + private boolean ignoreGif = true; + private int gifRate = 100; + // end region ########################################## decode options ################# + + // region ############# display options + private int loadingDrawableId = 0; + private int failureDrawableId = 0; + private Drawable loadingDrawable = null; + private Drawable failureDrawable = null; + private boolean forceLoadingDrawable = true; + + private ImageView.ScaleType placeholderScaleType = ImageView.ScaleType.CENTER_INSIDE; + private ImageView.ScaleType imageScaleType = ImageView.ScaleType.CENTER_CROP; + + private boolean fadeIn = false; + private Animation animation = null; + // end region ############ display options + + // extends + private boolean useMemCache = true; + private ParamsBuilder paramsBuilder; + + protected ImageOptions() { + } + + /*package*/ + final void optimizeMaxSize(ImageView view) { + if (width > 0 && height > 0) { + maxWidth = width; + maxHeight = height; + return; + } + + int screenWidth = DensityUtil.getScreenWidth(); + int screenHeight = DensityUtil.getScreenHeight(); + + if (this == DEFAULT) { + maxWidth = width = screenWidth * 3 / 2; + maxHeight = height = screenHeight * 3 / 2; + return; + } + + if (width < 0) { + maxWidth = screenWidth * 3 / 2; + compress = false; + } + if (height < 0) { + maxHeight = screenHeight * 3 / 2; + compress = false; + } + + if (view == null && maxWidth <= 0 && maxHeight <= 0) { + maxWidth = screenWidth; + maxHeight = screenHeight; + } else { + int tempWidth = maxWidth; + int tempHeight = maxHeight; + + if (view != null) { + final ViewGroup.LayoutParams params = view.getLayoutParams(); + if (params != null) { + + if (tempWidth <= 0) { + if (params.width > 0) { + tempWidth = params.width; + if (this.width <= 0) { + this.width = tempWidth; + } + } else if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { + tempWidth = view.getWidth(); + } + } + + if (tempHeight <= 0) { + if (params.height > 0) { + tempHeight = params.height; + if (this.height <= 0) { + this.height = tempHeight; + } + } else if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { + tempHeight = view.getHeight(); + } + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (tempWidth <= 0) tempWidth = view.getMaxWidth(); + if (tempHeight <= 0) tempHeight = view.getMaxHeight(); + } + } + + if (tempWidth <= 0) tempWidth = screenWidth; + if (tempHeight <= 0) tempHeight = screenHeight; + + maxWidth = tempWidth; + maxHeight = tempHeight; + } + } + + public int getMaxWidth() { + return maxWidth; + } + + public int getMaxHeight() { + return maxHeight; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public boolean isCrop() { + return crop; + } + + public int getRadius() { + return radius; + } + + public boolean isSquare() { + return square; + } + + public boolean isCircular() { + return circular; + } + + public boolean isIgnoreGif() { + return ignoreGif; + } + + public int getGifRate() { + return gifRate; + } + + public boolean isAutoRotate() { + return autoRotate; + } + + public boolean isCompress() { + return compress; + } + + public Bitmap.Config getConfig() { + return config; + } + + public Drawable getLoadingDrawable(ImageView view) { + if (loadingDrawable == null && loadingDrawableId > 0 && view != null) { + try { + loadingDrawable = view.getResources().getDrawable(loadingDrawableId); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return loadingDrawable; + } + + public Drawable getFailureDrawable(ImageView view) { + if (failureDrawable == null && failureDrawableId > 0 && view != null) { + try { + failureDrawable = view.getResources().getDrawable(failureDrawableId); + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + return failureDrawable; + } + + public boolean isFadeIn() { + return fadeIn; + } + + public Animation getAnimation() { + return animation; + } + + public ImageView.ScaleType getPlaceholderScaleType() { + return placeholderScaleType; + } + + public ImageView.ScaleType getImageScaleType() { + return imageScaleType; + } + + public boolean isForceLoadingDrawable() { + return forceLoadingDrawable; + } + + public boolean isUseMemCache() { + return useMemCache; + } + + public ParamsBuilder getParamsBuilder() { + return paramsBuilder; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ImageOptions options = (ImageOptions) o; + + if (maxWidth != options.maxWidth) return false; + if (maxHeight != options.maxHeight) return false; + if (width != options.width) return false; + if (height != options.height) return false; + if (crop != options.crop) return false; + if (radius != options.radius) return false; + if (square != options.square) return false; + if (circular != options.circular) return false; + if (autoRotate != options.autoRotate) return false; + if (compress != options.compress) return false; + return config == options.config; + + } + + @Override + public int hashCode() { + int result = maxWidth; + result = 31 * result + maxHeight; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + (crop ? 1 : 0); + result = 31 * result + radius; + result = 31 * result + (square ? 1 : 0); + result = 31 * result + (circular ? 1 : 0); + result = 31 * result + (autoRotate ? 1 : 0); + result = 31 * result + (compress ? 1 : 0); + result = 31 * result + (config != null ? config.hashCode() : 0); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("_"); + sb.append(maxWidth).append("_"); + sb.append(maxHeight).append("_"); + sb.append(width).append("_"); + sb.append(height).append("_"); + sb.append(radius).append("_"); + sb.append(config).append("_"); + sb.append(crop ? 1 : 0).append(square ? 1 : 0).append(circular ? 1 : 0); + sb.append(autoRotate ? 1 : 0).append(compress ? 1 : 0); + return sb.toString(); + } + + public interface ParamsBuilder { + RequestParams buildParams(RequestParams params, ImageOptions options); + } + + public static class Builder { + + protected ImageOptions options; + + public Builder() { + newImageOptions(); + } + + protected void newImageOptions() { + options = new ImageOptions(); + } + + public ImageOptions build() { + return options; + } + + /** + * 小于0时不采样压缩. 等于0时自动识别ImageView的宽高和(maxWidth, maxHeight). + */ + public Builder setSize(int width, int height) { + options.width = width; + options.height = height; + return this; + } + + public Builder setCrop(boolean crop) { + options.crop = crop; + return this; + } + + public Builder setRadius(int radius) { + options.radius = radius; + return this; + } + + public Builder setSquare(boolean square) { + options.square = square; + return this; + } + + public Builder setCircular(boolean circular) { + options.circular = circular; + return this; + } + + public Builder setAutoRotate(boolean autoRotate) { + options.autoRotate = autoRotate; + return this; + } + + public Builder setConfig(Bitmap.Config config) { + options.config = config; + return this; + } + + public Builder setIgnoreGif(boolean ignoreGif) { + options.ignoreGif = ignoreGif; + return this; + } + + public Builder setGifRate(int rate) { + options.gifRate = rate; + return this; + } + + public Builder setLoadingDrawableId(int loadingDrawableId) { + options.loadingDrawableId = loadingDrawableId; + return this; + } + + public Builder setLoadingDrawable(Drawable loadingDrawable) { + options.loadingDrawable = loadingDrawable; + return this; + } + + public Builder setFailureDrawableId(int failureDrawableId) { + options.failureDrawableId = failureDrawableId; + return this; + } + + public Builder setFailureDrawable(Drawable failureDrawable) { + options.failureDrawable = failureDrawable; + return this; + } + + public Builder setFadeIn(boolean fadeIn) { + options.fadeIn = fadeIn; + return this; + } + + public Builder setAnimation(Animation animation) { + options.animation = animation; + return this; + } + + public Builder setPlaceholderScaleType(ImageView.ScaleType placeholderScaleType) { + options.placeholderScaleType = placeholderScaleType; + return this; + } + + public Builder setImageScaleType(ImageView.ScaleType imageScaleType) { + options.imageScaleType = imageScaleType; + return this; + } + + public Builder setForceLoadingDrawable(boolean forceLoadingDrawable) { + options.forceLoadingDrawable = forceLoadingDrawable; + return this; + } + + public Builder setUseMemCache(boolean useMemCache) { + options.useMemCache = useMemCache; + return this; + } + + public Builder setParamsBuilder(ParamsBuilder paramsBuilder) { + options.paramsBuilder = paramsBuilder; + return this; + } + } + +} diff --git a/app/src/main/java/org/xutils/image/MemCacheKey.java b/app/src/main/java/org/xutils/image/MemCacheKey.java new file mode 100644 index 0000000..a41296f --- /dev/null +++ b/app/src/main/java/org/xutils/image/MemCacheKey.java @@ -0,0 +1,38 @@ +package org.xutils.image; + +/** + * Created by wyouflf on 15/10/20. + */ +/*package*/ final class MemCacheKey { + public final String url; + public final ImageOptions options; + + public MemCacheKey(String url, ImageOptions options) { + this.url = url; + this.options = options; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MemCacheKey that = (MemCacheKey) o; + + if (!url.equals(that.url)) return false; + return options.equals(that.options); + + } + + @Override + public int hashCode() { + int result = url.hashCode(); + result = 31 * result + options.hashCode(); + return result; + } + + @Override + public String toString() { + return url + options.toString(); + } +} diff --git a/app/src/main/java/org/xutils/image/ReusableBitmapDrawable.java b/app/src/main/java/org/xutils/image/ReusableBitmapDrawable.java new file mode 100644 index 0000000..0d46320 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ReusableBitmapDrawable.java @@ -0,0 +1,24 @@ +package org.xutils.image; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; + +/*package*/ final class ReusableBitmapDrawable extends BitmapDrawable implements ReusableDrawable { + + private MemCacheKey key; + + public ReusableBitmapDrawable(Resources res, Bitmap bitmap) { + super(res, bitmap); + } + + @Override + public MemCacheKey getMemCacheKey() { + return key; + } + + @Override + public void setMemCacheKey(MemCacheKey key) { + this.key = key; + } +} diff --git a/app/src/main/java/org/xutils/image/ReusableDrawable.java b/app/src/main/java/org/xutils/image/ReusableDrawable.java new file mode 100644 index 0000000..5fc9d56 --- /dev/null +++ b/app/src/main/java/org/xutils/image/ReusableDrawable.java @@ -0,0 +1,12 @@ +package org.xutils.image; + +/** + * Created by wyouflf on 15/10/20. + * 使已被LruCache移除, 但还在被ImageView使用的Drawable可以再次被回收使用. + */ +/*package*/ interface ReusableDrawable { + + MemCacheKey getMemCacheKey(); + + void setMemCacheKey(MemCacheKey key); +} diff --git a/app/src/main/java/org/xutils/view/EventListenerManager.java b/app/src/main/java/org/xutils/view/EventListenerManager.java new file mode 100644 index 0000000..5213371 --- /dev/null +++ b/app/src/main/java/org/xutils/view/EventListenerManager.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.view; + +import android.text.TextUtils; +import android.view.View; + +import org.xutils.common.util.DoubleKeyValueMap; +import org.xutils.common.util.LogUtil; +import org.xutils.view.annotation.Event; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/*package*/ final class EventListenerManager { + + private final static long QUICK_EVENT_TIME_SPAN = 300; + private final static HashSet AVOID_QUICK_EVENT_SET = new HashSet(2); + + static { + AVOID_QUICK_EVENT_SET.add("onClick"); + AVOID_QUICK_EVENT_SET.add("onItemClick"); + } + + private EventListenerManager() { + } + + /** + * k1: viewInjectInfo + * k2: interface Type + * value: listener + */ + private final static DoubleKeyValueMap, Object> + listenerCache = new DoubleKeyValueMap, Object>(); + + + public static void addEventMethod( + //根据页面或view holder生成的ViewFinder + ViewFinder finder, + //根据当前注解ID生成的ViewInfo + ViewInfo info, + //注解对象 + Event event, + //页面或view holder对象 + Object handler, + //当前注解方法 + Method method) { + try { + View view = finder.findViewByInfo(info); + + if (view != null) { + // 注解中定义的接口,比如Event注解默认的接口为View.OnClickListener + Class listenerType = event.type(); + // 默认为空,注解接口对应的Set方法,比如setOnClickListener方法 + String listenerSetter = event.setter(); + if (TextUtils.isEmpty(listenerSetter)) { + listenerSetter = "set" + listenerType.getSimpleName(); + } + + + String methodName = event.method(); + + boolean addNewMethod = false; + /* + 根据View的ID和当前的接口类型获取已经缓存的接口实例对象, + 比如根据View.id和View.OnClickListener.class两个键获取这个View的OnClickListener对象 + */ + Object listener = listenerCache.get(info, listenerType); + DynamicHandler dynamicHandler = null; + /* + 如果接口实例对象不为空 + 获取接口对象对应的动态代理对象 + 如果动态代理对象的handler和当前handler相同 + 则为动态代理对象添加代理方法 + */ + if (listener != null) { + dynamicHandler = (DynamicHandler) Proxy.getInvocationHandler(listener); + addNewMethod = handler.equals(dynamicHandler.getHandler()); + if (addNewMethod) { + dynamicHandler.addMethod(methodName, method); + } + } + + // 如果还没有注册此代理 + if (!addNewMethod) { + + dynamicHandler = new DynamicHandler(handler); + + dynamicHandler.addMethod(methodName, method); + + // 生成的代理对象实例,比如View.OnClickListener的实例对象 + listener = Proxy.newProxyInstance( + listenerType.getClassLoader(), + new Class[]{listenerType}, + dynamicHandler); + + listenerCache.put(info, listenerType, listener); + } + + Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType); + setEventListenerMethod.invoke(view, listener); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + + public static class DynamicHandler implements InvocationHandler { + // 存放代理对象,比如Fragment或view holder + private WeakReference handlerRef; + // 存放代理方法 + private final HashMap methodMap = new HashMap(1); + + private static long lastClickTime = 0; + + public DynamicHandler(Object handler) { + this.handlerRef = new WeakReference(handler); + } + + public void addMethod(String name, Method method) { + methodMap.put(name, method); + } + + public Object getHandler() { + return handlerRef.get(); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object handler = handlerRef.get(); + if (handler != null) { + + String eventMethod = method.getName(); + if ("toString".equals(eventMethod)) { + return DynamicHandler.class.getSimpleName(); + } + + method = methodMap.get(eventMethod); + if (method == null && methodMap.size() == 1) { + for (Map.Entry entry : methodMap.entrySet()) { + if (TextUtils.isEmpty(entry.getKey())) { + method = entry.getValue(); + } + break; + } + } + + if (method != null) { + + if (AVOID_QUICK_EVENT_SET.contains(eventMethod)) { + long timeSpan = System.currentTimeMillis() - lastClickTime; + if (timeSpan > 0 && timeSpan < QUICK_EVENT_TIME_SPAN) { + LogUtil.d("onClick cancelled: " + timeSpan); + return null; + } + lastClickTime = System.currentTimeMillis(); + } + + try { + return method.invoke(handler, args); + } catch (Throwable ex) { + throw new RuntimeException("invoke method error:" + + handler.getClass().getName() + "#" + method.getName(), ex); + } + } else { + LogUtil.w("method not impl: " + eventMethod + "(" + handler.getClass().getSimpleName() + ")"); + } + } + return null; + } + } +} diff --git a/app/src/main/java/org/xutils/view/ViewFinder.java b/app/src/main/java/org/xutils/view/ViewFinder.java new file mode 100644 index 0000000..8881c81 --- /dev/null +++ b/app/src/main/java/org/xutils/view/ViewFinder.java @@ -0,0 +1,48 @@ +package org.xutils.view; + +import android.app.Activity; +import android.view.View; + +/** + * Author: wyouflf + * Date: 13-9-9 + * Time: 下午12:29 + */ +/*package*/ final class ViewFinder { + + private View view; + private Activity activity; + + public ViewFinder(View view) { + this.view = view; + } + + public ViewFinder(Activity activity) { + this.activity = activity; + } + + public View findViewById(int id) { + if (view != null) return view.findViewById(id); + if (activity != null) return activity.findViewById(id); + return null; + } + + public View findViewByInfo(ViewInfo info) { + return findViewById(info.value, info.parentId); + } + + public View findViewById(int id, int pid) { + View pView = null; + if (pid > 0) { + pView = this.findViewById(pid); + } + + View view = null; + if (pView != null) { + view = pView.findViewById(id); + } else { + view = this.findViewById(id); + } + return view; + } +} diff --git a/app/src/main/java/org/xutils/view/ViewInfo.java b/app/src/main/java/org/xutils/view/ViewInfo.java new file mode 100644 index 0000000..9c1477a --- /dev/null +++ b/app/src/main/java/org/xutils/view/ViewInfo.java @@ -0,0 +1,30 @@ +package org.xutils.view; + +/** + * Author: wyouflf + * Date: 13-12-5 + * Time: 下午11:25 + */ +/*package*/ final class ViewInfo { + public int value; + public int parentId; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ViewInfo viewInfo = (ViewInfo) o; + + if (value != viewInfo.value) return false; + return parentId == viewInfo.parentId; + + } + + @Override + public int hashCode() { + int result = value; + result = 31 * result + parentId; + return result; + } +} diff --git a/app/src/main/java/org/xutils/view/ViewInjectorImpl.java b/app/src/main/java/org/xutils/view/ViewInjectorImpl.java new file mode 100644 index 0000000..2ddb413 --- /dev/null +++ b/app/src/main/java/org/xutils/view/ViewInjectorImpl.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.view; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.xutils.ViewInjector; +import org.xutils.common.util.LogUtil; +import org.xutils.view.annotation.ContentView; +import org.xutils.view.annotation.Event; +import org.xutils.view.annotation.ViewInject; +import org.xutils.x; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; + +public final class ViewInjectorImpl implements ViewInjector { + + private static final HashSet> IGNORED = new HashSet>(); + + static { + IGNORED.add(Object.class); + IGNORED.add(Activity.class); + IGNORED.add(android.app.Fragment.class); + try { + IGNORED.add(Class.forName("android.support.v4.app.Fragment")); + IGNORED.add(Class.forName("android.support.v4.app.FragmentActivity")); + } catch (Throwable ignored) { + } + } + + private static final Object lock = new Object(); + private static volatile ViewInjectorImpl instance; + + private ViewInjectorImpl() { + } + + public static void registerInstance() { + if (instance == null) { + synchronized (lock) { + if (instance == null) { + instance = new ViewInjectorImpl(); + } + } + } + x.Ext.setViewInjector(instance); + } + + @Override + public void inject(View view) { + injectObject(view, view.getClass(), new ViewFinder(view)); + } + + @Override + public void inject(Activity activity) { + //获取Activity的ContentView的注解 + Class handlerType = activity.getClass(); + try { + ContentView contentView = findContentView(handlerType); + if (contentView != null) { + int viewId = contentView.value(); + if (viewId > 0) { + activity.setContentView(viewId); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + injectObject(activity, handlerType, new ViewFinder(activity)); + } + + @Override + public void inject(Object handler, View view) { + injectObject(handler, handler.getClass(), new ViewFinder(view)); + } + + @Override + public View inject(Object fragment, LayoutInflater inflater, ViewGroup container) { + // inject ContentView + View view = null; + Class handlerType = fragment.getClass(); + try { + ContentView contentView = findContentView(handlerType); + if (contentView != null) { + int viewId = contentView.value(); + if (viewId > 0) { + view = inflater.inflate(viewId, container, false); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + + // inject res & event + injectObject(fragment, handlerType, new ViewFinder(view)); + + return view; + } + + /** + * 从父类获取注解View + */ + private static ContentView findContentView(Class thisCls) { + if (thisCls == null || IGNORED.contains(thisCls) || thisCls.getName().startsWith("androidx.")) { + return null; + } + ContentView contentView = thisCls.getAnnotation(ContentView.class); + if (contentView == null) { + return findContentView(thisCls.getSuperclass()); + } + return contentView; + } + + @SuppressWarnings("ConstantConditions") + private static void injectObject(Object handler, Class handlerType, ViewFinder finder) { + + if (handlerType == null || IGNORED.contains(handlerType) || handlerType.getName().startsWith("androidx.")) { + return; + } + + // 从父类到子类递归 + injectObject(handler, handlerType.getSuperclass(), finder); + + // inject view + Field[] fields = handlerType.getDeclaredFields(); + if (fields != null && fields.length > 0) { + for (Field field : fields) { + + Class fieldType = field.getType(); + if ( + /* 不注入静态字段 */ Modifier.isStatic(field.getModifiers()) || + /* 不注入final字段 */ Modifier.isFinal(field.getModifiers()) || + /* 不注入基本类型字段 */ fieldType.isPrimitive() || + /* 不注入数组类型字段 */ fieldType.isArray()) { + continue; + } + + ViewInject viewInject = field.getAnnotation(ViewInject.class); + if (viewInject != null) { + try { + View view = finder.findViewById(viewInject.value(), viewInject.parentId()); + if (view != null) { + field.setAccessible(true); + field.set(handler, view); + } else { + throw new RuntimeException("Invalid @ViewInject for " + + handlerType.getSimpleName() + "." + field.getName()); + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } // end inject view + + // inject event + Method[] methods = handlerType.getDeclaredMethods(); + if (methods != null && methods.length > 0) { + for (Method method : methods) { + + if (Modifier.isStatic(method.getModifiers()) + || !Modifier.isPrivate(method.getModifiers())) { + continue; + } + + //检查当前方法是否是event注解的方法 + Event event = method.getAnnotation(Event.class); + if (event != null) { + try { + // id参数 + int[] values = event.value(); + int[] parentIds = event.parentId(); + int parentIdsLen = parentIds == null ? 0 : parentIds.length; + //循环所有id,生成ViewInfo并添加代理反射 + for (int i = 0; i < values.length; i++) { + int value = values[i]; + if (value > 0) { + ViewInfo info = new ViewInfo(); + info.value = value; + info.parentId = parentIdsLen > i ? parentIds[i] : 0; + method.setAccessible(true); + EventListenerManager.addEventMethod(finder, info, event, handler, method); + } + } + } catch (Throwable ex) { + LogUtil.e(ex.getMessage(), ex); + } + } + } + } // end inject event + + } + +} diff --git a/app/src/main/java/org/xutils/view/annotation/ContentView.java b/app/src/main/java/org/xutils/view/annotation/ContentView.java new file mode 100644 index 0000000..7422694 --- /dev/null +++ b/app/src/main/java/org/xutils/view/annotation/ContentView.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.view.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ContentView { + int value(); +} diff --git a/app/src/main/java/org/xutils/view/annotation/Event.java b/app/src/main/java/org/xutils/view/annotation/Event.java new file mode 100644 index 0000000..3fe4db1 --- /dev/null +++ b/app/src/main/java/org/xutils/view/annotation/Event.java @@ -0,0 +1,48 @@ +package org.xutils.view.annotation; + +import android.view.View; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 事件注解. + * 被注解的方法必须具备以下形式: + * 1. private 修饰 + * 2. 返回值类型没有要求 + * 3. 参数签名和type的接口要求的参数签名一致. + * Author: wyouflf + * Date: 13-9-9 + * Time: 下午12:43 + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Event { + + /** + * 控件的id集合, id小于1时不执行ui事件绑定. + */ + int[] value(); + + /** + * 控件的parent控件的id集合, 组合为(value[i], parentId[i] or 0). + */ + int[] parentId() default 0; + + /** + * 事件的listener, 默认为点击事件. + */ + Class type() default View.OnClickListener.class; + + /** + * 事件的setter方法名, 默认为set+type#simpleName. + */ + String setter() default ""; + + /** + * 如果type的接口类型提供多个方法, 需要使用此参数指定方法名. + */ + String method() default ""; +} diff --git a/app/src/main/java/org/xutils/view/annotation/ViewInject.java b/app/src/main/java/org/xutils/view/annotation/ViewInject.java new file mode 100644 index 0000000..95b8043 --- /dev/null +++ b/app/src/main/java/org/xutils/view/annotation/ViewInject.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.xutils.view.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ViewInject { + + int value(); + + /* parent view id */ + int parentId() default 0; +} diff --git a/app/src/main/java/org/xutils/x.java b/app/src/main/java/org/xutils/x.java new file mode 100644 index 0000000..57cfcc9 --- /dev/null +++ b/app/src/main/java/org/xutils/x.java @@ -0,0 +1,131 @@ +package org.xutils; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.Context; + +import org.xutils.common.TaskController; +import org.xutils.common.task.TaskControllerImpl; +import org.xutils.db.DbManagerImpl; +import org.xutils.ex.DbException; +import org.xutils.http.HttpManagerImpl; +import org.xutils.image.ImageManagerImpl; +import org.xutils.view.ViewInjectorImpl; + +import java.lang.reflect.Method; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; + + +/** + * Created by wyouflf on 15/6/10. + * 任务控制中心, http, image, db, view注入等接口的入口. + * 需要在在application的onCreate中初始化: x.Ext.init(this); + */ +public final class x { + + private x() { + } + + public static boolean isDebug() { + return Ext.debug; + } + + public static Application app() { + if (Ext.app == null) { + try { + // 仅在IDE进行布局预览时使用,真机或模拟器不使用MockApplication. + @SuppressLint("PrivateApi") + Class renderActionClass = Class.forName("com.android.layoutlib.bridge.impl.RenderAction"); + Method method = renderActionClass.getDeclaredMethod("getCurrentContext"); + Context context = (Context) method.invoke(null); + Ext.app = new MockApplication(context); + } catch (Throwable ignored) { + throw new RuntimeException("please invoke x.Ext.init(app) on Application#onCreate()" + + " and register your Application in manifest."); + } + } + return Ext.app; + } + + public static TaskController task() { + return Ext.taskController; + } + + public static HttpManager http() { + if (Ext.httpManager == null) { + HttpManagerImpl.registerInstance(); + } + return Ext.httpManager; + } + + public static ImageManager image() { + if (Ext.imageManager == null) { + ImageManagerImpl.registerInstance(); + } + return Ext.imageManager; + } + + public static ViewInjector view() { + if (Ext.viewInjector == null) { + ViewInjectorImpl.registerInstance(); + } + return Ext.viewInjector; + } + + public static DbManager getDb(DbManager.DaoConfig daoConfig) throws DbException { + return DbManagerImpl.getInstance(daoConfig); + } + + public static class Ext { + private static boolean debug; + private static Application app; + private static TaskController taskController; + private static HttpManager httpManager; + private static ImageManager imageManager; + private static ViewInjector viewInjector; + + private Ext() { + } + + public static void init(Application app) { + TaskControllerImpl.registerInstance(); + if (Ext.app == null) { + Ext.app = app; + } + } + + public static void setDebug(boolean debug) { + Ext.debug = debug; + } + + public static void setTaskController(TaskController taskController) { + if (Ext.taskController == null) { + Ext.taskController = taskController; + } + } + + public static void setHttpManager(HttpManager httpManager) { + Ext.httpManager = httpManager; + } + + public static void setImageManager(ImageManager imageManager) { + Ext.imageManager = imageManager; + } + + public static void setViewInjector(ViewInjector viewInjector) { + Ext.viewInjector = viewInjector; + } + + public static void setDefaultHostnameVerifier(HostnameVerifier hostnameVerifier) { + HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier); + } + } + + private static class MockApplication extends Application { + public MockApplication(Context baseContext) { + this.attachBaseContext(baseContext); + } + } +} diff --git a/app/src/main/res/color/tab_text_color_selector.xml b/app/src/main/res/color/tab_text_color_selector.xml new file mode 100644 index 0000000..e09ba98 --- /dev/null +++ b/app/src/main/res/color/tab_text_color_selector.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_button.xml b/app/src/main/res/drawable/bg_button.xml new file mode 100644 index 0000000..9fe7d14 --- /dev/null +++ b/app/src/main/res/drawable/bg_button.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_info_linear.xml b/app/src/main/res/drawable/bg_info_linear.xml new file mode 100644 index 0000000..1645598 --- /dev/null +++ b/app/src/main/res/drawable/bg_info_linear.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_input.xml b/app/src/main/res/drawable/bg_input.xml new file mode 100644 index 0000000..b6af828 --- /dev/null +++ b/app/src/main/res/drawable/bg_input.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_sel.xml b/app/src/main/res/drawable/bg_sel.xml new file mode 100644 index 0000000..ad4ff3a --- /dev/null +++ b/app/src/main/res/drawable/bg_sel.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_unsel.xml b/app/src/main/res/drawable/bg_unsel.xml new file mode 100644 index 0000000..21ab147 --- /dev/null +++ b/app/src/main/res/drawable/bg_unsel.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/forward.xml b/app/src/main/res/drawable/forward.xml new file mode 100644 index 0000000..48ab0fe --- /dev/null +++ b/app/src/main/res/drawable/forward.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..05d2afe --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml new file mode 100644 index 0000000..4dd21fb --- /dev/null +++ b/app/src/main/res/layout/activity_auth.xml @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +