Aria下载器源码分析

Aria下载器源码分析

Aria 中文文档: https://aria.laoyuyu.me/aria_doc/

版本:3.8.15

0x01 注册流程

在Activity的onCreate、fragment的onCreate、java的构造函数中使用Aria.download(this).register()便可以实现注册。

0x0101 Aria类,下载库的统一入口

Aria类仅一个私有的构造方法,无法实例化

1
private Aria() {}

0x0102 Aria类的download方法

Aria类中的2个主要静态方法 download, upload, 对应下载和上传两种类型

以下载方法为例,下载,在当前类中调用Aria方法,参数需要使用this,返回对象是 DownloadReceiver

1
2
3
4
5
6
public static DownloadReceiver download(Object obj) {
if (AriaManager.getInstance() != null) {
return AriaManager.getInstance().download(obj);
}
return get(convertContext(obj)).download(obj);
}
  1. AriaManager是个单例
  2. 首次会走get方法,最终执行AriaManager#init()方法进行初始化
  3. convertContext()方法会判断当前参数obj是否是Context对象,并返回Context
  4. 最后都会执行AriaManager单例对象的download()方法,返回一个DownloadReceiver对象

0x0103 AriaManager初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SuppressLint("StaticFieldLeak") 
private static volatile AriaManager INSTANCE = null;

private AriaManager(Context context) {
APP = context.getApplicationContext();
}

public static AriaManager getInstance() {
return INSTANCE;
}

static AriaManager init(Context context) {
if (INSTANCE == null) {
synchronized (LOCK) {
if (INSTANCE == null) {
INSTANCE = new AriaManager(context);
INSTANCE.initData();
}
}
}
return INSTANCE;
}

private void initData() {
mConfig = AriaConfig.init(APP);
initDb(APP);
regAppLifeCallback(APP);
initAria();
}

  1. init()方法,双空判断加锁实现AriaManager单例
  2. 初始化调用 initData() 方法
  3. 初始化 AriaConfig
  4. 初始化DB
  5. 注册APP生命周期回调,Activity销毁自动移除当前对象的receiver
  6. Aria初始化,异常处理,日志,命令处理器 CommandManager 初始化

0x0104 AriaManager类的download

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private Map<String, AbsReceiver> mReceivers = new ConcurrentHashMap<>();
/**
* 处理下载操作
*/
DownloadReceiver download(Object obj) {
IReceiver receiver = mReceivers.get(getKey(ReceiverType.DOWNLOAD, obj));
if (receiver == null) {
receiver = putReceiver(ReceiverType.DOWNLOAD, obj);
}
return (receiver instanceof DownloadReceiver) ? (DownloadReceiver) receiver : null;
}

private IReceiver putReceiver(ReceiverType type, Object obj) {
final String key = getKey(type, obj);
IReceiver receiver = mReceivers.get(key);

if (receiver == null) {
AbsReceiver absReceiver =
type.equals(ReceiverType.DOWNLOAD) ? new DownloadReceiver(obj) : new UploadReceiver(obj);
mReceivers.put(key, absReceiver);
receiver = absReceiver;
}
return receiver;
}
  1. 调用download方法,根据obj和ReceiverType.DOWNLOAD类型生成key,查询mReceivers是否已经存在当前对象的下载功能接收器DownloadReceiver,存在直接返回
  2. 首次调用,会走到putReceiver方法,新生成一个下载功能接收器DownloadReceiver,并存储在mReceivers中

0x0105 将当前对象注册到Aria

  1. 调用DownloadReceiver#register()方法
  2. 通过DOWNLOAD注解或者实现DownloadTaskListener接口,调用TaskSchedulers.getInstance().register()将当前类注册到Aria

0x0106 TaskSchedulers注册(TODO)

TaskSchedulers 事件调度器,用于处理任务状态的调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private Map<String, Map<TaskEnum, Object>> mObservers = new ConcurrentHashMap<>();
/**
* 将当前类注册到Aria
*
* @param obj 观察者类
* @param taskEnum 任务类型 {@link TaskEnum}
*/
public void register(Object obj, TaskEnum taskEnum) {
String targetName = obj.getClass().getName();
Map<TaskEnum, Object> listeners = mObservers.get(getKey(obj));

if (listeners == null) {
listeners = new ConcurrentHashMap<>();
mObservers.put(getKey(obj), listeners);
}

if (!hasProxyListener(listeners, taskEnum)) {
if (obj instanceof TaskInternalListenerInterface) {
listeners.put(taskEnum, obj);
return;
}
String proxyClassName = targetName + taskEnum.proxySuffix;
ISchedulerListener listener = createListener(proxyClassName);
if (listener != null) {
listener.setListener(obj);
listeners.put(taskEnum, listener);
} else {
ALog.e(TAG, "注册错误,没有【" + proxyClassName + "】观察者");
}
}
}

0x02 下载流程(TODO)

1
2
3
4
long taskId = Aria.download(this)
.load(DOWNLOAD_URL) //读取下载地址
.setFilePath(DOWNLOAD_PATH) //设置文件保存的完整路径
.create(); //启动下载

01 DownloadReceiver.load()
1 HttpBuilderTarget.create()
2 BuilderController.create()
3 CmdHelper.createNormalCmd()
4 NormalCmdFactory.createCmd() -> StartCmd
5 EventMsgUtil.getDefault().post(StartCmd) -> mEventQueue.take()
6 EventMsgUtil.sendEvent()
7 StartCmd.executeCmd() -> AbsNormalCmd.startTask()
8 DTaskQueue.createTask(DTaskWrapper wrapper)
9 TaskWrapperManager.getInstance().putTaskWrapper(wrapper)
10 AbsTaskQueue.startTask()
11 DLoadExecutePool.putTask()
12 AbsTask.start()
13 HttpDLoaderUtil.start() -> AbsNormalLoaderUtil.start()
14 NormalLoader.run() -> AbsNormalLoader.run()
15 AbsNormalLoader.startFlow() -> NormalLoader.handleTask()// 启动单线程任务
16 NormalLoader.startThreadTask()
17 NormalTTBuilder.buildThreadTask()
18 ThreadTaskManager.getInstance().startThread()
19 AbsNormalLoader.startTimer() // 启动进度获取定时器

21 ThreadTask.call() // 线程池执行任务回调
22 AbsThreadTaskAdapter.call()
23 HttpDThreadTaskAdapter.handlerThreadTask() // 正式建立Http连接,下载任务
24 HttpDThreadTaskAdapter.readNormal()

25 HttpDThreadTaskAdapter.handleComplete()
26 ThreadTask.updateCompleteState()
27 NormalThreadStateManager.callback -> STATE_COMPLETE
28 BaseListener.onComplete() // 对应的实体类是BaseDListener
29 BaseListener.sendInState2Target() // 将任务状态发送给下载器

0x03 完成事件逆行分析

  1. IDLoadListener.onComplete()
  2. NormalLoader.addComponent(IRecordHandler recordHandler) -> ILoaderVisitor.addComponent(IRecordHandler recordHandler) // 处理任务记录
  3. RecordHandler.checkTaskCompleted() // 检查任务是否已完成
  4. 遍历TaskRecord中所有的ThreadRecord.isComplete就认为下载完成

0x04 M3U8文件下载过程

M3U8ThreadTaskAdapter.readDynamicFile(InputStream is) // 动态长度文件读取方式
M3U8ThreadTaskAdapter.handleComplete() // 处理完成
ThreadTask.updateCompleteState() // 组装Message消息,发送给VodStateManager.callback -> Handler.Callback
VodStateManager.callback -> STATE_COMPLETE
BaseListener.onComplete() // 对应的实体类是M3U8Listener

RecyclerView滑动到指定Item并置顶

RecyclerView滑动到指定Item并置顶

0x01 TopLinearSmoothScroller

1
2
3
4
5
6
7
8
9
10
11
12
import android.content.Context
import androidx.recyclerview.widget.LinearSmoothScroller

class TopLinearSmoothScroller(context: Context?) : LinearSmoothScroller(context) {
public override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}

override fun getHorizontalSnapPreference(): Int {
return SNAP_TO_START
}
}

0x02 TopScrollLinearLayoutManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class TopScrollLinearLayoutManager(context: Context?, orientation: Int, reverseLayout: Boolean) :
LinearLayoutManager(context, orientation, reverseLayout) {

override fun smoothScrollToPosition(
recyclerView: RecyclerView?,
state: RecyclerView.State?,
position: Int
) {
val linearSmoothScroller = TopLinearSmoothScroller(recyclerView?.context)
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}
}

0x03 RecyclerView中使用

设置recyclerView的layoutManager为自定义的TopScrollLinearLayoutManager,然后直接调用 smoothScrollToPosition() 方法就可以滚动到指定的位置并且置顶了。

1
2
3
4
5
6
7
recyclerView.layoutManager = TopScrollLinearLayoutManager(
this,
LinearLayoutManager.HORIZONTAL,
false
)
// ...
recyclerView.smoothScrollToPosition(1)

ExoPlayer简易播放器

ExoPlayer简易播放器

一个简单的基于ExoPlayer的播放器。
ExoPlayer官网:https://exoplayer.dev/
使用ExoPlayer版本:2.18.2

实现功能:通过url播放视频,简易自定义controller,监听播放器状态变化,首帧时间打印,错误信息打印。

如果需要深层次的UI定制,建议不要用exo_ui库下面的布局,用SurfaceView和TextureView完全自定义。具体代码如下:

1.播放器Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Player.Listener
import com.google.android.exoplayer2.ui.StyledPlayerView

class ExoPlayerFragment : Fragment() {

companion object {
fun newInstance(url: String): ExoPlayerFragment {
val args = Bundle()
args.putString(EXO_URI, url)
val fragment = ExoPlayerFragment()
fragment.arguments = args
return fragment
}

private const val TAG = "ExoPlayerFragment"
private const val EXO_URI = "EXO_URI"
}

private var videoUri: String? = null
private var player: ExoPlayer? = null

private val listener = object : Listener {

override fun onEvents(player: Player, events: Player.Events) {
super.onEvents(player, events)
Log.d(TAG, "onEvents->${events}:")
}

override fun onPlaybackStateChanged(playbackState: Int) {
val stateString: String = when (playbackState) {
ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE"
ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING"
ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY"
ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED"
else -> "UNKNOWN_STATE"
}
Log.d(TAG, "onPlaybackStateChanged: state=$stateString")
super.onPlaybackStateChanged(playbackState)
printPlayerTimeLine("onPlaybackStateChanged:")
}

override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
printPlayerTimeLine("onRenderedFirstFrame:")
}

override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
printPlayerTimeLine("onPlayerError:")
error.printStackTrace()
}
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
videoUri = arguments?.getString(EXO_URI)
val root = inflater.inflate(R.layout.exo_player_fragment, container, false)
val playerView = root.findViewById<StyledPlayerView>(R.id.styled_player_view)
initPlayerView(playerView)
return root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startPlay()
}

private fun initPlayerView(playerView: StyledPlayerView) {
Log.d(TAG, "initPlayerView: ")
player = ExoPlayer.Builder(requireContext()).build()
player?.addListener(listener)
// Bind the player to the view.
playerView.player = player

// back
playerView.findViewById<View>(R.id.back).setOnClickListener {
fragmentManager?.popBackStack()
val ft = fragmentManager?.beginTransaction()
ft?.remove(this@ExoPlayerFragment)
ft?.commitAllowingStateLoss()
}
}

private var prepareTime: Long = 0L
private fun printPlayerTimeLine(method: String) {
val c = System.currentTimeMillis()
val duration = if (prepareTime == 0L) 0 else c - prepareTime
prepareTime = c
Log.i(TAG, "printPlayerTimeLine: method=$method, duration=$duration, uri=${videoUri}")
}

private fun startPlay() {
Log.d(TAG, "startPlay: ")
videoUri?.let {
// Build the media item.
val mediaItem: MediaItem = MediaItem.fromUri(it)

// Set the media item to be played.
player?.setMediaItem(mediaItem)

// Prepare the player.
player?.prepare()
printPlayerTimeLine("player->prepare:")

// Start the playback.
player?.play()
// calPlayerTimeLine("player->play:")
}
}

override fun onDestroy() {
player?.release()
super.onDestroy()
}
}

2.播放器布局

exo_player_fragment.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="utf-8"?>

<FrameLayout 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:background="@color/black"
android:clickable="true">

<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/styled_player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
app:animation_enabled="false"
app:controller_layout_id="@layout/custom_player_control_view"
app:use_controller="true" />

</FrameLayout>

3.自定义的controller布局

custom_player_control_view.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:background="#00000000"
android:layoutDirection="ltr"
android:orientation="vertical"
tools:targetApi="28">

<ImageView
android:id="@+id/back"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:background="#33000000"
android:scaleType="centerInside"
android:src="@mipmap/back"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<LinearLayout
android:id="@id/exo_bottom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#33000000"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingTop="4dp"
android:paddingBottom="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">


<ImageButton
android:id="@id/exo_prev"
style="@style/ExoStyledControls.Button.Center.Previous"
android:padding="8dp" />

<ImageButton
android:id="@id/exo_play_pause"
style="@style/ExoStyledControls.Button.Center.PlayPause"
android:padding="8dp" />

<ImageButton
android:id="@id/exo_next"
style="@style/ExoStyledControls.Button.Center.Next"
android:padding="8dp" />

<TextView
android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />

<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_height="26dp"
android:layout_weight="1" />

<TextView
android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:textColor="#FFBEBEBE"
android:textSize="14sp"
android:textStyle="bold" />

</LinearLayout>


</androidx.constraintlayout.widget.ConstraintLayout>

SystemBarUtil 工具类

SystemBarUtil 工具类

工具类,提供了系统栏高度和屏幕宽高获取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

import android.app.Activity
import android.content.Context
import android.graphics.Point
import android.os.Build
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager

/**
* 工具类,提供了系统栏高度和屏幕宽高获取方法
*/
object SystemBarUtil {

/**
* 获取状态栏高度
*/
@JvmStatic
fun getStatusBarHeight(context: Context): Int {
var height = 0
try {
val resourceId = context.applicationContext.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
if (resourceId > 0) {
height =
context.applicationContext.resources.getDimensionPixelSize(resourceId)
}
} catch (e: Exception) {
}
return height
}

/**
* 获取系统导航栏高度
*/
@JvmStatic
fun getNavigationBarHeight(context: Context): Int {
var height = 0
try {
val resourceId = context.applicationContext.resources.getIdentifier(
"navigation_bar_height",
"dimen",
"android"
)
if (resourceId > 0) {
height =
context.applicationContext.resources.getDimensionPixelSize(resourceId)
}
} catch (e: Exception) {
}
return height
}

private const val NAVIGATION = "navigationBarBackground"

// 该方法需要在View完全被绘制出来之后调用
@JvmStatic
private fun isNavigationBarVisible(activity: Activity): Boolean {
val vp = activity.window.decorView as ViewGroup?
if (vp != null) {
for (i in 0 until vp.childCount) {
vp.getChildAt(i).context.packageName
if (vp.getChildAt(i).id !== View.NO_ID &&
NAVIGATION == activity.resources.getResourceEntryName(vp.getChildAt(i).id)
) {
return true
}
}
}
return false
}

/**
* 获取屏幕的物理大小 px
*/
@JvmStatic
fun getDeviceScreenSize(context: Context): Point {
val appContext = context.applicationContext
val wm = appContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val point = Point(0, 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
wm.defaultDisplay.getRealSize(point)
} else {
wm.defaultDisplay.getSize(point)
}
return point
}

/**
* 获取显示屏幕的宽高 px
*/
@JvmStatic
fun getDisplaySize(context: Context): Point {
val point = Point(0, 0)
val dm = context.applicationContext.resources.displayMetrics
point.x = dm.widthPixels
point.y = dm.heightPixels
return point
}
}

Android 13 监控网络连接状态

Android 13 监控网络连接状态

获取瞬时状态

1
2
3
4
5
6
val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val currentNetwork = cm.activeNetwork
if (currentNetwork != null) {
val caps = cm.getNetworkCapabilities(currentNetwork)
val linkProperties = cm.getLinkProperties(currentNetwork)
}

监听网络事件

NetworkCallback 类与 ConnectivityManager.registerDefaultNetworkCallback(NetworkCallback)ConnectivityManager.registerNetworkCallback(NetworkCallback) 结合使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
val cm = context.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
cm.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
Log.e(TAG, "The default network is now: " + network)
}

override fun onLost(network: Network) {
Log.e(TAG, "The application no longer has a default network. The last default network was " + network)
handle(null)
}

override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
Log.d(TAG, "The default network changed capabilities: " + networkCapabilities)
handle(networkCapabilities)
}

override fun onLinkPropertiesChanged(network: Network, linkProperties: LinkProperties) {
Log.i(TAG, "The default network changed link properties: " + linkProperties)
}
})

解析NetworkCapabilities的网络状态信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 private fun handle(caps: NetworkCapabilities?) {
if (caps != null) {
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
if (
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
) {
setResult(STATE_WIFI)
return
} else if (
caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
) {
setResult(STATE_MOBILE)
return
}
}
}
setResult(STATE_UNKNOWN)
return
}

APK签名之jarsigner签名工具

APK签名之jarsigner签名工具

使用JDK签名工具jarsigner签名APK文件 jarsigner -verbose -keystore [签名文件路径] -signedjar [签名后的apk文件路径] [未签名的apk文件路径] [证书别名]

20230110180748

1
jarsigner -verbose -keystore D:\xxx\xxx.jks -signedjar D:\xxx\xxx_signed.apk D:\xxx\***.apk keyAlias
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×