Files
assistant-android/app/src/main/java/com/gh/common/util/Extensions.kt
juntao 013969a73a 修复闪退
1. 捕抓首页刷新列表时偶发的数组越界闪退
2. 提高线程池并发数量,避免安装了大量游戏的设备检查更新不及时
3. 捕抓插件化时因获取不到包名而出现的闪退
2021-03-05 14:45:46 +08:00

950 lines
29 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.gh.common.util
import android.animation.Animator
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.text.*
import android.text.style.ClickableSpan
import android.text.style.ImageSpan
import android.text.style.URLSpan
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.PopupWindow
import android.widget.SeekBar
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.*
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.airbnb.lottie.LottieAnimationView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.DefaultUrlHandler
import com.gh.common.constant.Config
import com.gh.common.constant.Constants
import com.gh.common.view.CenterImageSpan
import com.gh.common.view.CustomLinkMovementMethod
import com.gh.common.view.ExpandTextView
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.manager.UserManager
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.ObservableTransformer
import io.reactivex.SingleTransformer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.RequestBody
import java.net.URI
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import kotlin.math.abs
/**
* 创建以 activity 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> FragmentActivity.viewModelProvider(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(this, provider).get(VM::class.java)
/**
* 创建以 activity 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> Fragment.viewModelProviderFromParent(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(requireActivity(), provider).get(VM::class.java)
/**
* 创建以 activity 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> FragmentActivity.viewModelProviderFromParent(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(this, provider).get(VM::class.java)
/**
* 创建以 fragment 为观察者上下文的 viewModel
*/
inline fun <reified VM : ViewModel> Fragment.viewModelProvider(
provider: ViewModelProvider.Factory? = null
) =
ViewModelProviders.of(this, provider).get(VM::class.java)
/**
*
* ViewPager Extensions
*
*/
fun ViewPager.doOnPageSelected(action: (position: Int) -> Unit) = addOnPageChangeListener(onSelected = action)
fun ViewPager.addOnPageChangeListener(onSelected: ((position: Int) -> Unit)? = null) {
val listener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
// Do nothing.
}
override fun onPageSelected(position: Int) {
onSelected?.invoke(position)
}
override fun onPageScrollStateChanged(state: Int) {
// Do nothing.
}
}
addOnPageChangeListener(listener)
}
fun ViewPager.addOnScrollStateChanged(onStateChanged: ((state: Int) -> Unit)? = null) {
val listener = object : ViewPager.OnPageChangeListener {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
}
override fun onPageScrollStateChanged(state: Int) {
onStateChanged?.invoke(state)
}
}
addOnPageChangeListener(listener)
}
/**
* Fragment related
*/
inline fun <reified T : Fragment> Fragment.fragmentFromActivity() =
parentFragmentManager.findFragmentByTag(T::class.java.simpleName) as? T
?: parentFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, T::class.java.canonicalName) as T
inline fun <reified T : Fragment> Fragment.fragmentFromParentFragment() =
childFragmentManager.findFragmentByTag(T::class.java.simpleName) as? T
?: childFragmentManager.fragmentFactory.instantiate(requireContext().classLoader, T::class.java.canonicalName) as T
/**
* RecyclerView Extensions
*/
// 监听滚动距离
fun RecyclerView.doOnScrolledSpecificDistance(distanceX: Int = 0, distanceY: Int = 0, singleTimeEvent: Boolean = false, action: () -> Unit) {
val listener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if ((distanceX != 0 && abs(dx) > distanceX) || (distanceY != 0 && abs(dy) > distanceY)) {
action.invoke()
if (singleTimeEvent) {
removeOnScrollListener(this)
}
}
}
}
addOnScrollListener(listener)
}
/**
* View Extensions
*/
fun View.visibleIf(predicate: Boolean) {
visibility = if (predicate) {
View.VISIBLE
} else {
View.INVISIBLE
}
}
fun View.goneIf(predicate: Boolean) {
visibility = if (predicate) {
View.GONE
} else {
View.VISIBLE
}
}
fun View.addSelectableItemBackground() {
val outValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
setBackgroundResource(outValue.resourceId);
}
fun View.removeSelectableItemBackground() {
background = null
}
fun View.setRoundedColorBackground(@ColorRes color: Int, radius: Float) {
val shape = GradientDrawable()
shape.cornerRadius = radius.dip2px().toFloat()
shape.setColor(ContextCompat.getColor(context, color))
background = shape
}
fun View.setDebouncedClickListener(action: () -> Unit) {
setOnClickListener { debounceActionWithInterval(interval = 300L) { action.invoke() } }
}
fun isPublishEnv(): Boolean {
return BuildConfig.FLAVOR != "internal"
}
/**
* LiveData Extensions
*/
fun <T> LiveData<T?>.observeNonNull(owner: LifecycleOwner, callback: (T) -> Unit) {
observe(owner, Observer { value ->
if (value != null) {
callback(value)
}
})
}
/**
* Login related extensions
*/
fun Fragment.ifLogin(entrance: String, action: (() -> Unit)? = null) {
requireContext().ifLogin(entrance, action)
}
fun Context.ifLogin(entrance: String, action: (() -> Unit)? = null) {
CheckLoginUtils.checkLogin(this, entrance, action)
}
/**
* Gson related extensions.
*/
inline fun <reified T : Any> String.toObject(): T? {
return try {
GsonUtils.gson.fromJson(this, object : TypeToken<T>() {}.type)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
inline fun <reified T : Any> T.toJson(): String {
return GsonUtils.toJson(this)
}
fun String.insert(index: Int, string: String): String {
return this.substring(0, index) + string + this.substring(index, this.length)
}
/**
* TextView 内部处理 ul li ol 得跟 Android 版本走,这里换成专属的标签手动处理
*/
fun String.replaceUnsupportedHtmlTag(): String {
return this.replace("<ul", "<hul")
.replace("</ul>", "</hul>")
.replace("<li", "<hli")
.replace("</li>", "</hli>")
.replace("<ol", "<hol")
.replace("</ol>", "</hol>")
}
fun String.containHtmlTag(): Boolean {
val pattern = Pattern.compile("<(\"[^\"]*\"|'[^']*'|[^'\">])*>")
val matcher = pattern.matcher(this)
return matcher.find()
}
/**
* 用户行为相关
*/
fun Fragment.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
if (UserManager.getInstance().userShouldTakeRegulationBaseOnLastRemind()) {
DialogUtils.showRegulationTestDialog(requireContext(),
{ DirectUtils.directToRegulationTestPage(requireContext()) },
{ action.invoke() })
} else {
action()
}
}
fun Context.showRegulationTestDialogIfNeeded(action: (() -> Unit)) {
if (UserManager.getInstance().userShouldTakeRegulationBaseOnLastRemind()) {
DialogUtils.showRegulationTestDialog(this,
{ DirectUtils.directToRegulationTestPage(this) },
{ action.invoke() })
} else {
action()
}
}
/**
* 在限定 interval 里只触发一次 action
*/
fun debounceActionWithInterval(id: Int, interval: Long = 300, action: (() -> Unit)? = null) {
if (!ClickUtils.isFastDoubleClick(id, interval)) {
action?.invoke()
}
}
fun View.debounceActionWithInterval(interval: Long = 300, action: (() -> Unit)? = null) {
debounceActionWithInterval(this.id, interval, action)
}
/**
* 告诉需要返回 true or false 的外层这个事件已经被消费(即返回 true
*/
inline fun consume(f: () -> Unit): Boolean {
f()
return true
}
/**
* 简化那些不得不写 try catch 的代码块
*/
inline fun tryWithDefaultCatch(action: (() -> Unit)) {
try {
action.invoke()
} catch (e: Throwable) {
e.printStackTrace()
}
}
/**
* 只在正式版本进行 try catch 操作
*
* 对于个别偶发的异常尽量不要做暴力的 try catch 处理
*/
inline fun tryCatchInRelease(action: (() -> Unit)) {
try {
action.invoke()
} catch (e: Throwable) {
if (BuildConfig.DEBUG) throw e
else e.printStackTrace()
}
}
/**
* 在 debug 状态下抛出异常
*/
fun throwExceptionInDebug(message: String = "", predicate: Boolean = true) {
if (predicate && BuildConfig.DEBUG) {
throw RuntimeException(message)
}
}
/**
* 在自动打包的包里弹 toast
*/
fun toastInInternalRelease(content: String) {
if (BuildConfig.BUILD_TIME != 0L) {
Utils.toast(HaloApp.getInstance(), content)
}
}
/**
* 主动抛出异常
*/
fun throwException(message: String = "", predicate: Boolean = true) {
if (predicate) {
throw RuntimeException(message)
}
}
/**
* String related
*/
fun String.fromHtml(): Spanned {
return HtmlCompat.fromHtml(this, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
// 将双引号换成单引号避免 JSON 解析异常 ( escape 后会有其它异常,暂时先替换成单引号了)
fun String.eliminateDoubleQuote(): String {
return this.replace("\"", "'")
}
// 去掉文章/答案的插入内容
fun String.removeInsertedContent(): String {
val textRegex = "(?s)<div class=\"gh-internal-content content-right\".*?</div>"
return this.replace(textRegex.toRegex(), "")
}
// 去除视频相关文本
fun String.removeVideoContent(): String {
val videoRegex = "(?s)<div class=\"insert-video-container\".*?</div>"
return this.replace(videoRegex.toRegex(), "")
}
// 完全地清除所有 Html 格式
fun String.clearHtmlFormatCompletely(): String {
return Html.fromHtml(this).toString().replace('\n', 32.toChar())
.replace(160.toChar(), 32.toChar()).replace(65532.toChar(), 32.toChar()).trim { it <= ' ' }
}
// 如果该字符串长度超过固定长度的话,从头开始截取固定长度并返回
fun String.subStringIfPossible(length: Int): String {
return if (this.length > length) {
this.substring(0, length)
} else {
this
}
}
fun String.countOccurrences(char: String): Int {
return StringTokenizer(" $this ", char).countTokens() - 1
}
fun String.getFirstElementDividedByDivider(divider: String): String {
if (this.contains(divider)) {
return this.split(divider.toRegex()).toTypedArray()[0]
}
return this
}
fun String.copyText() {
this.copyTextAndToast("")
}
fun String.copyTextAndToast(toastText: String = "复制成功") {
try {
val application = HaloApp.getInstance().application
val cmb = application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(null, this)
cmb.setPrimaryClip(clip)
if (!TextUtils.isEmpty(toastText)) {
ToastUtils.showToast(toastText)
}
} catch (e: SecurityException) {
// 在一些情况下会报以下这样的错误,可能是以浮动窗口显示然后没有焦点?(https://developer.android.com/about/versions/10/privacy/changes#clipboard-data)
// java.lang.Throwable: java.lang.SecurityException: com.xunmeng.pinduoduo from uid 10317 not allowed to perform READ_CLIPBOARD
ToastUtils.showToast("复制失败,请重试")
}
}
fun Map<String, String>.createRequestBody(): RequestBody {
val json = GsonUtils.toJson(this)
return RequestBody.create(MediaType.parse("application/json"), json)
}
fun Map<String, Any>.createRequestBodyAny(): RequestBody {
val json = GsonUtils.toJson(this)
return RequestBody.create(MediaType.parse("application/json"), json)
}
fun Any.toRequestBody(): RequestBody {
val json = GsonUtils.toJson(this)
return RequestBody.create(MediaType.parse("application/json"), json)
}
// 对在浏览器(WebView)显示的路径进行转码
fun String.decodeURI(): String {
return URI(null, null, this, null).rawPath
}
/**
* 根据手机的分辨率从 dip(像素) 的单位 转成为 px
*/
fun Float.dip2px(): Int {
val scale = HaloApp.getInstance().application.resources.displayMetrics.density
return (this * scale + 0.5f).toInt()
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dip
*/
fun Float.px2dip(): Int {
val scale = HaloApp.getInstance().application.resources.displayMetrics.density
return (this / scale + 0.5f).toInt()
}
fun Float.sp2px(): Int {
val scale: Float = HaloApp.getInstance().application.resources.displayMetrics.scaledDensity
return (this * scale + 0.5f).toInt()
}
/**
* PopupWindow 自动适配方向
* 弹出与锚点右对齐
*/
fun PopupWindow.showAutoOrientation(anchorView: View, distanceY: Int = 0, distanceX: Int = 0) {
val windowPos = IntArray(2)
val anchorLoc = IntArray(2)
// 获取锚点View在屏幕上的左上角坐标位置
anchorView.getLocationOnScreen(anchorLoc)
val anchorHeight = anchorView.height + distanceY
// 获取屏幕的高宽
val screenHeight = anchorView.context.resources.displayMetrics.heightPixels
// 测量contentView
contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
// 计算contentView的高宽
val windowHeight = contentView.measuredHeight
val windowWidth = contentView.measuredWidth
// 判断需要向上弹出还是向下弹出显示
val isNeedShowUp = screenHeight - anchorLoc[1] - anchorHeight < windowHeight
windowPos[1] = if (isNeedShowUp) {
anchorLoc[1] - windowHeight
} else anchorLoc[1] + anchorHeight
windowPos[0] = anchorLoc[0] - windowWidth + anchorView.width - distanceX
animationStyle = R.style.popwindow_option_anim_style
showAtLocation(anchorView, Gravity.TOP or Gravity.START, windowPos[0], windowPos[1])
}
/**
* 权限相关
*/
fun Fragment.checkReadPhoneStateAndStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStateAndStoragePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun Fragment.checkReadPhoneStatePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStatePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun Fragment.checkStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkStoragePermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun Fragment.checkCalendarPermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkCalendarPermissionBeforeAction(requireContext(), object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkReadPhoneStateAndStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStateAndStoragePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkReadPhoneStatePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkReadPhoneStatePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkStoragePermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkStoragePermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
fun FragmentActivity.checkCalendarPermissionBeforeAction(action: (() -> Unit)) {
PermissionHelper.checkCalendarPermissionBeforeAction(this, object : EmptyCallback {
override fun onCallback() {
action.invoke()
}
})
}
/**
* List related.
*/
// Returns the second element, or `null` if the list is empty.
fun <T> List<T>.secondOrNull(): T? {
return if (isEmpty() || size == 1) null else this[1]
}
/**
* TextView related.
*/
fun TextView.setTextWithHighlightedTextWrappedInsideWrapper(text: CharSequence,
wrapper: String = Constants.DEFAULT_TEXT_WRAPPER,
@ColorRes
highlightColorId: Int = R.color.theme_font,
copyClickedText: Boolean = false,
highlightedTextClickListener: (() -> Unit)? = null) {
TextHelper.highlightTextThatIsWrappedInsideWrapper(this, text, wrapper, highlightColorId, object : SimpleCallback<String> {
override fun onCallback(arg: String) {
if (copyClickedText) {
arg.copyTextAndToast("已复制:$arg")
}
highlightedTextClickListener?.invoke()
}
})
}
fun TextView.setTextChangedListener(action: (s: CharSequence, start: Int, before: Int, count: Int) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
action.invoke(s ?: "", start, before, count)
}
})
}
/**
* List related
*/
fun <T> List<T>.safelyGetInRelease(index: Int): T? {
return if (index >= size) {
throwExceptionInDebug("这里触发了数组越界,请检查 (index $index >= size $size)")
toastInInternalRelease("这个操作可能触发闪退,请确定复现方式并联系开发处理")
null
} else {
try {
this[index]
} catch (e : IndexOutOfBoundsException){
e.printStackTrace()
null
}
}
}
/**
* 拦截 TextView 中的 Url Span用应用内页面的形式打开链接
* @param shrankText 未展开时的文字
* @param expandedText 展开后的文字
*/
fun ExpandTextView.setTextWithInterceptingInternalUrl(shrankText: CharSequence, expandedText: CharSequence) {
var shrankSsb = shrankText.interceptUrlSpanAndRoundImageSpan()
var expandedSsb = expandedText.interceptUrlSpanAndRoundImageSpan()
// 去掉旧版本 Android 系统 [Html.FROM_HTML_MODE_LEGACY] 产生的两个换行符 (丑陋的代码)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
while (shrankSsb.contains("\n\n")) {
val index = shrankSsb.indexOf("\n\n", 0, true)
shrankSsb = SpannableStringBuilder(shrankSsb.subSequence(0, index)).append(shrankSsb.subSequence(index + "\n".length, shrankSsb.length))
}
while (expandedSsb.contains("\n\n")) {
val index = expandedSsb.indexOf("\n\n", 0, true)
expandedSsb = SpannableStringBuilder(expandedSsb.subSequence(0, index)).append(expandedSsb.subSequence(index + "\n".length, expandedSsb.length))
}
}
// 去掉多余的 P 标签换行
if (expandedSsb.endsWith("\n", true)) {
expandedSsb = SpannableStringBuilder((expandedSsb.subSequence(0, expandedSsb.length - "\n".length)))
}
movementMethod = CustomLinkMovementMethod.getInstance()
shrankSsb = TextHelper.updateSpannableStringWithHighlightedSpan(context, shrankSsb, highlightedTextClickListener = null)
expandedSsb = TextHelper.updateSpannableStringWithHighlightedSpan(context, expandedSsb, highlightedTextClickListener = null)
setShrankTextAndExpandedText(shrankSsb, expandedSsb)
}
fun CharSequence.interceptUrlSpanAndRoundImageSpan(): SpannableStringBuilder {
return SpannableStringBuilder.valueOf(this).apply {
getSpans(0, length, URLSpan::class.java).forEach {
setSpan(
object : ClickableSpan() {
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.color = ContextCompat.getColor(HaloApp.getInstance().application, R.color.theme_font)
ds.isUnderlineText = false
}
override fun onClick(widget: View) {
if (!DefaultUrlHandler.interceptUrl(widget.context, it.url, "")) {
widget.context.startActivity(WebActivity.getIntent(widget.context, it.url, true))
}
}
},
getSpanStart(it),
getSpanEnd(it),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
removeSpan(it)
}
getSpans(0, length, ImageSpan::class.java).forEach {
setSpan(
CenterImageSpan(it.drawable),
getSpanStart(it),
getSpanEnd(it),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
removeSpan(it)
}
}
}
fun Int.toColor(): Int {
return ContextCompat.getColor(HaloApp.getInstance().application, this)
}
fun Int.toResString(): String {
return HaloApp.getInstance().application.resources.getString(this)
}
fun Int.toSimpleCount(): String {
return NumberUtils.transSimpleCount(this)
}
/**
* Image related
*/
fun SimpleDraweeView.display(url: String) {
ImageUtils.display(this, url)
}
/**
* DownloadEntity extension
*/
fun DownloadEntity.addMetaExtra(key: String, value: String?) {
value?.let {
if (meta == null) {
meta = hashMapOf()
}
meta[key] = value
}
}
fun DownloadEntity.getMetaExtra(key: String): String {
return meta?.get(key) ?: ""
}
fun DownloadEntity.isSilentUpdate(): Boolean {
return Constants.SILENT_UPDATE == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}
fun DownloadEntity.isSimulatorDownload(): Boolean {
return Constants.SIMULATOR_DOWNLOAD == getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)
}
fun DownloadEntity.isSimulatorGame(): Boolean {
return getMetaExtra(Constants.SIMULATOR_GAME).isNotEmpty()
}
/**
* Process related
*/
fun Context.doOnMainProcessOnly(callback: EmptyCallback) {
doOnMainProcessOnly { callback.onCallback() }
}
/**
* 虽然现在我们没有了友盟以后只是单进程APP但在 debug 模式下还有 whatTheStack 这个进程如果不限定主进程会出现奇奇怪怪的问题 (BroadcastReceiver相关)
*/
inline fun Context.doOnMainProcessOnly(f: () -> Unit) {
val processName = PackageUtils.obtainProcessName(this)
if (processName == null || BuildConfig.APPLICATION_ID == processName) {
f.invoke()
} else {
tryWithDefaultCatch {
Utils.log("Block one useless sub process method call from ${Thread.currentThread().stackTrace[3].methodName} -> ${Thread.currentThread().stackTrace[2].methodName}.")
}
}
}
inline fun doOnMainProcessOnly(f: () -> Unit) {
val processName = PackageUtils.obtainProcessName(HaloApp.getInstance().application)
if (processName == null || BuildConfig.APPLICATION_ID == processName) {
f.invoke()
} else {
tryWithDefaultCatch {
Utils.log("Block one useless sub process method call from ${Thread.currentThread().stackTrace[3].methodName} -> ${Thread.currentThread().stackTrace[2].methodName}.")
}
}
}
/**
* 测试用包裹
*/
inline fun debugOnly(f: () -> Unit) {
if (BuildConfig.DEBUG) {
f()
}
}
inline fun testChannelOnly(f: () -> Unit) {
if (HaloApp.getInstance().channel == Config.DEFAULT_CHANNEL) {
f()
}
}
/**
* 倒计时单位s
*/
inline fun countDownTimer(
timeInSeconds: Long,
crossinline block: (finish: Boolean, remainingTime: Long) -> Unit
): Disposable {
var subscribe: Disposable? = null
subscribe = Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (it < timeInSeconds) {
block.invoke(false, timeInSeconds - it)
} else {
block.invoke(true, 0)
if (subscribe != null && !subscribe!!.isDisposed) {
subscribe?.dispose()
}
}
}
return subscribe
}
/**
* 正计时
* @start 起始时间
*/
inline fun countUpTimer(
start: Long,
period: Long = 1000,
crossinline block: (millisUntilFinished: Long) -> Unit
): Disposable {
var startTime = start
return Observable.interval(0, period, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
startTime += period
block.invoke(startTime)
}
}
/**
* 计时器(注意不需要的时候要取消订阅)
*/
inline fun rxTimer(interval: Long, crossinline block: (times: Long) -> Unit): Disposable {
return Observable.interval(0, interval, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
block.invoke(it)
}
}
fun LottieAnimationView.doOnAnimationEnd(action: () -> Unit) {
this.addAnimatorListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationEnd(animation: Animator?) {
action.invoke()
}
override fun onAnimationCancel(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
})
}
fun String?.getExtension(): String? {
this ?: return null
tryCatchInRelease {
val lastDotIndex = this.lastIndexOf('.')
return if (lastDotIndex == -1) null else this.substring(lastDotIndex + 1)
}
return null
}
/**
* 检查内容是否一致
* @return true:相同 false:不同
*/
fun List<String>?.checkSameFromStringArray(check2: List<String>?): Boolean {
if (this == check2) {
return true
}
if (this == null && check2 == null) {
return true
}
if (this == null || check2 == null) {
return false
}
if (this.size != check2.size) {
return false
}
for (tag in this) {
if (!check2.contains(tag)) return false
}
for (tag in check2) {
if (!this.contains(tag)) return false
}
return true
}
/**
* EditText弹出软键盘
*/
fun EditText.showKeyBoard() {
this.postDelayed({
this.requestFocus()
val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(this, 0)
}, 300)
}
fun SeekBar.doOnSeekBarChangeListener(progressChange: ((progress: Int) -> Unit)? = null, onStopTrackingTouch: (() -> Unit)? = null) {
this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
progressChange?.invoke(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
onStopTrackingTouch?.invoke()
}
})
}
fun <T> observableToMain(): ObservableTransformer<T, T> {
return ObservableTransformer { upstream ->
upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
fun <T> singleToMain(): SingleTransformer<T, T> {
return SingleTransformer { upstream ->
upstream.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}