parent
ce43e48097
commit
7b15d4c052
@ -0,0 +1,13 @@ |
||||
package com.lyy.frame; |
||||
|
||||
import android.app.Application; |
||||
import android.test.ApplicationTestCase; |
||||
|
||||
/** |
||||
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> |
||||
*/ |
||||
public class ApplicationTest extends ApplicationTestCase<Application> { |
||||
public ApplicationTest() { |
||||
super(Application.class); |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
package com.arialyy.frame.base; |
||||
|
||||
import android.app.Application; |
||||
import android.content.Context; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
*/ |
||||
|
||||
public class BaseApp { |
||||
public static Context context; |
||||
public static Application app; |
||||
} |
@ -0,0 +1,108 @@ |
||||
package com.arialyy.frame.base; |
||||
|
||||
import android.animation.Animator; |
||||
import android.animation.AnimatorListenerAdapter; |
||||
import android.animation.AnimatorSet; |
||||
import android.animation.IntEvaluator; |
||||
import android.animation.ObjectAnimator; |
||||
import android.animation.ValueAnimator; |
||||
import android.app.Dialog; |
||||
import android.databinding.ViewDataBinding; |
||||
import android.graphics.Color; |
||||
import android.graphics.drawable.ColorDrawable; |
||||
import android.os.Bundle; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import android.view.Window; |
||||
import android.view.WindowManager; |
||||
import android.view.animation.BounceInterpolator; |
||||
import com.arialyy.frame.core.AbsDialogFragment; |
||||
import com.arialyy.frame.util.AndroidUtils; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/12/4. |
||||
*/ |
||||
|
||||
public abstract class BaseDialog<VB extends ViewDataBinding> extends AbsDialogFragment<VB> { |
||||
private WindowManager.LayoutParams mWpm; |
||||
private Window mWindow; |
||||
protected boolean useDefaultAnim = true; |
||||
|
||||
@Override protected void init(Bundle savedInstanceState) { |
||||
mWindow = getDialog().getWindow(); |
||||
if (mWindow != null) { |
||||
mWpm = mWindow.getAttributes(); |
||||
} |
||||
if (mWpm != null && mWindow != null) { |
||||
//mView = mWindow.getDecorView();
|
||||
mRootView.setBackgroundColor(Color.WHITE); |
||||
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); |
||||
//in();
|
||||
if (useDefaultAnim) { |
||||
in1(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override public void dismiss() { |
||||
if (mWpm != null && mWindow != null) { |
||||
if (useDefaultAnim) { |
||||
out(); |
||||
} |
||||
} else { |
||||
super.dismiss(); |
||||
} |
||||
} |
||||
|
||||
@Override protected void dataCallback(int result, Object data) { |
||||
|
||||
} |
||||
|
||||
/** |
||||
* 进场动画 |
||||
*/ |
||||
private void in() { |
||||
int height = AndroidUtils.getScreenParams(getContext())[1]; |
||||
ValueAnimator animator = ValueAnimator.ofObject(new IntEvaluator(), -height / 2, 0); |
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
||||
@Override public void onAnimationUpdate(ValueAnimator animation) { |
||||
mWpm.y = (int) animation.getAnimatedValue(); |
||||
mWindow.setAttributes(mWpm); |
||||
} |
||||
}); |
||||
animator.setInterpolator(new BounceInterpolator()); //弹跳
|
||||
Animator alpha = ObjectAnimator.ofFloat(mRootView, "alpha", 0f, 1f); |
||||
AnimatorSet set = new AnimatorSet(); |
||||
set.play(animator).with(alpha); |
||||
set.setDuration(2000).start(); |
||||
} |
||||
|
||||
private void in1() { |
||||
Animator alpha = ObjectAnimator.ofFloat(mRootView, "alpha", 0f, 1f); |
||||
alpha.setDuration(800); |
||||
alpha.start(); |
||||
} |
||||
|
||||
/** |
||||
* 重力动画 |
||||
*/ |
||||
private void out() { |
||||
int height = AndroidUtils.getScreenParams(getContext())[1]; |
||||
ValueAnimator animator = ValueAnimator.ofObject(new IntEvaluator(), 0, height / 3); |
||||
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
||||
@Override public void onAnimationUpdate(ValueAnimator animation) { |
||||
mWpm.y = (int) animation.getAnimatedValue(); |
||||
mWindow.setAttributes(mWpm); |
||||
} |
||||
}); |
||||
Animator alpha = ObjectAnimator.ofFloat(mRootView, "alpha", 1f, 0f); |
||||
AnimatorSet set = new AnimatorSet(); |
||||
set.play(animator).with(alpha); |
||||
set.addListener(new AnimatorListenerAdapter() { |
||||
@Override public void onAnimationEnd(Animator animation) { |
||||
BaseDialog.super.dismiss(); |
||||
} |
||||
}); |
||||
set.setDuration(600).start(); |
||||
} |
||||
} |
@ -0,0 +1,19 @@ |
||||
package com.arialyy.frame.base; |
||||
|
||||
import android.databinding.ViewDataBinding; |
||||
import com.arialyy.frame.core.AbsFragment; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2017/12/1. |
||||
*/ |
||||
public abstract class BaseFragment<VB extends ViewDataBinding> extends AbsFragment<VB> { |
||||
public int color; |
||||
|
||||
@Override protected void dataCallback(int result, Object obj) { |
||||
|
||||
} |
||||
|
||||
@Override protected void onDelayLoad() { |
||||
|
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
package com.arialyy.frame.base; |
||||
|
||||
import android.arch.lifecycle.ViewModel; |
||||
import com.arialyy.frame.base.net.NetManager; |
||||
import com.arialyy.frame.util.StringUtil; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
* ViewModule只能是public |
||||
*/ |
||||
|
||||
public class BaseViewModule extends ViewModel { |
||||
protected NetManager mNetManager; |
||||
protected String TAG = ""; |
||||
|
||||
public BaseViewModule() { |
||||
mNetManager = NetManager.getInstance(); |
||||
TAG = StringUtil.getClassName(this); |
||||
} |
||||
} |
@ -0,0 +1,52 @@ |
||||
package com.arialyy.frame.base; |
||||
|
||||
import android.annotation.TargetApi; |
||||
import android.app.Activity; |
||||
import android.content.Context; |
||||
import android.graphics.Color; |
||||
import android.os.Build; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
|
||||
public class StatusBarCompat { |
||||
private static final int INVALID_VAL = -1; |
||||
private static final int COLOR_DEFAULT = Color.parseColor("#20000000"); |
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
||||
public static void compat(Activity activity, int statusColor) { |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
if (statusColor != INVALID_VAL) { |
||||
activity.getWindow().setStatusBarColor(statusColor); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT |
||||
&& Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { |
||||
int color = COLOR_DEFAULT; |
||||
ViewGroup contentView = activity.findViewById(android.R.id.content); |
||||
if (statusColor != INVALID_VAL) { |
||||
color = statusColor; |
||||
} |
||||
View statusBarView = new View(activity); |
||||
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, |
||||
getStatusBarHeight(activity)); |
||||
statusBarView.setBackgroundColor(color); |
||||
contentView.addView(statusBarView, lp); |
||||
} |
||||
} |
||||
|
||||
public static void compat(Activity activity) { |
||||
compat(activity, INVALID_VAL); |
||||
} |
||||
|
||||
public static int getStatusBarHeight(Context context) { |
||||
int result = 0; |
||||
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); |
||||
if (resourceId > 0) { |
||||
result = context.getResources().getDimensionPixelSize(resourceId); |
||||
} |
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,28 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
import com.google.gson.Gson; |
||||
import com.google.gson.JsonDeserializationContext; |
||||
import com.google.gson.JsonDeserializer; |
||||
import com.google.gson.JsonElement; |
||||
import com.google.gson.JsonObject; |
||||
import com.google.gson.JsonParseException; |
||||
import java.lang.reflect.Type; |
||||
|
||||
/** |
||||
* 自定义Gson描述 |
||||
* Created by “Aria.Lao” on 2016/10/26. |
||||
* |
||||
* @param <T> 服务器数据实体 |
||||
*/ |
||||
public class BasicDeserializer<T> implements JsonDeserializer<T> { |
||||
@Override |
||||
public T deserialize(JsonElement element, Type typeOfT, JsonDeserializationContext context) |
||||
throws JsonParseException { |
||||
JsonObject root = element.getAsJsonObject(); |
||||
if (JsonCodeAnalysisUtil.isSuccess(root)) { |
||||
return new Gson().fromJson(root.get("object"), typeOfT); |
||||
} else { |
||||
throw new IllegalStateException(root.get("rltmsg").getAsString()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
import com.arialyy.frame.util.show.FL; |
||||
import com.arialyy.frame.util.show.L; |
||||
import rx.Observable; |
||||
import rx.android.schedulers.AndroidSchedulers; |
||||
import rx.functions.Func1; |
||||
import rx.schedulers.Schedulers; |
||||
|
||||
/** |
||||
* Created by “Aria.Lao” on 2016/10/26. |
||||
* HTTP数据回调 |
||||
*/ |
||||
public abstract class HttpCallback<T> implements INetResponse<T>, Observable.Transformer<T, T> { |
||||
|
||||
@Override public void onFailure(Throwable e) { |
||||
L.e("HttpCallback", FL.getExceptionString(e)); |
||||
} |
||||
|
||||
@Override public Observable<T> call(Observable<T> observable) { |
||||
Observable<T> tObservable = observable.subscribeOn(Schedulers.io()) |
||||
.unsubscribeOn(Schedulers.io()) |
||||
.observeOn(AndroidSchedulers.mainThread()) |
||||
.map(new Func1<T, T>() { |
||||
@Override public T call(T t) { |
||||
onResponse(t); |
||||
return t; |
||||
} |
||||
}) |
||||
.onErrorReturn(new Func1<Throwable, T>() { |
||||
@Override public T call(Throwable throwable) { |
||||
onFailure(throwable); |
||||
return null; |
||||
} |
||||
}); |
||||
tObservable.subscribe(); |
||||
return tObservable; |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
/** |
||||
* Created by “Aria.Lao” on 2016/10/25. |
||||
* 网络响应接口,所有的网络回调都要继承该接口 |
||||
* |
||||
* @param <T> 数据实体结构 |
||||
*/ |
||||
public interface INetResponse<T> { |
||||
|
||||
/** |
||||
* 网络请求成功 |
||||
*/ |
||||
public void onResponse(T response); |
||||
|
||||
/** |
||||
* 请求失败 |
||||
*/ |
||||
public void onFailure(Throwable e); |
||||
} |
@ -0,0 +1,23 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
import com.google.gson.JsonObject; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
*/ |
||||
|
||||
public class JsonCodeAnalysisUtil { |
||||
|
||||
public static boolean isSuccess(JsonObject obj) { |
||||
JSONObject object = null; |
||||
try { |
||||
object = new JSONObject(obj.toString()); |
||||
return object.optBoolean("success"); |
||||
} catch (JSONException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,112 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
import android.util.SparseArray; |
||||
import com.arialyy.frame.base.BaseApp; |
||||
import com.arialyy.frame.config.CommonConstant; |
||||
import com.arialyy.frame.config.NetConstant; |
||||
import com.franmontiel.persistentcookiejar.ClearableCookieJar; |
||||
import com.franmontiel.persistentcookiejar.PersistentCookieJar; |
||||
import com.franmontiel.persistentcookiejar.cache.SetCookieCache; |
||||
import com.franmontiel.persistentcookiejar.persistence.SharedPrefsCookiePersistor; |
||||
import com.google.gson.Gson; |
||||
import java.util.concurrent.TimeUnit; |
||||
import okhttp3.OkHttpClient; |
||||
import retrofit2.Retrofit; |
||||
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; |
||||
import retrofit2.converter.gson.GsonConverterFactory; |
||||
|
||||
/** |
||||
* Created by “Aria.Lao” on 2016/10/25. |
||||
* 网络管理器 |
||||
*/ |
||||
public class NetManager { |
||||
private static final Object LOCK = new Object(); |
||||
private static volatile NetManager INSTANCE = null; |
||||
private static final long TIME_OUT = 8 * 1000; |
||||
private Retrofit mRetrofit; |
||||
private Retrofit.Builder mBuilder; |
||||
private SparseArray<GsonConverterFactory> mConverterFactorys = new SparseArray<>(); |
||||
private ClearableCookieJar mCookieJar; |
||||
|
||||
private NetManager() { |
||||
init(); |
||||
} |
||||
|
||||
public static NetManager getInstance() { |
||||
if (INSTANCE == null) { |
||||
synchronized (LOCK) { |
||||
INSTANCE = new NetManager(); |
||||
} |
||||
} |
||||
return INSTANCE; |
||||
} |
||||
|
||||
OkHttpClient okHttpClient; |
||||
|
||||
private void init() { |
||||
mCookieJar = new PersistentCookieJar(new SetCookieCache(), |
||||
new SharedPrefsCookiePersistor(BaseApp.context)); |
||||
//OkHttpClient okHttpClient = provideOkHttpClient();
|
||||
okHttpClient = provideOkHttpClient(); |
||||
} |
||||
|
||||
public ClearableCookieJar getCookieJar() { |
||||
return mCookieJar; |
||||
} |
||||
|
||||
/** |
||||
* 执行网络请求 |
||||
* |
||||
* @param service 服务器返回的实体类型 |
||||
* @param gson gson 为传入的数据解析器,ENTITY 为 网络实体 |
||||
* <pre><code> |
||||
* Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<ENTITY>() { |
||||
* }.getType(), new BasicDeserializer<ENTITY>()).create(); |
||||
* |
||||
* //如启动图,需要将‘ENTITY’替换为启动图实体‘LauncherImgEntity’
|
||||
* Gson gson = new GsonBuilder().registerTypeAdapter(new TypeToken<LauncherImgEntity>() { |
||||
* }.getType(), new BasicDeserializer<LauncherImgEntity>()).create(); |
||||
* |
||||
* </code></pre> |
||||
*/ |
||||
public <SERVICE> SERVICE request(Class<SERVICE> service, Gson gson) { |
||||
GsonConverterFactory f = null; |
||||
if (gson == null) { |
||||
f = GsonConverterFactory.create(); |
||||
} else { |
||||
f = GsonConverterFactory.create(gson); |
||||
} |
||||
; |
||||
final Retrofit.Builder builder = new Retrofit.Builder().client(okHttpClient) |
||||
.baseUrl(NetConstant.BASE_URL) |
||||
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()); |
||||
builder.addConverterFactory(f); |
||||
return builder.build().create(service); |
||||
} |
||||
|
||||
/** |
||||
* 创建OKHTTP |
||||
*/ |
||||
private OkHttpClient provideOkHttpClient() { |
||||
final OkHttpClient.Builder builder = new OkHttpClient.Builder(); |
||||
if (CommonConstant.DEBUG) { |
||||
//HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
|
||||
//logging.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
//builder.addInterceptor(logging);
|
||||
builder.addInterceptor(new OkHttpLogger()); |
||||
} |
||||
builder.connectTimeout(TIME_OUT, TimeUnit.MILLISECONDS) |
||||
.readTimeout(TIME_OUT, TimeUnit.MILLISECONDS); |
||||
builder.cookieJar(mCookieJar); |
||||
//builder.addInterceptor(chain -> {
|
||||
// //String cookies = CookieUtil.getCookies();
|
||||
// Request request = chain.request().newBuilder()
|
||||
// //.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
// //.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||
// //.addHeader("Cookie", cookies)
|
||||
// .build();
|
||||
// return chain.proceed(request);
|
||||
//});
|
||||
return builder.build(); |
||||
} |
||||
} |
@ -0,0 +1,65 @@ |
||||
package com.arialyy.frame.base.net; |
||||
|
||||
import com.arialyy.frame.util.show.FL; |
||||
import com.arialyy.frame.util.show.L; |
||||
import java.io.IOException; |
||||
import java.nio.charset.Charset; |
||||
import java.util.concurrent.TimeUnit; |
||||
import okhttp3.Headers; |
||||
import okhttp3.Interceptor; |
||||
import okhttp3.MediaType; |
||||
import okhttp3.Request; |
||||
import okhttp3.Response; |
||||
import okhttp3.ResponseBody; |
||||
import okio.Buffer; |
||||
import okio.BufferedSource; |
||||
|
||||
/** |
||||
* Created by Lyy on 2016/9/19. |
||||
* 自定义的 OKHTTP 日志 |
||||
*/ |
||||
public class OkHttpLogger implements Interceptor { |
||||
final static String TAG = "OKHTTP"; |
||||
|
||||
@Override public Response intercept(Chain chain) throws IOException { |
||||
Request request = chain.request(); |
||||
long startNs = System.nanoTime(); |
||||
Response response = chain.proceed(request); |
||||
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); |
||||
ResponseBody responseBody = response.body(); |
||||
long contentLength = responseBody.contentLength(); |
||||
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; |
||||
L.d(TAG, "<-- " |
||||
+ response.code() |
||||
+ ' ' |
||||
+ response.message() |
||||
+ ' ' |
||||
+ response.request().url() |
||||
+ " (" |
||||
+ tookMs |
||||
+ "ms" |
||||
+ (", " + bodySize + " body") |
||||
+ ')'); |
||||
//Headers headers = response.headers();
|
||||
//for (int i = 0, count = headers.size(); i < count; i++) {
|
||||
// FL.d(TAG, headers.name(i) + ": " + headers.value(i));
|
||||
//}
|
||||
BufferedSource source = responseBody.source(); |
||||
source.request(Long.MAX_VALUE); // Buffer the entire body.
|
||||
Buffer buffer = source.buffer(); |
||||
Charset UTF8 = Charset.forName("UTF-8"); |
||||
Charset charset = UTF8; |
||||
MediaType contentType = responseBody.contentType(); |
||||
if (contentType != null) { |
||||
charset = contentType.charset(UTF8); |
||||
} |
||||
if (contentLength != 0) { |
||||
//FL.j(TAG, buffer.clone().readString(charset));
|
||||
L.j(buffer.clone().readString(charset)); |
||||
} |
||||
|
||||
L.d(TAG, "<-- END HTTP (" + buffer.size() + "-byte body)"); |
||||
|
||||
return response; |
||||
} |
||||
} |
@ -0,0 +1,968 @@ |
||||
/* |
||||
* 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 com.arialyy.frame.cache; |
||||
|
||||
import java.io.BufferedInputStream; |
||||
import java.io.BufferedWriter; |
||||
import java.io.Closeable; |
||||
import java.io.EOFException; |
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileOutputStream; |
||||
import java.io.FileWriter; |
||||
import java.io.FilterOutputStream; |
||||
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.StringWriter; |
||||
import java.io.Writer; |
||||
import java.lang.reflect.Array; |
||||
import java.nio.charset.Charset; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.ExecutorService; |
||||
import java.util.concurrent.LinkedBlockingQueue; |
||||
import java.util.concurrent.ThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* ***************************************************************************** |
||||
* Taken from the JB source code, can be found in: |
||||
* libcore/luni/src/main/java/libcore/io/DiskLruCache.java |
||||
* or direct link: |
||||
* https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
|
||||
* ***************************************************************************** |
||||
* |
||||
* A cache that uses a bounded amount of space on a filesystem. Each cache |
||||
* entry has a string key and a fixed number of values. Values are byte |
||||
* sequences, accessible as streams or files. Each value must be between {@code |
||||
* 0} and {@code Integer.MAX_VALUE} bytes in length. |
||||
* |
||||
* The cache stores its data in a directory on the filesystem. This |
||||
* directory must be exclusive to the cache; the cache may delete or overwrite |
||||
* files from its directory. It is an error for multiple processes to use the |
||||
* same cache directory at the same time. |
||||
* |
||||
* This cache limits the number of bytes that it will store on the |
||||
* filesystem. When the number of stored bytes exceeds the limit, the cache will |
||||
* remove entries in the background until the limit is satisfied. The limit is |
||||
* not strict: the cache may temporarily exceed it while waiting for files to be |
||||
* deleted. The limit does not include filesystem overhead or the cache |
||||
* journal so space-sensitive applications should set a conservative limit. |
||||
* |
||||
* Clients call {@link #edit} to create or update the values of an entry. An |
||||
* entry may have only one editor at one time; if a value is not available to be |
||||
* edited then {@link #edit} will return null. |
||||
* |
||||
* When an entry is being <strong>created</strong> it is necessary to |
||||
* supply a full set of values; the empty value should be used as a |
||||
* placeholder if necessary. |
||||
* When an entry is being <strong>edited</strong>, it is not necessary |
||||
* to supply data for every value; values default to their previous |
||||
* value. |
||||
* |
||||
* Clients call {@link #get} to read a snapshot of an entry. The read will |
||||
* observe the value at the time that {@link #get} was called. Updates and |
||||
* removals after the call do not impact ongoing reads. |
||||
* |
||||
* This class is tolerant of some I/O errors. If files are missing from the |
||||
* filesystem, the corresponding entries will be dropped from the cache. If |
||||
* an error occurs while writing a cache value, the edit will fail silently. |
||||
* Callers should handle other problems by catching {@code IOException} and |
||||
* responding appropriately. |
||||
*/ |
||||
public final class DiskLruCache implements Closeable { |
||||
static final String JOURNAL_FILE = "journal"; |
||||
static final String JOURNAL_FILE_TMP = "journal.tmp"; |
||||
static final String MAGIC = "libcore.io.DiskLruCache"; |
||||
static final String VERSION_1 = "1"; |
||||
static final long ANY_SEQUENCE_NUMBER = -1; |
||||
private static final String CLEAN = "CLEAN"; |
||||
private static final String DIRTY = "DIRTY"; |
||||
private static final String REMOVE = "REMOVE"; |
||||
private static final String READ = "READ"; |
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8"); |
||||
private static final int IO_BUFFER_SIZE = 8 * 1024; |
||||
|
||||
/* |
||||
* This cache uses a journal file named "journal". A typical journal file |
||||
* looks like this: |
||||
* libcore.io.DiskLruCache |
||||
* 1 |
||||
* 100 |
||||
* 2 |
||||
* |
||||
* CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054 |
||||
* DIRTY 335c4c6028171cfddfbaae1a9c313c52 |
||||
* CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342 |
||||
* REMOVE 335c4c6028171cfddfbaae1a9c313c52 |
||||
* DIRTY 1ab96a171faeeee38496d8b330771a7a |
||||
* CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234 |
||||
* READ 335c4c6028171cfddfbaae1a9c313c52 |
||||
* READ 3400330d1dfc7f3f7f4b8d4d803dfcf6 |
||||
* |
||||
* The first five lines of the journal form its header. They are the |
||||
* constant string "libcore.io.DiskLruCache", the disk cache's version, |
||||
* the application's version, the value count, and a blank line. |
||||
* |
||||
* Each of the subsequent lines in the file is a record of the state of a |
||||
* cache entry. Each line contains space-separated values: a state, a key, |
||||
* and optional state-specific values. |
||||
* o DIRTY lines track that an entry is actively being created or updated. |
||||
* Every successful DIRTY action should be followed by a CLEAN or REMOVE |
||||
* action. DIRTY lines without a matching CLEAN or REMOVE indicate that |
||||
* temporary files may need to be deleted. |
||||
* o CLEAN lines track a cache entry that has been successfully published |
||||
* and may be read. A publish line is followed by the lengths of each of |
||||
* its values. |
||||
* o READ lines track accesses for LRU. |
||||
* o REMOVE lines track entries that have been deleted. |
||||
* |
||||
* The journal file is appended to as cache operations occur. The journal may |
||||
* occasionally be compacted by dropping redundant lines. A temporary file named |
||||
* "journal.tmp" will be used during compaction; that file should be deleted if |
||||
* it exists when the cache is opened. |
||||
*/ |
||||
|
||||
private final File directory; |
||||
private final File journalFile; |
||||
private final File journalFileTmp; |
||||
private final int appVersion; |
||||
private final long maxSize; |
||||
private final int valueCount; |
||||
private long size = 0; |
||||
private Writer journalWriter; |
||||
private final LinkedHashMap<String, Entry> lruEntries = |
||||
new LinkedHashMap<String, Entry>(0, 0.75f, true); |
||||
private int redundantOpCount; |
||||
|
||||
/** |
||||
* To differentiate between old and current snapshots, each entry is given |
||||
* a sequence number each time an edit is committed. A snapshot is stale if |
||||
* its sequence number is not equal to its entry's sequence number. |
||||
*/ |
||||
private long nextSequenceNumber = 0; |
||||
|
||||
/* From java.util.Arrays */ |
||||
@SuppressWarnings("unchecked") |
||||
private static <T> T[] copyOfRange(T[] original, int start, int end) { |
||||
final int originalLength = original.length; // For exception priority compatibility.
|
||||
if (start > end) { |
||||
throw new IllegalArgumentException(); |
||||
} |
||||
if (start < 0 || start > originalLength) { |
||||
throw new ArrayIndexOutOfBoundsException(); |
||||
} |
||||
final int resultLength = end - start; |
||||
final int copyLength = Math.min(resultLength, originalLength - start); |
||||
final T[] result = |
||||
(T[]) Array.newInstance(original.getClass().getComponentType(), resultLength); |
||||
System.arraycopy(original, start, result, 0, copyLength); |
||||
return result; |
||||
} |
||||
|
||||
/** |
||||
* Returns the remainder of 'reader' as a string, closing it when done. |
||||
*/ |
||||
public static String readFully(Reader reader) throws IOException { |
||||
try { |
||||
StringWriter writer = new StringWriter(); |
||||
char[] buffer = new char[1024]; |
||||
int count; |
||||
while ((count = reader.read(buffer)) != -1) { |
||||
writer.write(buffer, 0, count); |
||||
} |
||||
return writer.toString(); |
||||
} finally { |
||||
reader.close(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the ASCII characters up to but not including the next "\r\n", or |
||||
* "\n". |
||||
* |
||||
* @throws EOFException if the stream is exhausted before the next newline |
||||
* character. |
||||
*/ |
||||
public static String readAsciiLine(InputStream in) throws IOException { |
||||
|
||||
StringBuilder result = new StringBuilder(80); |
||||
while (true) { |
||||
int c = in.read(); |
||||
if (c == -1) { |
||||
throw new EOFException(); |
||||
} else if (c == '\n') { |
||||
break; |
||||
} |
||||
|
||||
result.append((char) c); |
||||
} |
||||
int length = result.length(); |
||||
if (length > 0 && result.charAt(length - 1) == '\r') { |
||||
result.setLength(length - 1); |
||||
} |
||||
return result.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. |
||||
*/ |
||||
public static void closeQuietly(Closeable closeable) { |
||||
if (closeable != null) { |
||||
try { |
||||
closeable.close(); |
||||
} catch (RuntimeException rethrown) { |
||||
throw rethrown; |
||||
} catch (Exception ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Recursively delete everything in {@code dir}. |
||||
*/ |
||||
// TODO: this should specify paths as Strings rather than as Files
|
||||
public static void deleteContents(File dir) throws IOException { |
||||
File[] files = dir.listFiles(); |
||||
if (files == null) { |
||||
throw new IllegalArgumentException("not a directory: " + dir); |
||||
} |
||||
for (File file : files) { |
||||
if (file.isDirectory()) { |
||||
deleteContents(file); |
||||
} |
||||
if (!file.delete()) { |
||||
throw new IOException("failed to delete file: " + file); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This cache uses a single background thread to evict entries. |
||||
*/ |
||||
private final ExecutorService executorService = |
||||
new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); |
||||
private final Callable<Void> cleanupCallable = new Callable<Void>() { |
||||
@Override |
||||
public Void call() throws Exception { |
||||
synchronized (DiskLruCache.this) { |
||||
if (journalWriter == null) { |
||||
return null; // closed
|
||||
} |
||||
trimToSize(); |
||||
if (journalRebuildRequired()) { |
||||
rebuildJournal(); |
||||
redundantOpCount = 0; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
}; |
||||
|
||||
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { |
||||
this.directory = directory; |
||||
this.appVersion = appVersion; |
||||
this.journalFile = new File(directory, JOURNAL_FILE); |
||||
this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP); |
||||
this.valueCount = valueCount; |
||||
this.maxSize = maxSize; |
||||
} |
||||
|
||||
/** |
||||
* Opens the cache in {@code directory}, creating a cache if none exists |
||||
* there. |
||||
* |
||||
* @param directory a writable directory |
||||
* @param valueCount the number of values per cache entry. Must be positive. |
||||
* @param maxSize the maximum number of bytes this cache should use to store |
||||
* @throws IOException if reading or writing the cache directory fails |
||||
*/ |
||||
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) |
||||
throws IOException { |
||||
if (maxSize <= 0) { |
||||
throw new IllegalArgumentException("maxSize <= 0"); |
||||
} |
||||
if (valueCount <= 0) { |
||||
throw new IllegalArgumentException("valueCount <= 0"); |
||||
} |
||||
|
||||
// prefer to pick up where we left off
|
||||
DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); |
||||
if (cache.journalFile.exists()) { |
||||
try { |
||||
cache.readJournal(); |
||||
cache.processJournal(); |
||||
cache.journalWriter = |
||||
new BufferedWriter(new FileWriter(cache.journalFile, true), IO_BUFFER_SIZE); |
||||
return cache; |
||||
} catch (IOException journalIsCorrupt) { |
||||
// System.logW("DiskLruCache " + directory + " is corrupt: "
|
||||
// + journalIsCorrupt.getMessage() + ", removing");
|
||||
cache.delete(); |
||||
} |
||||
} |
||||
|
||||
// create a new empty cache
|
||||
directory.mkdirs(); |
||||
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); |
||||
cache.rebuildJournal(); |
||||
return cache; |
||||
} |
||||
|
||||
private void readJournal() throws IOException { |
||||
InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); |
||||
try { |
||||
String magic = readAsciiLine(in); |
||||
String version = readAsciiLine(in); |
||||
String appVersionString = readAsciiLine(in); |
||||
String valueCountString = readAsciiLine(in); |
||||
String blank = readAsciiLine(in); |
||||
if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion) |
||||
.equals(appVersionString) || !Integer.toString(valueCount).equals(valueCountString) || !"" |
||||
.equals(blank)) { |
||||
throw new IOException("unexpected journal header: [" |
||||
+ magic |
||||
+ ", " |
||||
+ version |
||||
+ ", " |
||||
+ valueCountString |
||||
+ ", " |
||||
+ blank |
||||
+ "]"); |
||||
} |
||||
|
||||
while (true) { |
||||
try { |
||||
readJournalLine(readAsciiLine(in)); |
||||
} catch (EOFException endOfJournal) { |
||||
break; |
||||
} |
||||
} |
||||
} finally { |
||||
closeQuietly(in); |
||||
} |
||||
} |
||||
|
||||
private void readJournalLine(String line) throws IOException { |
||||
String[] parts = line.split(" "); |
||||
if (parts.length < 2) { |
||||
throw new IOException("unexpected journal line: " + line); |
||||
} |
||||
|
||||
String key = parts[1]; |
||||
if (parts[0].equals(REMOVE) && parts.length == 2) { |
||||
lruEntries.remove(key); |
||||
return; |
||||
} |
||||
|
||||
Entry entry = lruEntries.get(key); |
||||
if (entry == null) { |
||||
entry = new Entry(key); |
||||
lruEntries.put(key, entry); |
||||
} |
||||
|
||||
if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) { |
||||
entry.readable = true; |
||||
entry.currentEditor = null; |
||||
entry.setLengths(copyOfRange(parts, 2, parts.length)); |
||||
} else if (parts[0].equals(DIRTY) && parts.length == 2) { |
||||
entry.currentEditor = new Editor(entry); |
||||
} else if (parts[0].equals(READ) && parts.length == 2) { |
||||
// this work was already done by calling lruEntries.get()
|
||||
} else { |
||||
throw new IOException("unexpected journal line: " + line); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Computes the initial size and collects garbage as a part of opening the |
||||
* cache. Dirty entries are assumed to be inconsistent and will be deleted. |
||||
*/ |
||||
private void processJournal() throws IOException { |
||||
deleteIfExists(journalFileTmp); |
||||
for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) { |
||||
Entry entry = i.next(); |
||||
if (entry.currentEditor == null) { |
||||
for (int t = 0; t < valueCount; t++) { |
||||
size += entry.lengths[t]; |
||||
} |
||||
} else { |
||||
entry.currentEditor = null; |
||||
for (int t = 0; t < valueCount; t++) { |
||||
deleteIfExists(entry.getCleanFile(t)); |
||||
deleteIfExists(entry.getDirtyFile(t)); |
||||
} |
||||
i.remove(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates a new journal that omits redundant information. This replaces the |
||||
* current journal if it exists. |
||||
*/ |
||||
private synchronized void rebuildJournal() throws IOException { |
||||
if (journalWriter != null) { |
||||
journalWriter.close(); |
||||
} |
||||
|
||||
Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE); |
||||
writer.write(MAGIC); |
||||
writer.write("\n"); |
||||
writer.write(VERSION_1); |
||||
writer.write("\n"); |
||||
writer.write(Integer.toString(appVersion)); |
||||
writer.write("\n"); |
||||
writer.write(Integer.toString(valueCount)); |
||||
writer.write("\n"); |
||||
writer.write("\n"); |
||||
|
||||
for (Entry entry : lruEntries.values()) { |
||||
if (entry.currentEditor != null) { |
||||
writer.write(DIRTY + ' ' + entry.key + '\n'); |
||||
} else { |
||||
writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); |
||||
} |
||||
} |
||||
|
||||
writer.close(); |
||||
journalFileTmp.renameTo(journalFile); |
||||
journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE); |
||||
} |
||||
|
||||
private static void deleteIfExists(File file) throws IOException { |
||||
// try {
|
||||
// Libcore.os.remove(file.getPath());
|
||||
// } catch (ErrnoException errnoException) {
|
||||
// if (errnoException.errno != OsConstants.ENOENT) {
|
||||
// throw errnoException.rethrowAsIOException();
|
||||
// }
|
||||
// }
|
||||
if (file.exists() && !file.delete()) { |
||||
throw new IOException(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns a snapshot of the entry named {@code key}, or null if it doesn't |
||||
* exist is not currently readable. If a value is returned, it is moved to |
||||
* the head of the LRU queue. |
||||
*/ |
||||
public synchronized Snapshot get(String key) throws IOException { |
||||
checkNotClosed(); |
||||
validateKey(key); |
||||
Entry entry = lruEntries.get(key); |
||||
if (entry == null) { |
||||
return null; |
||||
} |
||||
|
||||
if (!entry.readable) { |
||||
return null; |
||||
} |
||||
|
||||
/* |
||||
* Open all streams eagerly to guarantee that we see a single published |
||||
* snapshot. If we opened streams lazily then the streams could come |
||||
* from different edits. |
||||
*/ |
||||
InputStream[] ins = new InputStream[valueCount]; |
||||
try { |
||||
for (int i = 0; i < valueCount; i++) { |
||||
ins[i] = new FileInputStream(entry.getCleanFile(i)); |
||||
} |
||||
} catch (FileNotFoundException e) { |
||||
// a file must have been deleted manually!
|
||||
return null; |
||||
} |
||||
|
||||
redundantOpCount++; |
||||
journalWriter.append(READ + ' ' + key + '\n'); |
||||
if (journalRebuildRequired()) { |
||||
executorService.submit(cleanupCallable); |
||||
} |
||||
|
||||
return new Snapshot(key, entry.sequenceNumber, ins); |
||||
} |
||||
|
||||
/** |
||||
* Returns an editor for the entry named {@code key}, or null if another |
||||
* edit is in progress. |
||||
*/ |
||||
public Editor edit(String key) throws IOException { |
||||
return edit(key, ANY_SEQUENCE_NUMBER); |
||||
} |
||||
|
||||
private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { |
||||
checkNotClosed(); |
||||
validateKey(key); |
||||
Entry entry = lruEntries.get(key); |
||||
if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null |
||||
|| entry.sequenceNumber != expectedSequenceNumber)) { |
||||
return null; // snapshot is stale
|
||||
} |
||||
if (entry == null) { |
||||
entry = new Entry(key); |
||||
lruEntries.put(key, entry); |
||||
} else if (entry.currentEditor != null) { |
||||
return null; // another edit is in progress
|
||||
} |
||||
|
||||
Editor editor = new Editor(entry); |
||||
entry.currentEditor = editor; |
||||
|
||||
// flush the journal before creating files to prevent file leaks
|
||||
journalWriter.write(DIRTY + ' ' + key + '\n'); |
||||
journalWriter.flush(); |
||||
return editor; |
||||
} |
||||
|
||||
/** |
||||
* Returns the directory where this cache stores its data. |
||||
*/ |
||||
public File getDirectory() { |
||||
return directory; |
||||
} |
||||
|
||||
/** |
||||
* Returns the maximum number of bytes that this cache should use to store |
||||
* its data. |
||||
*/ |
||||
public long maxSize() { |
||||
return maxSize; |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of bytes currently being used to store the values in |
||||
* this cache. This may be greater than the max size if a background |
||||
* deletion is pending. |
||||
*/ |
||||
public synchronized long size() { |
||||
return size; |
||||
} |
||||
|
||||
private synchronized void completeEdit(Editor editor, boolean success) throws IOException { |
||||
Entry entry = editor.entry; |
||||
if (entry.currentEditor != editor) { |
||||
throw new IllegalStateException(); |
||||
} |
||||
|
||||
// if this edit is creating the entry for the first time, every index must have a value
|
||||
if (success && !entry.readable) { |
||||
for (int i = 0; i < valueCount; i++) { |
||||
if (!entry.getDirtyFile(i).exists()) { |
||||
editor.abort(); |
||||
throw new IllegalStateException("edit didn't create file " + i); |
||||
} |
||||
} |
||||
} |
||||
|
||||
for (int i = 0; i < valueCount; i++) { |
||||
File dirty = entry.getDirtyFile(i); |
||||
if (success) { |
||||
if (dirty.exists()) { |
||||
File clean = entry.getCleanFile(i); |
||||
dirty.renameTo(clean); |
||||
long oldLength = entry.lengths[i]; |
||||
long newLength = clean.length(); |
||||
entry.lengths[i] = newLength; |
||||
size = size - oldLength + newLength; |
||||
} |
||||
} else { |
||||
deleteIfExists(dirty); |
||||
} |
||||
} |
||||
|
||||
redundantOpCount++; |
||||
entry.currentEditor = null; |
||||
if (entry.readable | success) { |
||||
entry.readable = true; |
||||
journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); |
||||
if (success) { |
||||
entry.sequenceNumber = nextSequenceNumber++; |
||||
} |
||||
} else { |
||||
lruEntries.remove(entry.key); |
||||
journalWriter.write(REMOVE + ' ' + entry.key + '\n'); |
||||
} |
||||
|
||||
if (size > maxSize || journalRebuildRequired()) { |
||||
executorService.submit(cleanupCallable); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* We only rebuild the journal when it will halve the size of the journal |
||||
* and eliminate at least 2000 ops. |
||||
*/ |
||||
private boolean journalRebuildRequired() { |
||||
final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000; |
||||
return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD |
||||
&& redundantOpCount >= lruEntries.size(); |
||||
} |
||||
|
||||
/** |
||||
* Drops the entry for {@code key} if it exists and can be removed. Entries |
||||
* actively being edited cannot be removed. |
||||
* |
||||
* @return true if an entry was removed. |
||||
*/ |
||||
public synchronized boolean remove(String key) throws IOException { |
||||
checkNotClosed(); |
||||
validateKey(key); |
||||
Entry entry = lruEntries.get(key); |
||||
if (entry == null || entry.currentEditor != null) { |
||||
return false; |
||||
} |
||||
|
||||
for (int i = 0; i < valueCount; i++) { |
||||
File file = entry.getCleanFile(i); |
||||
if (!file.delete()) { |
||||
throw new IOException("failed to delete " + file); |
||||
} |
||||
size -= entry.lengths[i]; |
||||
entry.lengths[i] = 0; |
||||
} |
||||
|
||||
redundantOpCount++; |
||||
journalWriter.append(REMOVE + ' ' + key + '\n'); |
||||
lruEntries.remove(key); |
||||
|
||||
if (journalRebuildRequired()) { |
||||
executorService.submit(cleanupCallable); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Returns true if this cache has been closed. |
||||
*/ |
||||
public boolean isClosed() { |
||||
return journalWriter == null; |
||||
} |
||||
|
||||
private void checkNotClosed() { |
||||
if (journalWriter == null) { |
||||
throw new IllegalStateException("cache is closed"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Force buffered operations to the filesystem. |
||||
*/ |
||||
public synchronized void flush() throws IOException { |
||||
checkNotClosed(); |
||||
trimToSize(); |
||||
journalWriter.flush(); |
||||
} |
||||
|
||||
/** |
||||
* Closes this cache. Stored values will remain on the filesystem. |
||||
*/ |
||||
public synchronized void close() throws IOException { |
||||
if (journalWriter == null) { |
||||
return; // already closed
|
||||
} |
||||
for (Entry entry : new ArrayList<Entry>(lruEntries.values())) { |
||||
if (entry.currentEditor != null) { |
||||
entry.currentEditor.abort(); |
||||
} |
||||
} |
||||
trimToSize(); |
||||
journalWriter.close(); |
||||
journalWriter = null; |
||||
} |
||||
|
||||
private void trimToSize() throws IOException { |
||||
while (size > maxSize) { |
||||
// Map.Entry<String, Entry> toEvict = lruEntries.eldest();
|
||||
final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next(); |
||||
remove(toEvict.getKey()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Closes the cache and deletes all of its stored values. This will delete |
||||
* all files in the cache directory including files that weren't created by |
||||
* the cache. |
||||
*/ |
||||
public void delete() throws IOException { |
||||
close(); |
||||
deleteContents(directory); |
||||
} |
||||
|
||||
private void validateKey(String key) { |
||||
if (key.contains(" ") || key.contains("\n") || key.contains("\r")) { |
||||
throw new IllegalArgumentException( |
||||
"keys must not contain spaces or newlines: \"" + key + "\""); |
||||
} |
||||
} |
||||
|
||||
private static String inputStreamToString(InputStream in) throws IOException { |
||||
return readFully(new InputStreamReader(in, UTF_8)); |
||||
} |
||||
|
||||
/** |
||||
* A snapshot of the values for an entry. |
||||
*/ |
||||
public final class Snapshot implements Closeable { |
||||
private final String key; |
||||
private final long sequenceNumber; |
||||
private final InputStream[] ins; |
||||
|
||||
private Snapshot(String key, long sequenceNumber, InputStream[] ins) { |
||||
this.key = key; |
||||
this.sequenceNumber = sequenceNumber; |
||||
this.ins = ins; |
||||
} |
||||
|
||||
/** |
||||
* Returns an editor for this snapshot's entry, or null if either the |
||||
* entry has changed since this snapshot was created or if another edit |
||||
* is in progress. |
||||
*/ |
||||
public Editor edit() throws IOException { |
||||
return DiskLruCache.this.edit(key, sequenceNumber); |
||||
} |
||||
|
||||
/** |
||||
* Returns the unbuffered stream with the value for {@code index}. |
||||
*/ |
||||
public InputStream getInputStream(int index) { |
||||
return ins[index]; |
||||
} |
||||
|
||||
/** |
||||
* Returns the string value for {@code index}. |
||||
*/ |
||||
public String getString(int index) throws IOException { |
||||
return inputStreamToString(getInputStream(index)); |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
for (InputStream in : ins) { |
||||
closeQuietly(in); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Edits the values for an entry. |
||||
*/ |
||||
public final class Editor { |
||||
private final Entry entry; |
||||
private boolean hasErrors; |
||||
|
||||
private Editor(Entry entry) { |
||||
this.entry = entry; |
||||
} |
||||
|
||||
/** |
||||
* Returns an unbuffered input stream to read the last committed value, |
||||
* or null if no value has been committed. |
||||
*/ |
||||
public InputStream newInputStream(int index) throws IOException { |
||||
synchronized (DiskLruCache.this) { |
||||
if (entry.currentEditor != this) { |
||||
throw new IllegalStateException(); |
||||
} |
||||
if (!entry.readable) { |
||||
return null; |
||||
} |
||||
return new FileInputStream(entry.getCleanFile(index)); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Returns the last committed value as a string, or null if no value |
||||
* has been committed. |
||||
*/ |
||||
public String getString(int index) throws IOException { |
||||
InputStream in = newInputStream(index); |
||||
return in != null ? inputStreamToString(in) : null; |
||||
} |
||||
|
||||
/** |
||||
* Returns a new unbuffered output stream to write the value at |
||||
* {@code index}. If the underlying output stream encounters errors |
||||
* when writing to the filesystem, this edit will be aborted when |
||||
* {@link #commit} is called. The returned output stream does not throw |
||||
* IOExceptions. |
||||
*/ |
||||
public OutputStream newOutputStream(int index) throws IOException { |
||||
synchronized (DiskLruCache.this) { |
||||
if (entry.currentEditor != this) { |
||||
throw new IllegalStateException(); |
||||
} |
||||
return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index))); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the value at {@code index} to {@code value}. |
||||
*/ |
||||
public void set(int index, String value) throws IOException { |
||||
Writer writer = null; |
||||
try { |
||||
writer = new OutputStreamWriter(newOutputStream(index), UTF_8); |
||||
writer.write(value); |
||||
} finally { |
||||
closeQuietly(writer); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Commits this edit so it is visible to readers. This releases the |
||||
* edit lock so another edit may be started on the same key. |
||||
*/ |
||||
public void commit() throws IOException { |
||||
if (hasErrors) { |
||||
completeEdit(this, false); |
||||
remove(entry.key); // the previous entry is stale
|
||||
} else { |
||||
completeEdit(this, true); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Aborts this edit. This releases the edit lock so another edit may be |
||||
* started on the same key. |
||||
*/ |
||||
public void abort() throws IOException { |
||||
completeEdit(this, false); |
||||
} |
||||
|
||||
private class FaultHidingOutputStream extends FilterOutputStream { |
||||
private FaultHidingOutputStream(OutputStream out) { |
||||
super(out); |
||||
} |
||||
|
||||
@Override |
||||
public void write(int oneByte) { |
||||
try { |
||||
out.write(oneByte); |
||||
} catch (IOException e) { |
||||
hasErrors = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void write(byte[] buffer, int offset, int length) { |
||||
try { |
||||
out.write(buffer, offset, length); |
||||
} catch (IOException e) { |
||||
hasErrors = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
try { |
||||
out.close(); |
||||
} catch (IOException e) { |
||||
hasErrors = true; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void flush() { |
||||
try { |
||||
out.flush(); |
||||
} catch (IOException e) { |
||||
hasErrors = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
private final class Entry { |
||||
private final String key; |
||||
|
||||
/** |
||||
* Lengths of this entry's files. |
||||
*/ |
||||
private final long[] lengths; |
||||
|
||||
/** |
||||
* True if this entry has ever been published |
||||
*/ |
||||
private boolean readable; |
||||
|
||||
/** |
||||
* The ongoing edit or null if this entry is not being edited. |
||||
*/ |
||||
private Editor currentEditor; |
||||
|
||||
/** |
||||
* The sequence number of the most recently committed edit to this entry. |
||||
*/ |
||||
private long sequenceNumber; |
||||
|
||||
private Entry(String key) { |
||||
this.key = key; |
||||
this.lengths = new long[valueCount]; |
||||
} |
||||
|
||||
public String getLengths() throws IOException { |
||||
StringBuilder result = new StringBuilder(); |
||||
for (long size : lengths) { |
||||
result.append(' ').append(size); |
||||
} |
||||
return result.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Set lengths using decimal numbers like "10123". |
||||
*/ |
||||
private void setLengths(String[] strings) throws IOException { |
||||
if (strings.length != valueCount) { |
||||
throw invalidLengths(strings); |
||||
} |
||||
|
||||
try { |
||||
for (int i = 0; i < strings.length; i++) { |
||||
lengths[i] = Long.parseLong(strings[i]); |
||||
} |
||||
} catch (NumberFormatException e) { |
||||
throw invalidLengths(strings); |
||||
} |
||||
} |
||||
|
||||
private IOException invalidLengths(String[] strings) throws IOException { |
||||
throw new IOException("unexpected journal line: " + Arrays.toString(strings)); |
||||
} |
||||
|
||||
public File getCleanFile(int i) { |
||||
return new File(directory, key + "." + i); |
||||
} |
||||
|
||||
public File getDirtyFile(int i) { |
||||
return new File(directory, key + "." + i + ".tmp"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@ |
||||
package com.arialyy.frame.cache; |
||||
|
||||
import android.os.Environment; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
*/ |
||||
|
||||
public class PathConstaant { |
||||
private static final String WP_DIR = "windPath"; |
||||
|
||||
/** |
||||
* 获取APK升级路径 |
||||
*/ |
||||
public static String getWpPath() { |
||||
return Environment.getExternalStorageDirectory().getPath() |
||||
+ "/" |
||||
+ WP_DIR |
||||
+ "/update/windPath.apk"; |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
package com.arialyy.frame.config; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
*/ |
||||
|
||||
public interface CommonConstant { |
||||
boolean DEBUG = true; |
||||
} |
@ -0,0 +1,9 @@ |
||||
package com.arialyy.frame.config; |
||||
|
||||
/** |
||||
* Created by AriaL on 2017/11/26. |
||||
*/ |
||||
|
||||
public interface NetConstant { |
||||
String BASE_URL = "http://wwww.baidu.com/"; |
||||
} |
@ -0,0 +1,83 @@ |
||||
package com.arialyy.frame.util; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.media.MediaMetadataRetriever; |
||||
import android.media.MediaPlayer; |
||||
import android.net.Uri; |
||||
import java.io.IOException; |
||||
import java.util.Formatter; |
||||
import java.util.Locale; |
||||
|
||||
/** |
||||
* Created by Aria.Lao on 2018/1/4. |
||||
* 多媒体工具 |
||||
*/ |
||||
public class MediaUtil { |
||||
private MediaUtil() { |
||||
throw new AssertionError(); |
||||
} |
||||
|
||||
/** |
||||
* 获取音频、视频播放长度 |
||||
*/ |
||||
public static long getDuration(String path) { |
||||
MediaPlayer mediaPlayer = new MediaPlayer(); |
||||
try { |
||||
mediaPlayer.setDataSource(path); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
return -1; |
||||
} |
||||
int duration = mediaPlayer.getDuration(); |
||||
mediaPlayer.release(); |
||||
return duration; |
||||
} |
||||
|
||||
/** |
||||
* 格式化视频时间 |
||||
*/ |
||||
public static String convertViewTime(long timeMs) { |
||||
int totalSeconds = (int) (timeMs / 1000); |
||||
|
||||
int seconds = totalSeconds % 60; |
||||
int minutes = (totalSeconds / 60) % 60; |
||||
int hours = totalSeconds / 3600; |
||||
StringBuilder sFormatBuilder = new StringBuilder(); |
||||
Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault()); |
||||
sFormatBuilder.setLength(0); |
||||
if (hours > 0) { |
||||
return sFormatter.format("%02d:%02d:%02d", hours, minutes, seconds).toString(); |
||||
} else { |
||||
return sFormatter.format("%02d:%02d", minutes, seconds).toString(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取音频封面 |
||||
*/ |
||||
public static Bitmap getArtwork(Context context, String url) { |
||||
Uri selectedAudio = Uri.parse(url); |
||||
MediaMetadataRetriever myRetriever = new MediaMetadataRetriever(); |
||||
myRetriever.setDataSource(context, selectedAudio); // the URI of audio file
|
||||
byte[] artwork; |
||||
artwork = myRetriever.getEmbeddedPicture(); |
||||
if (artwork != null) { |
||||
return BitmapFactory.decodeByteArray(artwork, 0, artwork.length); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static byte[] getArtworkAsByte(Context context, String url) { |
||||
Uri selectedAudio = Uri.parse(url); |
||||
MediaMetadataRetriever myRetriever = new MediaMetadataRetriever(); |
||||
myRetriever.setDataSource(context, selectedAudio); // the URI of audio file
|
||||
byte[] artwork; |
||||
artwork = myRetriever.getEmbeddedPicture(); |
||||
if (artwork != null) { |
||||
return artwork; |
||||
} |
||||
return null; |
||||
} |
||||
} |
@ -0,0 +1,90 @@ |
||||
|
||||
import android.os.Bundle; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import com.arialyy.annotations.Download; |
||||
import com.arialyy.aria.core.Aria; |
||||
import com.arialyy.aria.core.download.DownloadTask; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import com.arialyy.simple.R; |
||||
import com.arialyy.simple.base.BaseActivity; |
||||
import com.arialyy.simple.databinding.ActivityTestBinding; |
||||
import java.io.File; |
||||
|
||||
/** |
||||
* Created by lyy on 2019/5/28. |
||||
* Ftp 下载 |
||||
* <a href="https://aria.laoyuyu.me/aria_doc/">文档</> |
||||
*/ |
||||
public class FtpDownload extends BaseActivity<ActivityTestBinding> { |
||||
String TAG = "TestFTPActivity"; |
||||
private final String URL = "ftp://192.168.1.3:21/download//AriaPrj.rar"; |
||||
private final String FILE_PATH = "/mnt/sdcard/AriaPrj.rar"; |
||||
|
||||
@Override protected int setLayoutId() { |
||||
return R.layout.activity_test; |
||||
} |
||||
|
||||
@Override protected void init(Bundle savedInstanceState) { |
||||
super.init(savedInstanceState); |
||||
mBar.setVisibility(View.GONE); |
||||
Aria.download(this).register(); |
||||
} |
||||
|
||||
@Download.onWait void onWait(DownloadTask task) { |
||||
Log.d(TAG, "wait ==> " + task.getEntity().getFileName()); |
||||
} |
||||
|
||||
@Download.onPre protected void onPre(DownloadTask task) { |
||||
Log.d(TAG, "onPre"); |
||||
} |
||||
|
||||
@Download.onTaskStart void taskStart(DownloadTask task) { |
||||
Log.d(TAG, "onStart"); |
||||
} |
||||
|
||||
@Download.onTaskRunning protected void running(DownloadTask task) { |
||||
Log.d(TAG, "running,speed=" + task.getConvertSpeed()); |
||||
} |
||||
|
||||
@Download.onTaskResume void taskResume(DownloadTask task) { |
||||
Log.d(TAG, "resume"); |
||||
} |
||||
|
||||
@Download.onTaskStop void taskStop(DownloadTask task) { |
||||
Log.d(TAG, "stop"); |
||||
} |
||||
|
||||
@Download.onTaskCancel void taskCancel(DownloadTask task) { |
||||
Log.d(TAG, "cancel"); |
||||
} |
||||
|
||||
@Download.onTaskFail void taskFail(DownloadTask task) { |
||||
Log.d(TAG, "fail"); |
||||
} |
||||
|
||||
@Download.onTaskComplete void taskComplete(DownloadTask task) { |
||||
Log.d(TAG, "complete, md5 => " + CommonUtil.getFileMD5(new File(task.getKey()))); |
||||
} |
||||
|
||||
public void onClick(View view) { |
||||
switch (view.getId()) { |
||||
case R.id.start: |
||||
Aria.download(this) |
||||
.loadFtp(URL) |
||||
.setFilePath(FILE_PATH) |
||||
.login("lao", "123456") |
||||
//.asFtps() // ftps 配置
|
||||
//.setStorePath("/mnt/sdcard/Download/server.crt") //设置证书路径
|
||||
// .setAlias("www.laoyuyu.me") // 设置证书别名
|
||||
.start(); |
||||
break; |
||||
case R.id.stop: |
||||
Aria.download(this).loadFtp(FILE_PATH).stop(); |
||||
break; |
||||
case R.id.cancel: |
||||
Aria.download(this).loadFtp(FILE_PATH).cancel(); |
||||
break; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,127 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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. |
||||
*/ |
||||
import android.app.Activity; |
||||
import android.os.Bundle; |
||||
import android.os.Environment; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import com.arialyy.annotations.Upload; |
||||
import com.arialyy.aria.core.Aria; |
||||
import com.arialyy.aria.core.common.ftp.FtpInterceptHandler; |
||||
import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.aria.core.upload.UploadTask; |
||||
import com.arialyy.frame.util.FileUtil; |
||||
import com.arialyy.frame.util.show.T; |
||||
import com.arialyy.simple.R; |
||||
import java.io.File; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Created by lyy on 2019/5/28. Ftp 文件上传demo |
||||
* <a href="https://aria.laoyuyu.me/aria_doc/">文档</> |
||||
*/ |
||||
public class FtpUpload extends Activity { |
||||
private static final String TAG = "FtpUpload"; |
||||
private String mFilePath = Environment.getExternalStorageDirectory().getPath() + "/AriaPrj.rar"; |
||||
private String mUrl = "ftp://172.168.1.2:2121/aa/你好"; |
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
Aria.upload(this).register(); |
||||
// 读取历史记录信息
|
||||
UploadEntity entity = Aria.upload(this).getUploadEntity(mFilePath); |
||||
if (entity != null) { |
||||
// 设置界面的进度、文件大小等信息
|
||||
} |
||||
} |
||||
|
||||
public void onClick(View view) { |
||||
switch (view.getId()) { |
||||
case R.id.start: |
||||
if (Aria.upload(this).load(mFilePath).isRunning()) { |
||||
Aria.upload(this).loadFtp(mFilePath).stop(); // 停止任务
|
||||
} else { |
||||
Aria.upload(this) |
||||
.loadFtp(mFilePath) // 需要上传的文件
|
||||
.setUploadUrl(mUrl) // 服务器地址
|
||||
// 如果ftp服务器端有同名文件,可通过拦截器处理是覆盖服务器端文件,还是修改文件名
|
||||
.setUploadInterceptor( |
||||
new IFtpUploadInterceptor() { |
||||
|
||||
@Override |
||||
public FtpInterceptHandler onIntercept(UploadEntity entity, |
||||
List<String> fileList) { |
||||
FtpInterceptHandler.Builder builder = new FtpInterceptHandler.Builder(); |
||||
//builder.coverServerFile();
|
||||
builder.resetFileName("test.zip"); |
||||
return builder.build(); |
||||
} |
||||
}) |
||||
.login("N0rI", "0qcK") |
||||
.start(); |
||||
} |
||||
break; |
||||
case R.id.cancel: |
||||
Aria.upload(this).loadFtp(mFilePath).cancel(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Upload.onWait void onWait(UploadTask task) { |
||||
Log.d(TAG, task.getTaskName() + "_wait"); |
||||
} |
||||
|
||||
@Upload.onPre public void onPre(UploadTask task) { |
||||
setFileSize(task.getConvertFileSize()); |
||||
} |
||||
|
||||
@Upload.onTaskStart public void taskStart(UploadTask task) { |
||||
Log.d(TAG, "开始上传,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath()))); |
||||
} |
||||
|
||||
@Upload.onTaskResume public void taskResume(UploadTask task) { |
||||
Log.d(TAG, "恢复上传"); |
||||
} |
||||
|
||||
@Upload.onTaskStop public void taskStop(UploadTask task) { |
||||
setSpeed(""); |
||||
Log.d(TAG, "停止上传"); |
||||
} |
||||
|
||||
@Upload.onTaskCancel public void taskCancel(UploadTask task) { |
||||
setSpeed(""); |
||||
setFileSize(""); |
||||
setProgress(0); |
||||
Log.d(TAG, "删除任务"); |
||||
} |
||||
|
||||
@Upload.onTaskFail public void taskFail(UploadTask task) { |
||||
Log.d(TAG, "上传失败"); |
||||
} |
||||
|
||||
@Upload.onTaskRunning public void taskRunning(UploadTask task) { |
||||
Log.d(TAG, "PP = " + task.getPercent()); |
||||
setProgress(task.getPercent()); |
||||
setSpeed(task.getConvertSpeed()); |
||||
} |
||||
|
||||
@Upload.onTaskComplete public void taskComplete(UploadTask task) { |
||||
setProgress(100); |
||||
setSpeed(""); |
||||
T.showShort(this, "文件:" + task.getEntity().getFileName() + ",上传完成"); |
||||
} |
||||
} |
@ -0,0 +1,119 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.core.upload; |
||||
|
||||
import android.app.Activity; |
||||
import android.os.Bundle; |
||||
import android.os.Environment; |
||||
import android.util.Log; |
||||
import android.view.View; |
||||
import com.arialyy.annotations.Download; |
||||
import com.arialyy.aria.core.Aria; |
||||
import com.arialyy.aria.core.common.ftp.FtpInterceptHandler; |
||||
import com.arialyy.aria.core.common.ftp.IFtpUploadInterceptor; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.aria.core.upload.UploadTask; |
||||
import com.arialyy.frame.util.FileUtil; |
||||
import com.arialyy.frame.util.show.T; |
||||
import com.arialyy.simple.R; |
||||
import java.io.File; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Created by lyy on 2017/7/28. HTTP 文件下载demo |
||||
* <a href="https://aria.laoyuyu.me/aria_doc/">文档</> |
||||
*/ |
||||
public class FtpUpload extends Activity { |
||||
private static final String TAG = "FtpUpload"; |
||||
private String mFilePath = Environment.getExternalStorageDirectory().getPath() + "/test.apk"; |
||||
private String mUrl = "http://hzdown.muzhiwan.com/2017/05/08/nl.noio.kingdom_59104935e56f0.apk"; |
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
Aria.download(this).register(); |
||||
// 读取历史记录信息
|
||||
DownloadEntity entity = Aria.download(this).getDownloadEntity(mFilePath); |
||||
if (entity != null) { |
||||
// 设置界面的进度、文件大小等信息
|
||||
} |
||||
} |
||||
|
||||
public void onClick(View view) { |
||||
switch (view.getId()) { |
||||
case R.id.start: |
||||
if (Aria.download(this).load(mUrl).isRunning()) { |
||||
Aria.download(this).load(mUrl).stop(); // 停止任务
|
||||
} else { |
||||
Aria.download(this) |
||||
.load(mUrl) // 下载url
|
||||
.setFilePath(mFilePath) // 文件保存路径
|
||||
//.addHeader(key, value) // 添加头
|
||||
//.asPost() //或 asGet()
|
||||
//.setParam() // 设置参数
|
||||
.start(); |
||||
} |
||||
break; |
||||
case R.id.cancel: |
||||
Aria.download(this).load(mUrl).cancel(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Download.onWait void onWait(DownloadTask task) { |
||||
Log.d(TAG, task.getTaskName() + "_wait"); |
||||
} |
||||
|
||||
@Download.onPre public void onPre(DownloadTask task) { |
||||
setFileSize(task.getConvertFileSize()); |
||||
} |
||||
|
||||
@Download.onTaskStart public void taskStart(DownloadTask task) { |
||||
Log.d(TAG, "开始下载,md5:" + FileUtil.getFileMD5(new File(task.getEntity().getFilePath()))); |
||||
} |
||||
|
||||
@Download.onTaskResume public void taskResume(DownloadTask task) { |
||||
Log.d(TAG, "恢复下载"); |
||||
} |
||||
|
||||
@Download.onTaskStop public void taskStop(DownloadTask task) { |
||||
setSpeed(""); |
||||
Log.d(TAG, "停止下载"); |
||||
} |
||||
|
||||
@Download.onTaskCancel public void taskCancel(DownloadTask task) { |
||||
setSpeed(""); |
||||
setFileSize(""); |
||||
setProgress(0); |
||||
Log.d(TAG, "删除任务"); |
||||
} |
||||
|
||||
@Download.onTaskFail public void taskFail(DownloadTask task) { |
||||
Log.d(TAG, "下载失败"); |
||||
} |
||||
|
||||
@Download.onTaskRunning public void taskRunning(DownloadTask task) { |
||||
Log.d(TAG, "PP = " + task.getPercent()); |
||||
setProgress(task.getPercent()); |
||||
setSpeed(task.getConvertSpeed()); |
||||
} |
||||
|
||||
@Download.onTaskComplete public void taskComplete(DownloadTask task) { |
||||
setProgress(100); |
||||
setSpeed(""); |
||||
T.showShort(this, "文件:" + task.getEntity().getFileName() + ",下载完成"); |
||||
} |
||||
} |
@ -0,0 +1,170 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) |
||||
* |
||||
* 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. |
||||
*/ |
||||
import android.os.Bundle |
||||
import android.os.Environment |
||||
import android.util.Log |
||||
import android.view.View |
||||
import android.widget.Button |
||||
import android.widget.Toast |
||||
import com.arialyy.annotations.Download |
||||
import com.arialyy.aria.core.Aria |
||||
import com.arialyy.aria.core.download.DownloadTarget |
||||
import com.arialyy.aria.core.download.DownloadTask |
||||
import com.arialyy.aria.core.inf.IEntity |
||||
import com.arialyy.simple.R |
||||
import com.arialyy.simple.base.BaseActivity |
||||
import com.arialyy.simple.databinding.ActivitySingleBinding |
||||
|
||||
/** |
||||
* Created by lyy on 2017/10/23. |
||||
*/ |
||||
class KotlinDownloadActivity : BaseActivity<ActivitySingleBinding>() { |
||||
|
||||
private val DOWNLOAD_URL = |
||||
"http://static.gaoshouyou.com/d/22/94/822260b849944492caadd2983f9bb624.apk" |
||||
|
||||
private lateinit var mStart: Button |
||||
private lateinit var mStop: Button |
||||
private lateinit var mCancel: Button |
||||
private lateinit var target: DownloadTarget |
||||
|
||||
override fun setLayoutId(): Int { |
||||
return R.layout.activity_single |
||||
} |
||||
|
||||
override fun init(savedInstanceState: Bundle?) { |
||||
title = "kotlin测试" |
||||
Aria.get(this) |
||||
.downloadConfig.maxTaskNum = 2 |
||||
Aria.download(this) |
||||
.register() |
||||
mStart = findViewById(R.id.start) |
||||
mStop = findViewById(R.id.stop) |
||||
mCancel = findViewById(R.id.cancel) |
||||
mStop.visibility = View.GONE |
||||
|
||||
target = Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
binding.progress = target.percent |
||||
if (target.taskState == IEntity.STATE_STOP) { |
||||
mStart.text = "恢复" |
||||
} else if (target.isRunning) { |
||||
mStart.text = "停止" |
||||
} |
||||
binding.fileSize = target.convertFileSize |
||||
} |
||||
|
||||
/** |
||||
* 注解方法不能添加internal修饰符,否则会出现e: [kapt] An exception occurred: java.lang.IllegalArgumentException: index 1 for '$a' not in range (received 0 arguments)错误 |
||||
*/ |
||||
@Download.onTaskRunning |
||||
fun running(task: DownloadTask) { |
||||
Log.d(TAG, task.percent.toString()) |
||||
val len = task.fileSize |
||||
if (len == 0L) { |
||||
binding.progress = 0 |
||||
} else { |
||||
binding.progress = task.percent |
||||
} |
||||
binding.speed = task.convertSpeed |
||||
} |
||||
|
||||
@Download.onWait |
||||
fun onWait(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
Log.d(TAG, "wait ==> " + task.downloadEntity.fileName) |
||||
} |
||||
} |
||||
|
||||
@Download.onPre |
||||
fun onPre(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "停止" |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskStart |
||||
fun taskStart(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
binding.fileSize = task.convertFileSize |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskComplete |
||||
fun complete(task: DownloadTask) { |
||||
Log.d(TAG, "完成") |
||||
} |
||||
|
||||
@Download.onTaskResume |
||||
fun taskResume(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "停止" |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskStop |
||||
fun taskStop(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "恢复" |
||||
binding.speed = "" |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskCancel |
||||
fun taskCancel(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
binding.progress = 0 |
||||
Toast.makeText(this@KotlinDownloadActivity, "取消下载", Toast.LENGTH_SHORT) |
||||
.show() |
||||
mStart.text = "开始" |
||||
binding.speed = "" |
||||
Log.d(TAG, "cancel") |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskFail |
||||
fun taskFail(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
Toast.makeText(this@KotlinDownloadActivity, "下载失败", Toast.LENGTH_SHORT) |
||||
.show() |
||||
mStart.text = "开始" |
||||
} |
||||
} |
||||
|
||||
fun onClick(view: View) { |
||||
when (view.id) { |
||||
R.id.start -> { |
||||
if (target.isRunning) { |
||||
Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
.stop() |
||||
} else { |
||||
startD() |
||||
} |
||||
} |
||||
R.id.stop -> Aria.download(this).load(DOWNLOAD_URL).stop() |
||||
R.id.cancel -> Aria.download(this).load(DOWNLOAD_URL).cancel() |
||||
} |
||||
} |
||||
|
||||
private fun startD() { |
||||
Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
.addHeader("groupHash", "value") |
||||
.setFilePath(Environment.getExternalStorageDirectory().path + "/kotlin.apk") |
||||
.start() |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.common; |
||||
|
||||
import android.arch.lifecycle.LiveData; |
||||
import android.arch.lifecycle.MutableLiveData; |
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.frame.base.BaseViewModule; |
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class DialogModule extends BaseViewModule { |
||||
|
||||
private MutableLiveData<List<File>> mDirs = new MutableLiveData<>(); |
||||
|
||||
/** |
||||
* 获取指定目录下的文件夹 |
||||
* |
||||
* @param path 指定路径 |
||||
*/ |
||||
LiveData<List<File>> getDirs(String path) { |
||||
if (TextUtils.isEmpty(path)) { |
||||
ALog.e(TAG, "路径为空"); |
||||
return mDirs; |
||||
} |
||||
if (!path.startsWith("/")) { |
||||
ALog.e(TAG, "路径错误"); |
||||
return mDirs; |
||||
} |
||||
File file = new File(path); |
||||
File[] files = file.listFiles(); |
||||
List<File> data = new ArrayList<>(); |
||||
for (File f : files) { |
||||
if (f.isDirectory()) { |
||||
data.add(f); |
||||
} |
||||
} |
||||
mDirs.postValue(data); |
||||
|
||||
return mDirs; |
||||
} |
||||
} |
@ -0,0 +1,177 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.common; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.app.Dialog; |
||||
import android.arch.lifecycle.Observer; |
||||
import android.arch.lifecycle.ViewModelProviders; |
||||
import android.content.Context; |
||||
import android.content.DialogInterface; |
||||
import android.os.Bundle; |
||||
import android.os.Environment; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v7.widget.DividerItemDecoration; |
||||
import android.support.v7.widget.LinearLayoutManager; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.util.DisplayMetrics; |
||||
import android.view.KeyEvent; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.view.Window; |
||||
import android.widget.TextView; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.simple.R; |
||||
import com.arialyy.simple.base.BaseDialog; |
||||
import com.arialyy.simple.base.adapter.AbsHolder; |
||||
import com.arialyy.simple.base.adapter.AbsRVAdapter; |
||||
import com.arialyy.simple.base.adapter.RvItemClickSupport; |
||||
import com.arialyy.simple.databinding.DialogChooseDirBinding; |
||||
import com.arialyy.simple.databinding.DialogMsgBinding; |
||||
import java.io.File; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
/** |
||||
* Created by AriaL on 2019/5/28. |
||||
*/ |
||||
@SuppressLint("ValidFragment") public class DirChooseDialog |
||||
extends BaseDialog<DialogChooseDirBinding> { |
||||
public static final int DIR_CHOOSE_DIALOG_RESULT = 0xB2; |
||||
private String mCurrentPath = Environment.getExternalStorageDirectory().getPath(); |
||||
private List<File> mData = new ArrayList<>(); |
||||
private DialogModule mModule; |
||||
|
||||
public DirChooseDialog(Object obj) { |
||||
super(obj); |
||||
} |
||||
|
||||
@Override protected void init(Bundle savedInstanceState) { |
||||
super.init(savedInstanceState); |
||||
getBinding().list.setLayoutManager(new LinearLayoutManager(getContext())); |
||||
getBinding().list.addItemDecoration( |
||||
new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); |
||||
final Adapter adapter = new Adapter(getContext(), mData); |
||||
getBinding().list.setAdapter(adapter); |
||||
getBinding().setCurrentPath(mCurrentPath); |
||||
|
||||
mModule = ViewModelProviders.of(this).get(DialogModule.class); |
||||
mModule.getDirs(mCurrentPath).observe(this, new Observer<List<File>>() { |
||||
@Override public void onChanged(@Nullable List<File> files) { |
||||
mData.clear(); |
||||
if (files != null && !files.isEmpty()) { |
||||
mData.addAll(files); |
||||
} |
||||
adapter.notifyDataSetChanged(); |
||||
if (mCurrentPath.equals(Environment.getExternalStorageDirectory().getPath())) { |
||||
getBinding().up.setVisibility(View.GONE); |
||||
} |
||||
getBinding().setCurrentPath(mCurrentPath); |
||||
getBinding().currentPath.setSelected(true); |
||||
} |
||||
}); |
||||
|
||||
getBinding().up.setOnClickListener(new View.OnClickListener() { |
||||
@Override public void onClick(View v) { |
||||
up(); |
||||
} |
||||
}); |
||||
|
||||
getBinding().enter.setOnClickListener(new View.OnClickListener() { |
||||
@Override public void onClick(View v) { |
||||
getSimplerModule().onDialog(DIR_CHOOSE_DIALOG_RESULT, mCurrentPath); |
||||
dismiss(); |
||||
} |
||||
}); |
||||
|
||||
RvItemClickSupport.addTo(getBinding().list).setOnItemClickListener( |
||||
new RvItemClickSupport.OnItemClickListener() { |
||||
@Override public void onItemClicked(RecyclerView recyclerView, int position, View v) { |
||||
getBinding().up.setVisibility(View.VISIBLE); |
||||
mCurrentPath = mCurrentPath.concat("/").concat(mData.get(position).getName()); |
||||
mModule.getDirs(mCurrentPath); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void up() { |
||||
int endIndex = mCurrentPath.lastIndexOf("/"); |
||||
mCurrentPath = mCurrentPath.substring(0, endIndex); |
||||
mModule.getDirs(mCurrentPath); |
||||
} |
||||
|
||||
@Override public void onStart() { |
||||
super.onStart(); |
||||
Dialog dialog = getDialog(); |
||||
if (dialog != null) { |
||||
DisplayMetrics dm = new DisplayMetrics(); |
||||
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); |
||||
dialog.getWindow() |
||||
.setLayout((dm.widthPixels), ViewGroup.LayoutParams.WRAP_CONTENT); |
||||
// 拦截返回键
|
||||
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() { |
||||
@Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { |
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { |
||||
if (mCurrentPath.equals(Environment.getExternalStorageDirectory().getPath())) { |
||||
dismiss(); |
||||
} else { |
||||
up(); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
@Override protected int setLayoutId() { |
||||
return R.layout.dialog_choose_dir; |
||||
} |
||||
|
||||
/** |
||||
* 适配器 |
||||
*/ |
||||
private class Adapter extends AbsRVAdapter<File, Adapter.Holder> { |
||||
|
||||
Adapter(Context context, List<File> data) { |
||||
super(context, data); |
||||
} |
||||
|
||||
@Override protected Holder getViewHolder(View convertView, int viewType) { |
||||
return new Holder(convertView); |
||||
} |
||||
|
||||
@Override protected int setLayoutId(int type) { |
||||
return R.layout.item_choose_dir; |
||||
} |
||||
|
||||
@Override protected void bindData(Holder holder, int position, File item) { |
||||
holder.text.setSelected(true); |
||||
holder.text.setText(item.getName()); |
||||
} |
||||
|
||||
private class Holder extends AbsHolder { |
||||
private TextView text; |
||||
|
||||
Holder(View itemView) { |
||||
super(itemView); |
||||
text = findViewById(R.id.text); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.common; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.os.Bundle; |
||||
import android.text.TextUtils; |
||||
import android.view.View; |
||||
import android.widget.Toast; |
||||
import com.arialyy.simple.R; |
||||
import com.arialyy.simple.base.BaseDialog; |
||||
import com.arialyy.simple.databinding.DialogModifyPathBinding; |
||||
import com.arialyy.simple.databinding.DialogMsgBinding; |
||||
import java.io.File; |
||||
|
||||
/** |
||||
* Created by AriaL on 2019/5/28. |
||||
* 修改文件路径 |
||||
*/ |
||||
@SuppressLint("ValidFragment") public class ModifyPathDialog |
||||
extends BaseDialog<DialogModifyPathBinding> { |
||||
public static final int MODIFY_PATH_RESULT = 0xB3; |
||||
|
||||
private String mTitle, mFilePath, mDir; |
||||
|
||||
public ModifyPathDialog(Object obj, String title, String filePath) { |
||||
super(obj); |
||||
mTitle = title; |
||||
mFilePath = filePath; |
||||
} |
||||
|
||||
@Override protected void init(Bundle savedInstanceState) { |
||||
super.init(savedInstanceState); |
||||
getBinding().setTitle(mTitle); |
||||
getBinding().setViewModel(this); |
||||
final File temp = new File(mFilePath); |
||||
mDir = temp.getParent(); |
||||
getBinding().setDir(mDir); |
||||
getBinding().setName(temp.getName()); |
||||
getBinding().edit.post(new Runnable() { |
||||
@Override public void run() { |
||||
getBinding().edit.setSelection(temp.getName().length()); |
||||
} |
||||
}); |
||||
getBinding().enter.setOnClickListener(new View.OnClickListener() { |
||||
@Override public void onClick(View v) { |
||||
if (TextUtils.isEmpty(getBinding().getName())) { |
||||
Toast.makeText(getContext(), getString(R.string.error_file_name_null), Toast.LENGTH_SHORT) |
||||
.show(); |
||||
return; |
||||
} |
||||
mFilePath = mDir + "/" + getBinding().getName(); |
||||
getSimplerModule().onDialog(MODIFY_PATH_RESULT, mFilePath); |
||||
dismiss(); |
||||
} |
||||
}); |
||||
getBinding().cancel.setOnClickListener(new View.OnClickListener() { |
||||
@Override public void onClick(View v) { |
||||
dismiss(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void chooseDir() { |
||||
DirChooseDialog dirChooseDialog = new DirChooseDialog(this); |
||||
dirChooseDialog.show(getChildFragmentManager(), "DirChooseDialog"); |
||||
} |
||||
|
||||
@Override protected int setLayoutId() { |
||||
return R.layout.dialog_modify_path; |
||||
} |
||||
|
||||
@Override protected void dataCallback(int result, Object data) { |
||||
super.dataCallback(result, data); |
||||
if (result == DirChooseDialog.DIR_CHOOSE_DIALOG_RESULT) { |
||||
mDir = String.valueOf(data); |
||||
getBinding().setDir(mDir); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,68 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.core; |
||||
|
||||
import android.content.pm.ActivityInfo; |
||||
import android.os.Bundle; |
||||
import android.text.TextUtils; |
||||
import android.view.Window; |
||||
import android.view.WindowManager; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.simple.R; |
||||
import com.arialyy.simple.base.BaseActivity; |
||||
import com.arialyy.simple.databinding.ActivityFullScreenCodeBinding; |
||||
import com.pddstudio.highlightjs.models.Language; |
||||
import com.pddstudio.highlightjs.models.Theme; |
||||
import java.io.File; |
||||
|
||||
/** |
||||
* 全是显示代码的actiivty |
||||
*/ |
||||
public class FullScreenCodeActivity extends BaseActivity<ActivityFullScreenCodeBinding> { |
||||
private static final String TAG = "FullScreenCodeActivity"; |
||||
public static final String KEY_FILE_PATH = "KEY_FILE_PATH"; |
||||
|
||||
@Override protected int setLayoutId() { |
||||
return R.layout.activity_full_screen_code; |
||||
} |
||||
|
||||
@Override protected void onCreate(Bundle savedInstanceState) { |
||||
//去除标题栏
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE); |
||||
//去除状态栏
|
||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, |
||||
WindowManager.LayoutParams.FLAG_FULLSCREEN); |
||||
super.onCreate(savedInstanceState); |
||||
} |
||||
|
||||
@Override protected void init(Bundle savedInstanceState) { |
||||
super.init(savedInstanceState); |
||||
String filePath = getIntent().getStringExtra(KEY_FILE_PATH); |
||||
|
||||
if (TextUtils.isEmpty(filePath)) { |
||||
ALog.e(TAG, "代码的文件路径为空"); |
||||
finish(); |
||||
return; |
||||
} |
||||
getBinding().codeView.setZoomSupportEnabled(true); |
||||
getBinding().codeView.setHighlightLanguage(Language.JAVA); |
||||
getBinding().codeView.setTheme(Theme.ANDROID_STUDIO); |
||||
getBinding().codeView.setSource(new File(filePath)); |
||||
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
||||
} |
||||
} |
@ -0,0 +1,95 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.core.download; |
||||
|
||||
import android.arch.lifecycle.LiveData; |
||||
import android.arch.lifecycle.MutableLiveData; |
||||
import android.content.Context; |
||||
import android.os.Environment; |
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.core.Aria; |
||||
import com.arialyy.aria.core.download.DownloadEntity; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.frame.base.BaseViewModule; |
||||
import com.arialyy.simple.util.AppUtil; |
||||
import java.io.File; |
||||
|
||||
public class DownloadModule1 extends BaseViewModule { |
||||
private final String DOWNLOAD_URL_KEY = "DOWNLOAD_URL_KEY"; |
||||
private final String DOWNLOAD_PATH_KEY = "DOWNLOAD_PATH_KEY"; |
||||
|
||||
private final String defUrl = |
||||
"http://hzdown.muzhiwan.com/2017/05/08/nl.noio.kingdom_59104935e56f0.apk"; |
||||
private final String defFilePath = |
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); |
||||
|
||||
private MutableLiveData<DownloadEntity> liveData = new MutableLiveData<>(); |
||||
private DownloadEntity singDownloadInfo; |
||||
|
||||
/** |
||||
* 单任务下载的信息 |
||||
*/ |
||||
LiveData<DownloadEntity> getSingDownloadInfo(Context context) { |
||||
String url = AppUtil.getConfigValue(context, DOWNLOAD_URL_KEY, defUrl); |
||||
String filePath = AppUtil.getConfigValue(context, DOWNLOAD_PATH_KEY, defFilePath); |
||||
|
||||
singDownloadInfo = Aria.download(context).getDownloadEntity(url); |
||||
if (singDownloadInfo == null) { |
||||
singDownloadInfo = new DownloadEntity(); |
||||
singDownloadInfo.setUrl(url); |
||||
File temp = new File(defFilePath); |
||||
singDownloadInfo.setDownloadPath(filePath); |
||||
singDownloadInfo.setFileName(temp.getName()); |
||||
} else { |
||||
AppUtil.setConfigValue(context, DOWNLOAD_PATH_KEY, singDownloadInfo.getDownloadPath()); |
||||
AppUtil.setConfigValue(context, DOWNLOAD_URL_KEY, singDownloadInfo.getUrl()); |
||||
} |
||||
liveData.postValue(singDownloadInfo); |
||||
|
||||
return liveData; |
||||
} |
||||
|
||||
/** |
||||
* 更新文件保存路径 |
||||
* |
||||
* @param filePath 文件保存路径 |
||||
*/ |
||||
void updateFilePath(Context context, String filePath) { |
||||
if (TextUtils.isEmpty(filePath)) { |
||||
ALog.e(TAG, "文件保存路径为空"); |
||||
return; |
||||
} |
||||
File temp = new File(filePath); |
||||
AppUtil.setConfigValue(context, DOWNLOAD_PATH_KEY, filePath); |
||||
singDownloadInfo.setFileName(temp.getName()); |
||||
singDownloadInfo.setDownloadPath(filePath); |
||||
liveData.postValue(singDownloadInfo); |
||||
} |
||||
|
||||
/** |
||||
* 更新url |
||||
*/ |
||||
void uploadUrl(Context context, String url) { |
||||
if (TextUtils.isEmpty(url)) { |
||||
ALog.e(TAG, "下载地址为空"); |
||||
return; |
||||
} |
||||
AppUtil.setConfigValue(context, DOWNLOAD_URL_KEY, url); |
||||
singDownloadInfo.setUrl(url); |
||||
liveData.postValue(singDownloadInfo); |
||||
} |
||||
} |
@ -1,166 +1,317 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) |
||||
* |
||||
* 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 com.arialyy.simple.core.download |
||||
|
||||
import android.arch.lifecycle.Observer |
||||
import android.arch.lifecycle.ViewModelProviders |
||||
import android.content.BroadcastReceiver |
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.os.Environment |
||||
import android.util.Log |
||||
import android.view.Menu |
||||
import android.view.MenuItem |
||||
import android.view.View |
||||
import android.widget.Button |
||||
import android.widget.Toast |
||||
|
||||
import com.arialyy.annotations.Download |
||||
import com.arialyy.aria.core.Aria |
||||
import com.arialyy.aria.core.download.DownloadEntity |
||||
import com.arialyy.aria.core.download.DownloadTarget |
||||
import com.arialyy.aria.core.download.DownloadTask |
||||
import com.arialyy.aria.core.inf.IEntity |
||||
import com.arialyy.aria.core.inf.IHttpFileLenAdapter |
||||
import com.arialyy.aria.core.scheduler.ISchedulers |
||||
import com.arialyy.aria.util.ALog |
||||
import com.arialyy.aria.util.CommonUtil |
||||
import com.arialyy.frame.util.show.T |
||||
import com.arialyy.simple.R |
||||
import com.arialyy.simple.base.BaseActivity |
||||
import com.arialyy.simple.databinding.ActivitySingleBinding |
||||
import com.arialyy.simple.common.ModifyPathDialog |
||||
import com.arialyy.simple.common.ModifyUrlDialog |
||||
import com.arialyy.simple.databinding.ActivitySingleKotlinBinding |
||||
import com.arialyy.simple.util.AppUtil |
||||
import com.pddstudio.highlightjs.models.Language |
||||
|
||||
/** |
||||
* Created by lyy on 2017/10/23. |
||||
*/ |
||||
class KotlinDownloadActivity : BaseActivity<ActivitySingleBinding>() { |
||||
import java.io.IOException |
||||
|
||||
private val DOWNLOAD_URL = |
||||
"http://static.gaoshouyou.com/d/22/94/822260b849944492caadd2983f9bb624.apk" |
||||
class KotlinDownloadActivity : BaseActivity<ActivitySingleKotlinBinding>() { |
||||
|
||||
private lateinit var mStart: Button |
||||
private lateinit var mStop: Button |
||||
private lateinit var mCancel: Button |
||||
private lateinit var target: DownloadTarget |
||||
private var mUrl: String? = null |
||||
private var mFilePath: String? = null |
||||
private var mModule: DownloadModule1? = null |
||||
private var mTarget: DownloadTarget? = null |
||||
|
||||
override fun setLayoutId(): Int { |
||||
return R.layout.activity_single |
||||
internal var receiver: BroadcastReceiver = object : BroadcastReceiver() { |
||||
override fun onReceive( |
||||
context: Context, |
||||
intent: Intent |
||||
) { |
||||
if (intent.action == ISchedulers.ARIA_TASK_INFO_ACTION) { |
||||
ALog.d(TAG, "state = " + intent.getIntExtra(ISchedulers.TASK_STATE, -1)) |
||||
ALog.d(TAG, "type = " + intent.getIntExtra(ISchedulers.TASK_TYPE, -1)) |
||||
ALog.d(TAG, "speed = " + intent.getLongExtra(ISchedulers.TASK_SPEED, -1)) |
||||
ALog.d(TAG, "percent = " + intent.getIntExtra(ISchedulers.TASK_PERCENT, -1)) |
||||
ALog.d( |
||||
TAG, "entity = " + intent.getParcelableExtra<DownloadEntity>( |
||||
ISchedulers.TASK_ENTITY |
||||
).toString() |
||||
) |
||||
} |
||||
} |
||||
} |
||||
|
||||
override fun onResume() { |
||||
super.onResume() |
||||
//registerReceiver(receiver, new IntentFilter(ISchedulers.ARIA_TASK_INFO_ACTION)); |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
super.onDestroy() |
||||
//unregisterReceiver(receiver); |
||||
Aria.download(this) |
||||
.unRegister() |
||||
} |
||||
|
||||
override fun init(savedInstanceState: Bundle?) { |
||||
title = "kotlin测试" |
||||
Aria.get(this) |
||||
.downloadConfig.maxTaskNum = 2 |
||||
super.init(savedInstanceState) |
||||
title = "单任务下载" |
||||
Aria.download(this) |
||||
.register() |
||||
mStart = findViewById(R.id.start) |
||||
mStop = findViewById(R.id.stop) |
||||
mCancel = findViewById(R.id.cancel) |
||||
mStop.visibility = View.GONE |
||||
mModule = ViewModelProviders.of(this) |
||||
.get(DownloadModule1::class.java) |
||||
mModule!!.getSingDownloadInfo(this) |
||||
.observe(this, Observer { entity -> |
||||
if (entity == null) { |
||||
return@Observer |
||||
} |
||||
mTarget = Aria.download(this) |
||||
.load(entity.url) |
||||
if (mTarget!!.taskState == IEntity.STATE_STOP) { |
||||
binding.stateStr = getString(R.string.resume) |
||||
} else if (mTarget!!.isRunning) { |
||||
binding.stateStr = getString(R.string.stop) |
||||
} |
||||
|
||||
target = Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
binding.progress = target.percent |
||||
if (target.taskState == IEntity.STATE_STOP) { |
||||
mStart.text = "恢复" |
||||
} else if (target.isRunning) { |
||||
mStart.text = "停止" |
||||
if (entity.fileSize != 0L) { |
||||
binding.fileSize = CommonUtil.formatFileSize(entity.fileSize.toDouble()) |
||||
binding.progress = if (entity.isComplete) |
||||
100 |
||||
else |
||||
(entity.currentProgress * 100 / entity.fileSize).toInt() |
||||
} |
||||
binding.fileSize = target.convertFileSize |
||||
binding.url = entity.url |
||||
binding.filePath = entity.filePath |
||||
mUrl = entity.url |
||||
mFilePath = entity.filePath |
||||
}) |
||||
binding.viewModel = this |
||||
try { |
||||
binding.codeView.setSource(AppUtil.getHelpCode(this, "KotlinHttpDownload.kt"), Language.JAVA) |
||||
} catch (e: IOException) { |
||||
e.printStackTrace() |
||||
} |
||||
|
||||
/** |
||||
* 注解方法不能添加internal修饰符,否则会出现e: [kapt] An exception occurred: java.lang.IllegalArgumentException: index 1 for '$a' not in range (received 0 arguments)错误 |
||||
*/ |
||||
@Download.onTaskRunning |
||||
fun running(task: DownloadTask) { |
||||
Log.d(TAG, task.percent.toString()) |
||||
val len = task.fileSize |
||||
if (len == 0L) { |
||||
binding.progress = 0 |
||||
} else { |
||||
binding.progress = task.percent |
||||
} |
||||
binding.speed = task.convertSpeed |
||||
|
||||
fun chooseUrl() { |
||||
val dialog = ModifyUrlDialog(this, getString(R.string.modify_url_dialog_title), mUrl) |
||||
dialog.show(supportFragmentManager, "ModifyUrlDialog") |
||||
} |
||||
|
||||
fun chooseFilePath() { |
||||
val dialog = ModifyPathDialog(this, getString(R.string.modify_file_path), mFilePath) |
||||
dialog.show(supportFragmentManager, "ModifyPathDialog") |
||||
} |
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean { |
||||
menuInflater.inflate(R.menu.menu_single_task_activity, menu) |
||||
return super.onCreateOptionsMenu(menu) |
||||
} |
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean { |
||||
var speed = -1 |
||||
var msg = "" |
||||
when (item.itemId) { |
||||
R.id.help -> { |
||||
msg = ("一些小知识点:\n" |
||||
+ "1、你可以在注解中增加链接,用于指定被注解的方法只能被特定的下载任务回调,以防止progress乱跳\n" |
||||
+ "2、当遇到网络慢的情况时,你可以先使用onPre()更新UI界面,待连接成功时,再在onTaskPre()获取完整的task数据,然后给UI界面设置正确的数据\n" |
||||
+ "3、你可以在界面初始化时通过Aria.download(this).load(URL).getPercent()等方法快速获取相关任务的一些数据") |
||||
showMsgDialog("tip", msg) |
||||
} |
||||
R.id.speed_0 -> speed = 0 |
||||
R.id.speed_128 -> speed = 128 |
||||
R.id.speed_256 -> speed = 256 |
||||
R.id.speed_512 -> speed = 512 |
||||
R.id.speed_1m -> speed = 1024 |
||||
} |
||||
if (speed > -1) { |
||||
msg = item.title.toString() |
||||
Aria.download(this) |
||||
.setMaxSpeed(speed) |
||||
T.showShort(this, msg) |
||||
} |
||||
return true |
||||
} |
||||
|
||||
@Download.onWait |
||||
fun onWait(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
if (task.key == mUrl) { |
||||
Log.d(TAG, "wait ==> " + task.downloadEntity.fileName) |
||||
} |
||||
} |
||||
|
||||
@Download.onPre |
||||
fun onPre(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "停止" |
||||
if (task.key == mUrl) { |
||||
binding.stateStr = getString(R.string.stop) |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskStart |
||||
fun taskStart(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
if (task.key == mUrl) { |
||||
binding.fileSize = task.convertFileSize |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskComplete |
||||
fun complete(task: DownloadTask) { |
||||
Log.d(TAG, "完成") |
||||
@Download.onTaskRunning |
||||
fun running(task: DownloadTask) { |
||||
if (task.key == mUrl) { |
||||
//Log.d(TAG, task.getKey()); |
||||
val len = task.fileSize |
||||
if (len == 0L) { |
||||
binding.progress = 0 |
||||
} else { |
||||
binding.progress = task.percent |
||||
} |
||||
binding.speed = task.convertSpeed |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskResume |
||||
fun taskResume(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "停止" |
||||
if (task.key == mUrl) { |
||||
binding.stateStr = getString(R.string.stop) |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskStop |
||||
fun taskStop(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
mStart.text = "恢复" |
||||
if (task.key == mUrl) { |
||||
binding.stateStr = getString(R.string.resume) |
||||
binding.speed = "" |
||||
} |
||||
} |
||||
|
||||
@Download.onTaskCancel |
||||
fun taskCancel(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
if (task.key == mUrl) { |
||||
binding.progress = 0 |
||||
Toast.makeText(this@KotlinDownloadActivity, "取消下载", Toast.LENGTH_SHORT) |
||||
.show() |
||||
mStart.text = "开始" |
||||
binding.stateStr = getString(R.string.start) |
||||
binding.speed = "" |
||||
Log.d(TAG, "cancel") |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
@Download.onTaskFail |
||||
fun taskFail(task: DownloadTask) { |
||||
if (task.key == DOWNLOAD_URL) { |
||||
Toast.makeText(this@KotlinDownloadActivity, "下载失败", Toast.LENGTH_SHORT) |
||||
fun taskFail( |
||||
task: DownloadTask, |
||||
e: Exception |
||||
) { |
||||
if (task.key == mUrl) { |
||||
Toast.makeText(this, getString(R.string.download_fail), Toast.LENGTH_SHORT) |
||||
.show() |
||||
mStart.text = "开始" |
||||
binding.stateStr = getString(R.string.start) |
||||
} |
||||
} |
||||
|
||||
@Download.onNoSupportBreakPoint |
||||
fun onNoSupportBreakPoint(task: DownloadTask) { |
||||
Log.d(TAG, "该下载链接不支持断点") |
||||
if (task.key == DOWNLOAD_URL) { |
||||
T.showShort(this@KotlinDownloadActivity, "该下载链接不支持断点") |
||||
@Download.onTaskComplete |
||||
fun taskComplete(task: DownloadTask) { |
||||
|
||||
if (task.key == mUrl) { |
||||
binding.progress = 100 |
||||
Toast.makeText( |
||||
this, getString(R.string.download_success), |
||||
Toast.LENGTH_SHORT |
||||
) |
||||
.show() |
||||
binding.stateStr = getString(R.string.re_start) |
||||
binding.speed = "" |
||||
} |
||||
} |
||||
|
||||
override fun setLayoutId(): Int { |
||||
return R.layout.activity_single_kotlin |
||||
} |
||||
|
||||
fun onClick(view: View) { |
||||
when (view.id) { |
||||
R.id.start -> { |
||||
if (target.isRunning) { |
||||
R.id.start -> if (mTarget!!.isRunning) { |
||||
Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
.load(mUrl!!) |
||||
.stop() |
||||
} else { |
||||
startD() |
||||
} |
||||
} |
||||
R.id.stop -> Aria.download(this).load(DOWNLOAD_URL).stop() |
||||
R.id.cancel -> Aria.download(this).load(DOWNLOAD_URL).cancel() |
||||
R.id.stop -> Aria.download(this).load(mUrl!!).stop() |
||||
R.id.cancel -> Aria.download(this).load(mUrl!!).cancel(true) |
||||
} |
||||
} |
||||
|
||||
private fun startD() { |
||||
Aria.download(this) |
||||
.load(DOWNLOAD_URL) |
||||
.addHeader("groupHash", "value") |
||||
.setFilePath(Environment.getExternalStorageDirectory().path + "/kotlin.apk") |
||||
.load(mUrl!!) |
||||
//.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") |
||||
//.addHeader("Accept-Encoding", "gzip, deflate") |
||||
//.addHeader("DNT", "1") |
||||
//.addHeader("Cookie", "BAIDUID=648E5FF020CC69E8DD6F492D1068AAA9:FG=1; BIDUPSID=648E5FF020CC69E8DD6F492D1068AAA9; PSTM=1519099573; BD_UPN=12314753; locale=zh; BDSVRTM=0") |
||||
.useServerFileName(true) |
||||
.setFilePath(mFilePath!!, true) |
||||
.setFileLenAdapter(IHttpFileLenAdapter { headers -> |
||||
val sLength = headers["Content-Length"] |
||||
if (sLength == null || sLength.isEmpty()) { |
||||
return@IHttpFileLenAdapter -1 |
||||
} |
||||
val temp = sLength[0] |
||||
|
||||
java.lang.Long.parseLong(temp) |
||||
}) |
||||
.start() |
||||
} |
||||
|
||||
override fun onStop() { |
||||
super.onStop() |
||||
//Aria.download(this).unRegister(); |
||||
} |
||||
|
||||
override fun dataCallback( |
||||
result: Int, |
||||
data: Any |
||||
) { |
||||
super.dataCallback(result, data) |
||||
if (result == ModifyUrlDialog.MODIFY_URL_DIALOG_RESULT) { |
||||
mModule!!.uploadUrl(this, data.toString()) |
||||
} else if (result == ModifyPathDialog.MODIFY_PATH_RESULT) { |
||||
mModule!!.updateFilePath(this, data.toString()) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,74 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.core.upload; |
||||
|
||||
import android.arch.lifecycle.LiveData; |
||||
import android.arch.lifecycle.MutableLiveData; |
||||
import android.content.Context; |
||||
import android.os.Environment; |
||||
import com.arialyy.aria.core.Aria; |
||||
import com.arialyy.aria.core.upload.UploadEntity; |
||||
import com.arialyy.frame.base.BaseViewModule; |
||||
import com.arialyy.simple.util.AppUtil; |
||||
|
||||
public class UploadModule extends BaseViewModule { |
||||
private final String FTP_URL_KEY = "FTP_URL_KEY"; |
||||
private final String FTP_PATH_KEY = "FTP_PATH_KEY"; |
||||
private MutableLiveData<UploadEntity> liveData = new MutableLiveData<>(); |
||||
UploadEntity uploadInfo; |
||||
|
||||
/** |
||||
* 获取Ftp上传信息 |
||||
*/ |
||||
LiveData<UploadEntity> getFtpInfo(Context context) { |
||||
String url = AppUtil.getConfigValue(context, FTP_URL_KEY, "ftp://9.9.9.205:2121/aa/你好"); |
||||
String filePath = AppUtil.getConfigValue(context, FTP_PATH_KEY, |
||||
Environment.getExternalStorageDirectory().getPath() + "/AriaPrj.rar"); |
||||
|
||||
UploadEntity entity = Aria.upload(context).getUploadEntity(filePath); |
||||
if (entity != null) { |
||||
uploadInfo = entity; |
||||
AppUtil.setConfigValue(context, FTP_URL_KEY, uploadInfo.getUrl()); |
||||
AppUtil.setConfigValue(context, FTP_PATH_KEY, uploadInfo.getFilePath()); |
||||
} else { |
||||
uploadInfo = new UploadEntity(); |
||||
uploadInfo.setUrl(url); |
||||
uploadInfo.setFilePath(filePath); |
||||
} |
||||
|
||||
liveData.postValue(uploadInfo); |
||||
return liveData; |
||||
} |
||||
|
||||
/** |
||||
* 更新Url |
||||
*/ |
||||
void updateFtpUrl(Context context, String url) { |
||||
uploadInfo.setUrl(url); |
||||
AppUtil.setConfigValue(context, FTP_URL_KEY, url); |
||||
liveData.postValue(uploadInfo); |
||||
} |
||||
|
||||
/** |
||||
* 更新文件路径 |
||||
*/ |
||||
void updateFtpFilePath(Context context, String filePath) { |
||||
uploadInfo.setFilePath(filePath); |
||||
AppUtil.setConfigValue(context, FTP_PATH_KEY, filePath); |
||||
liveData.postValue(uploadInfo); |
||||
} |
||||
} |
@ -0,0 +1,106 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.util; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.content.SharedPreferences; |
||||
import android.net.Uri; |
||||
import android.os.Build; |
||||
import android.support.v4.content.FileProvider; |
||||
import android.text.TextUtils; |
||||
import com.arialyy.aria.util.ALog; |
||||
import com.arialyy.aria.util.CommonUtil; |
||||
import com.arialyy.simple.BuildConfig; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
|
||||
public class AppUtil { |
||||
private static final String TAG = "AppUtil"; |
||||
private static final String ARIA_SHARE_PRE_KEY = "ARIA_SHARE_PRE_KEY"; |
||||
|
||||
/** |
||||
* http下载示例代码 |
||||
*/ |
||||
public static File getHelpCode(Context context, String fileName) throws IOException { |
||||
String path = String.format("%s/code/%s", context.getFilesDir().getPath(), fileName); |
||||
File ftpCode = new File(path); |
||||
if (!ftpCode.exists()) { |
||||
CommonUtil.createFile(path); |
||||
CommonUtil.createFileFormInputStream(context.getAssets() |
||||
.open(String.format("help_code/%s", fileName)), |
||||
path); |
||||
} |
||||
return ftpCode; |
||||
} |
||||
|
||||
/** |
||||
* 读取配置文件字段 |
||||
* |
||||
* @param key key |
||||
* @param defStr 默认字符串 |
||||
*/ |
||||
public static String getConfigValue(Context context, String key, String defStr) { |
||||
SharedPreferences preferences = |
||||
context.getSharedPreferences(ARIA_SHARE_PRE_KEY, Context.MODE_PRIVATE); |
||||
return preferences.getString(key, defStr); |
||||
} |
||||
|
||||
/** |
||||
* set配置文件字段 |
||||
* |
||||
* @param key key |
||||
* @param value 需要保存的字符串 |
||||
*/ |
||||
public static void setConfigValue(Context context, String key, String value) { |
||||
SharedPreferences preferences = |
||||
context.getSharedPreferences(ARIA_SHARE_PRE_KEY, Context.MODE_PRIVATE); |
||||
SharedPreferences.Editor editor = preferences.edit(); |
||||
editor.putString(key, value); |
||||
editor.apply(); |
||||
} |
||||
|
||||
/** |
||||
* 调用系统文件管理器选择文件 |
||||
* |
||||
* @param file 不能是文件夹 |
||||
* @param mineType android 可用的minetype |
||||
* @param requestCode 请求码 |
||||
*/ |
||||
public static void chooseFile(Activity activity, File file, String mineType, int requestCode) { |
||||
if (file.isDirectory()) { |
||||
ALog.e(TAG, "不能选择文件夹"); |
||||
return; |
||||
} |
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
||||
Uri uri; |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { |
||||
uri = FileProvider.getUriForFile(activity.getApplicationContext(), |
||||
BuildConfig.APPLICATION_ID + ".provider", |
||||
file); |
||||
} else { |
||||
uri = Uri.fromFile(file); |
||||
} |
||||
|
||||
intent.setDataAndType(uri, TextUtils.isEmpty(mineType) ? "*/*" : mineType); |
||||
intent.addCategory(Intent.CATEGORY_OPENABLE); |
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); |
||||
|
||||
activity.startActivityForResult(intent, requestCode); |
||||
} |
||||
} |
@ -0,0 +1,76 @@ |
||||
/* |
||||
* Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria)
|
||||
* |
||||
* 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 com.arialyy.simple.widget; |
||||
|
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.util.AttributeSet; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.widget.RelativeLayout; |
||||
import com.arialyy.simple.R; |
||||
import com.arialyy.simple.core.FullScreenCodeActivity; |
||||
import com.pddstudio.highlightjs.HighlightJsView; |
||||
import com.pddstudio.highlightjs.models.Language; |
||||
import com.pddstudio.highlightjs.models.Theme; |
||||
import java.io.File; |
||||
|
||||
/** |
||||
* 代码高亮控件 |
||||
*/ |
||||
public class CodeView extends RelativeLayout { |
||||
|
||||
private HighlightJsView mCodeView; |
||||
private File mSourceFile; |
||||
|
||||
public CodeView(Context context) { |
||||
super(context, null); |
||||
} |
||||
|
||||
public CodeView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
init(context); |
||||
} |
||||
|
||||
private void init(Context context) { |
||||
LayoutInflater.from(context).inflate(R.layout.layout_code_demo, this, true); |
||||
mCodeView = findViewById(R.id.js_view); |
||||
mCodeView.setHighlightLanguage(Language.JAVA); |
||||
mCodeView.setTheme(Theme.ANDROID_STUDIO); |
||||
mCodeView.setZoomSupportEnabled(true); |
||||
findViewById(R.id.full_screen).setOnClickListener(new OnClickListener() { |
||||
@Override public void onClick(View v) { |
||||
// 横屏显示代码
|
||||
Intent intent = new Intent(getContext(), FullScreenCodeActivity.class); |
||||
intent.putExtra(FullScreenCodeActivity.KEY_FILE_PATH, mSourceFile.getPath()); |
||||
getContext().startActivity(intent); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public void setSource(File sourceFile) { |
||||
mSourceFile = sourceFile; |
||||
mCodeView.setSource(sourceFile); |
||||
} |
||||
|
||||
|
||||
public void setSource(File sourceFile, Language language) { |
||||
mSourceFile = sourceFile; |
||||
mCodeView.setHighlightLanguage(language); |
||||
mCodeView.setSource(sourceFile); |
||||
} |
||||
} |
@ -1,6 +1,15 @@ |
||||
<vector android:height="24dp" android:viewportHeight="1024" |
||||
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="#2b2b2b" android:pathData="M895.65,672.81c14.3,0 23.74,-9.54 23.74,-23.74L919.39,269.29a30.46,30.46 0,0 0,-8.66 -21.29L738.23,71.19A30.46,30.46 0,0 0,716.42 62L199.21,62c-52.29,0 -95.07,42.78 -95.07,95.07v654h-0.1v51.85h0.1c0,52.25 42.81,95.07 95.07,95.07h625.11c52.25,0 95.07,-42.81 95.07,-95.07v-71.31c0,-14.31 -9.54,-23.74 -23.74,-23.74s-23.74,9.54 -23.74,23.74v71.32a47.69,47.69 0,0 1,-47.48 47.58L199.21,910.51a47.72,47.72 0,0 1,-47.58 -47.58v-28.11h0.1L151.73,157.06a47.63,47.63 0,0 1,47.58 -47.48h482.47v123.57a71.32,71.32 0,0 0,71.32 71.32h118.8v344.6c-0.09,14.2 9.44,23.74 23.75,23.74zM847.19,256.81L753,256.81v0.1c-14.3,0 -23.74,-11.92 -23.74,-23.74v-98.75a2.4,2.4 0,0 1,4.13 -1.67l115.52,120a2.4,2.4 0,0 1,-1.73 4.04z"/> |
||||
<path android:fillColor="#2b2b2b" android:pathData="M318.91,394.59c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h300.47c14.78,0 26.81,12.25 26.81,27.3s-12,27.3 -26.81,27.3zM318.91,569.8c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h384.85c14.78,0 26.81,12.25 26.81,27.3s-12,27.3 -26.81,27.3zM318.91,742c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h261.3c14.78,0 26.81,12.25 26.81,27.3S595,742 580.21,742z"/> |
||||
<path android:fillColor="#2b2b2b" android:pathData="M704.01,367.3m-27.3,0a27.3,27.3 0,1 0,54.6 0,27.3 27.3,0 1,0 -54.6,0Z"/> |
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:height="24dp" |
||||
android:viewportHeight="1024" |
||||
android:viewportWidth="1024" |
||||
android:width="24dp"> |
||||
<path |
||||
android:fillColor="@color/icon_black" |
||||
android:pathData="M895.65,672.81c14.3,0 23.74,-9.54 23.74,-23.74L919.39,269.29a30.46,30.46 0,0 0,-8.66 -21.29L738.23,71.19A30.46,30.46 0,0 0,716.42 62L199.21,62c-52.29,0 -95.07,42.78 -95.07,95.07v654h-0.1v51.85h0.1c0,52.25 42.81,95.07 95.07,95.07h625.11c52.25,0 95.07,-42.81 95.07,-95.07v-71.31c0,-14.31 -9.54,-23.74 -23.74,-23.74s-23.74,9.54 -23.74,23.74v71.32a47.69,47.69 0,0 1,-47.48 47.58L199.21,910.51a47.72,47.72 0,0 1,-47.58 -47.58v-28.11h0.1L151.73,157.06a47.63,47.63 0,0 1,47.58 -47.48h482.47v123.57a71.32,71.32 0,0 0,71.32 71.32h118.8v344.6c-0.09,14.2 9.44,23.74 23.75,23.74zM847.19,256.81L753,256.81v0.1c-14.3,0 -23.74,-11.92 -23.74,-23.74v-98.75a2.4,2.4 0,0 1,4.13 -1.67l115.52,120a2.4,2.4 0,0 1,-1.73 4.04z"/> |
||||
<path |
||||
android:fillColor="@color/icon_black" |
||||
android:pathData="M318.91,394.59c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h300.47c14.78,0 26.81,12.25 26.81,27.3s-12,27.3 -26.81,27.3zM318.91,569.8c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h384.85c14.78,0 26.81,12.25 26.81,27.3s-12,27.3 -26.81,27.3zM318.91,742c-14.78,0 -26.81,-12.25 -26.81,-27.3s12,-27.3 26.81,-27.3h261.3c14.78,0 26.81,12.25 26.81,27.3S595,742 580.21,742z"/> |
||||
<path |
||||
android:fillColor="@color/icon_black" |
||||
android:pathData="M704.01,367.3m-27.3,0a27.3,27.3 0,1 0,54.6 0,27.3 27.3,0 1,0 -54.6,0Z"/> |
||||
</vector> |
||||
|
@ -0,0 +1,6 @@ |
||||
<vector android:height="24dp" android:viewportHeight="1024" |
||||
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="#CE9F06" android:pathData="M975,774.77H49V220.37h857.09c38.11,0 69.04,30.93 69.04,69.04v485.37h-0.14zM627.71,220.37c0,-63.93 -51.78,-115.71 -115.71,-115.71H49v115.71h578.71"/> |
||||
<path android:fillColor="#FFFFFF" android:pathData="M164.72,620.26V273.11h694.56v347.14h-694.56z"/> |
||||
<path android:fillColor="#FFCD2C" android:pathData="M975,848.1V398.49c0,-31.9 -25.96,-57.86 -57.86,-57.86H106.86c-31.9,0 -57.86,25.96 -57.86,57.86v449.46c0,39.35 31.9,71.39 71.39,71.39h783.35c39.35,0.14 71.25,-31.9 71.25,-71.25z"/> |
||||
</vector> |
@ -0,0 +1,4 @@ |
||||
<vector android:height="24dp" android:viewportHeight="1024" |
||||
android:viewportWidth="1024" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<path android:fillColor="@color/icon_black" android:pathData="M432,636.8l-41.6,-41.6 -150.4,150.4L128,633.6L128,896h259.2l-108.8,-108.8 153.6,-150.4zM595.2,387.2l41.6,41.6 150.4,-150.4 108.8,108.8L896,128h-259.2l108.8,108.8 -150.4,150.4zM128,128v259.2l108.8,-108.8 150.4,150.4 41.6,-41.6 -147.2,-150.4L390.4,128L128,128zM595.2,636.8l150.4,150.4 -108.8,108.8L896,896v-259.2l-108.8,108.8 -150.4,-150.4 -41.6,41.6zM595.2,636.8"/> |
||||
</vector> |
@ -0,0 +1,17 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical" |
||||
> |
||||
|
||||
<com.pddstudio.highlightjs.HighlightJsView |
||||
android:id="@+id/code_view" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
/> |
||||
|
||||
</LinearLayout> |
||||
|
||||
</layout> |
@ -0,0 +1,86 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:bind="http://schemas.android.com/apk/res-auto" |
||||
> |
||||
|
||||
<data> |
||||
<variable |
||||
name="fileSize" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="speed" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="progress" |
||||
type="int" |
||||
/> |
||||
<variable |
||||
name="stateStr" |
||||
type="String" |
||||
/> |
||||
|
||||
<variable |
||||
name="url" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="filePath" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="viewModel" |
||||
type="com.arialyy.simple.core.download.KotlinDownloadActivity" |
||||
/> |
||||
</data> |
||||
|
||||
<LinearLayout |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:fitsSystemWindows="true" |
||||
android:orientation="vertical" |
||||
tools:context=".core.download.SingleTaskActivity" |
||||
> |
||||
|
||||
<include layout="@layout/layout_bar"/> |
||||
|
||||
<com.arialyy.simple.widget.SvgTextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="16dp" |
||||
bind:iconClickListener="@{() -> viewModel.chooseUrl()}" |
||||
bind:svg_text_view_icon="@drawable/ic_modify" |
||||
bind:text="@{@string/url(url)}" |
||||
/> |
||||
|
||||
<com.arialyy.simple.widget.SvgTextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="8dp" |
||||
bind:iconClickListener="@{() -> viewModel.chooseFilePath()}" |
||||
bind:svg_text_view_icon="@drawable/ic_choose_file" |
||||
bind:text="@{@string/file_path(filePath)}" |
||||
/> |
||||
|
||||
<include |
||||
layout="@layout/layout_content_single" |
||||
bind:fileSize="@{fileSize}" |
||||
bind:progress="@{progress}" |
||||
bind:speed="@{speed}" |
||||
bind:stateStr="@{stateStr}" |
||||
/> |
||||
|
||||
<com.arialyy.simple.widget.CodeView |
||||
android:id="@+id/code_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
/> |
||||
|
||||
</LinearLayout> |
||||
</layout> |
@ -0,0 +1,81 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<data> |
||||
<variable |
||||
name="currentPath" |
||||
type="String" |
||||
/> |
||||
<import type="android.text.Html"/> |
||||
</data> |
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/white" |
||||
android:orientation="vertical" |
||||
> |
||||
|
||||
<TextView |
||||
android:id="@+id/title" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/background_color" |
||||
android:maxHeight="400dp" |
||||
android:paddingBottom="8dp" |
||||
android:paddingLeft="16dp" |
||||
android:paddingRight="16dp" |
||||
android:paddingTop="8dp" |
||||
android:text="@string/choose_dir" |
||||
android:textColor="@android:color/black" |
||||
android:textSize="22sp" |
||||
/> |
||||
|
||||
<TextView |
||||
android:id="@+id/current_path" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_below="@+id/title" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="4dp" |
||||
android:ellipsize="marquee" |
||||
android:singleLine="true" |
||||
android:text="@{Html.fromHtml(@string/current_path(currentPath))}" |
||||
/> |
||||
|
||||
<TextView |
||||
android:id="@+id/up" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_below="@+id/current_path" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:gravity="center_vertical" |
||||
android:text=".." |
||||
android:textColor="@color/text_black" |
||||
android:textSize="18sp" |
||||
android:textStyle="bold" |
||||
/> |
||||
|
||||
<android.support.v7.widget.RecyclerView |
||||
android:id="@+id/list" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_above="@+id/enter" |
||||
android:layout_below="@+id/up" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="4dp" |
||||
/> |
||||
|
||||
<Button |
||||
android:id="@+id/enter" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_alignParentBottom="true" |
||||
android:text="@string/choose_current_dir" |
||||
style="?buttonBarButtonStyle" |
||||
/> |
||||
|
||||
|
||||
</RelativeLayout> |
||||
</layout> |
@ -0,0 +1,120 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:bind="http://schemas.android.com/apk/res-auto" |
||||
> |
||||
<data> |
||||
<variable |
||||
name="title" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="name" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="dir" |
||||
type="String" |
||||
/> |
||||
<variable |
||||
name="viewModel" |
||||
type="com.arialyy.simple.common.ModifyPathDialog" |
||||
/> |
||||
</data> |
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/white" |
||||
android:orientation="vertical" |
||||
> |
||||
|
||||
<TextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:background="@color/background_color" |
||||
android:gravity="center|left" |
||||
android:maxHeight="400dp" |
||||
android:paddingBottom="8dp" |
||||
android:paddingLeft="16dp" |
||||
android:paddingRight="16dp" |
||||
android:paddingTop="8dp" |
||||
android:text="@{title}" |
||||
android:textColor="@android:color/black" |
||||
android:textSize="22sp" |
||||
/> |
||||
|
||||
<com.arialyy.simple.widget.SvgTextView |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="16dp" |
||||
bind:iconClickListener="@{() -> viewModel.chooseDir()}" |
||||
bind:svg_text_view_icon="@drawable/ic_modify" |
||||
bind:text="@{@string/dir_path(dir)}" |
||||
/> |
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
android:layout_marginTop="8dp" |
||||
> |
||||
|
||||
<TextView |
||||
android:id="@+id/dir_hint" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:text="@string/name" |
||||
android:textColor="@color/text_black" |
||||
android:textSize="@dimen/text_size_normal" |
||||
android:textStyle="bold" |
||||
/> |
||||
|
||||
<EditText |
||||
android:id="@+id/edit" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:layout_marginBottom="15dp" |
||||
android:layout_marginLeft="8dp" |
||||
android:layout_toRightOf="@+id/dir_hint" |
||||
android:background="@android:color/transparent" |
||||
android:hint="@string/file_name_hint" |
||||
android:lineSpacingMultiplier="1.2" |
||||
android:text="@={name}" |
||||
android:textColor="@color/text_black" |
||||
android:textSize="@dimen/text_size_normal" |
||||
/> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
<LinearLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="horizontal" |
||||
> |
||||
<Button |
||||
android:id="@+id/cancel" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_weight="1" |
||||
android:text="@string/cancel" |
||||
style="?buttonBarButtonStyle" |
||||
/> |
||||
|
||||
<Button |
||||
android:id="@+id/enter" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_weight="1" |
||||
android:text="@string/enter" |
||||
style="?buttonBarButtonStyle" |
||||
/> |
||||
|
||||
</LinearLayout> |
||||
|
||||
|
||||
</LinearLayout> |
||||
</layout> |
@ -0,0 +1,31 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="@dimen/bar_height" |
||||
android:background="@drawable/item_bg" |
||||
android:orientation="vertical" |
||||
> |
||||
|
||||
<android.support.v7.widget.AppCompatImageView |
||||
android:id="@+id/icon" |
||||
android:layout_width="@dimen/icon_size" |
||||
android:layout_height="@dimen/icon_size" |
||||
android:layout_centerVertical="true" |
||||
app:srcCompat="@drawable/ic_dir" |
||||
/> |
||||
|
||||
<TextView |
||||
android:id="@+id/text" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:layout_marginLeft="8dp" |
||||
android:layout_toRightOf="@+id/icon" |
||||
android:ellipsize="marquee" |
||||
android:singleLine="true" |
||||
android:textColor="@color/text_black" |
||||
android:textSize="@dimen/text_size_normal" |
||||
/> |
||||
|
||||
</RelativeLayout> |
@ -0,0 +1,44 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
android:orientation="vertical" |
||||
> |
||||
|
||||
|
||||
<RelativeLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:layout_marginLeft="16dp" |
||||
android:layout_marginRight="16dp" |
||||
> |
||||
<TextView |
||||
android:id="@+id/hint" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_centerVertical="true" |
||||
android:text="@string/code_simple" |
||||
android:textColor="@color/text_black" |
||||
android:textSize="@dimen/text_size_normal" |
||||
android:textStyle="bold" |
||||
/> |
||||
<android.support.v7.widget.AppCompatImageView |
||||
android:id="@+id/full_screen" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_alignParentRight="true" |
||||
android:layout_centerVertical="true" |
||||
app:srcCompat="@drawable/ic_full_screen" |
||||
/> |
||||
|
||||
</RelativeLayout> |
||||
|
||||
<com.pddstudio.highlightjs.HighlightJsView |
||||
android:id="@+id/js_view" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
/> |
||||
|
||||
|
||||
</LinearLayout> |
@ -0,0 +1,12 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
|
||||
<string name="code_ftp_upload"> |
||||
Aria.upload(this) |
||||
.loadFtp("/mnt/sdcard/gggg.apk") //上传文件路径 |
||||
.setUploadUrl(URL) //上传的ftp服务器地址 |
||||
.login("lao", "123456") |
||||
.start(); |
||||
</string> |
||||
|
||||
</resources> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue