-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
有webstrom的直接选中文件run就ok了,没有直接 node express_demo.js,可以直接浏览器打开:http://127.0.0.1:8081/update
- 效果如下:
上图为打开浏览器后的显示结果。
上图为webstrom的终端显示结果。
客户端需要实现:
我们知道不同的需求有不同的操作方法和界面显示:
- 从是否为app内部下载还是通知栏更新:
- app内下载更新
这时我们必须等下载安装完全后才能进行操作,效果是这样的:
- 通知栏下载更新
这种情况是不在应用内更新,放在通知栏并不会影响当前app的使用,效果是这样的:
- app更新分3种:强制更新,推荐更新,无需更新
- 强制更新
- 推荐更新
- 无需更新
具体思路:
- 实现bean用于对接后端接口实现app的更新(不写网络请求模拟本地数据也需要这个模型)
- 使用retrofit来请求版本更新接口
- 下载apk我们分别使用DownloadManager和普通的httpurlconnection
- 通过BroadcastReceiver来监听是否下载完成
准备bean
首先我们要去解析服务端给的json,那么我们就要来创建一个bean类了,这里是严格根据json文件的格式来的:
package com.losileeya.appupdate.bean; /** * User: Losileeya (847457332@qq.com) * Date: 2016-09-27 * Time: 11:20 * 类描述:版本更新的实体与你服务器的字段相匹配 * @version : */ public class UpdateAppInfo { public UpdateInfo data; // 信息 public Integer error_code; // 错误代码 public String error_msg; // 错误信息 public static class UpdateInfo{ // app名字 public String appname; //服务器版本 public String serverVersion; //服务器标志 public String serverFlag; //强制升级 public String lastForce; //app最新版本地址 public String updateurl; //升级信息 public String upgradeinfo; get... set... } get... set... }
-
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
-
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
网络接口的实现
这里使用retrofit和rxjava来练笔
先加入 依赖
compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid compile 'io.reactivex:rxjava:1.1.0' // 推荐同时加载RxJava compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' // Retrofit网络处理 compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' // Retrofit的rx解析库 compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' // Retrofit的gson库
-
1
-
2
-
3
-
4
-
5
-
6
-
1
-
2
-
3
-
4
-
5
-
6
接下来网络接口的定制:
public interface ApiService { //实际开发过程可能的接口方式 @GET("update") Observable<UpdateAppInfo> getUpdateInfo(@Query("appname") String appname, @Query("serverVersion") String appVersion); //以下方便版本更新接口测试 @GET("update") Observable<UpdateAppInfo> getUpdateInfo(); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
通过工厂模式来创建ApiService :
public class ServiceFactory { private static final String BASEURL="http://192.168.191.1:8081/"; public static <T> T createServiceFrom(final Class<T> serviceClass) { Retrofit adapter = new Retrofit.Builder() .baseUrl(BASEURL) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 添加Rx适配器 .addConverterFactory(GsonConverterFactory.create()) // 添加Gson转换器 .build(); return adapter.create(serviceClass); } }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
版本检测接口的使用:
/** * 检查更新 */ @SuppressWarnings("unused") public static void checkUpdate(String appCode, String curVersion,final CheckCallBack updateCallback) { ApiService apiService= ServiceFactory.createServiceFrom(ApiService.class); apiService.getUpdateInfo()//测试使用 // .apiService.getUpdateInfo(appCode, curVersion)//开发过程中可能使用的 .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Subscriber<UpdateAppInfo>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(UpdateAppInfo updateAppInfo) { if (updateAppInfo.error_code == 0 || updateAppInfo.data == null || updateAppInfo.data.updateurl == null) { updateCallback.onError(); // 失败 } else { updateCallback.onSuccess(updateAppInfo); } } }); }
-
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
-
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
以上就是版本更新接口的调用,具体的rxjava+retrofit请自行学习你真的会用Retrofit2吗?Retrofit2完全教程
附上结果回调监听:
public interface CheckCallBack{//检测成功或者失败的相关接口 void onSuccess(UpdateAppInfo updateInfo); void onError(); }
-
1
-
2
-
3
-
4
-
1
-
2
-
3
-
4
具体使用接口的处理:
//网络检查版本是否需要更新 CheckUpdateUtils.checkUpdate("apk", "1.0.0", new CheckUpdateUtils.CheckCallBack() { @Override public void onSuccess(UpdateAppInfo updateInfo) { String isForce=updateInfo.data.getLastForce();//是否需要强制更新 String downUrl= updateInfo.data.getUpdateurl();//apk下载地址 String updateinfo = updateInfo.data.getUpgradeinfo();//apk更新详情 String appName = updateInfo.data.getAppname(); if(isForce.equals("1")&& !TextUtils.isEmpty(updateinfo)){//强制更新 forceUpdate(MainActivity.this,appName,downUrl,updateinfo); }else{//非强制更新 //正常升级 normalUpdate(MainActivity.this,appName,downUrl,updateinfo); } } @Override public void onError() { noneUpdate(MainActivity.this); } });
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
实在不想写网络也好,直接使用假想数据做相关操作如下:
UpdateAppInfo.UpdateInfo info =new UpdateAppInfo.UpdateInfo(); info.setLastForce("1"); info.setAppname("我日你"); info.setUpgradeinfo("whejjefjhrherkjreghgrjrgjjhrh"); info.setUpdateurl("http://releases.b0.upaiyun.com/hoolay.apk"); if(info.getLastForce().equals("1")){//强制更新 forceUpdate(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo()); }else{//非强制更新 //正常升级 normalUpdate(MainActivity.this,info.getAppname(),info.getUpdateurl(),info.getUpgradeinfo()); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
更新dialog的使用注意:
private void forceUpdate(final Context context, final String appName, final String downUrl, final String updateinfo) { mDialog = new AlertDialog.Builder(context); mDialog.setTitle(appName+"又更新咯!"); mDialog.setMessage(updateinfo); mDialog.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (!canDownloadState()) { showDownloadSetting(); return; } // DownLoadApk.download(MainActivity.this,downUrl,updateinfo,appName); AppInnerDownLoder.downLoadApk(MainActivity.this,downUrl,appName); } }).setCancelable(false).create().show(); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
上面以强制更新举个例子,因为AlertDialog在不同的版本下面表现的美观度不一致,所以我们需要
import android.support.v7.app.AlertDialog;
- 1
- 1
然后显然是不能按返回键取消的,我们需要
.setCancelable(false)
- 1
- 1
使用谷歌推荐的DownloadManager实现下载
Android自带的DownloadManager模块来下载,在api level 9之后,我们通过通知栏知道, 该模块属于系统自带, 它已经帮我们处理了下载失败、重新下载等功能。整个下载 过程全部交给系统负责,不需要我们过多的处理。
DownLoadManager.Query:主要用于查询下载信息。
DownLoadManager.Request:主要用于发起一个下载请求。
先看下简单的实现:
创建Request对象的代码如下:
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(apkurl)); //设置在什么网络情况下进行下载 request.setAllowedNetworkTypes(Request.NETWORK_WIFI); //设置通知栏标题 request.setNotificationVisibility(Request.VISIBILITY_VISIBLE); request.setTitle("下载"); request.setDescription("apk正在下载"); request.setAllowedOverRoaming(false); //设置文件存放目录 request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "mydown");
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
取得系统服务后,调用downloadmanager对象的enqueue方法进行下载,此方法返回一个编号用于标示此下载任务:
downManager = (DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE); id= downManager.enqueue(request);
-
1
-
2
-
1
-
2
这里我们可以看下request的一些属性:
addRequestHeader(String header,String value):添加网络下载请求的http头信息 allowScanningByMediaScanner():用于设置是否允许本MediaScanner扫描。 setAllowedNetworkTypes(int flags):设置用于下载时的网络类型,默认任何网络都可以下载,提供的网络常量有:NETWORK_BLUETOOTH、NETWORK_MOBILE、NETWORK_WIFI。 setAllowedOverRoaming(Boolean allowed):用于设置漫游状态下是否可以下载 setNotificationVisibility(int visibility):用于设置下载时时候在状态栏显示通知信息 setTitle(CharSequence):设置Notification的title信息 setDescription(CharSequence):设置Notification的message信息 setDestinationInExternalFilesDir、setDestinationInExternalPublicDir、 setDestinationUri等方法用于设置下载文件的存放路径,注意如果将下载文件存放在默认路径,那么在空间不足的情况下系统会将文件删除,所 以使用上述方法设置文件存放目录是十分必要的。
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
具体实现思路:
- 我们通过downloaderManager来下载apk,并且本地保存downManager.enqueue(request)返回的id值,并且通过这个id获取apk的下载文件路径和下载的状态,并且通过状态来更新通知栏的显示。
- 第一次下载成功,弹出安装界面
如果用户没有点击安装,而是按了返回键,在某个时候,又再次使用了我们的APP
如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本,如果都满足则直接启动安装程序。
具体代码实现:
文件下载管理的实现,包括创建request和加入队列下载,通过返回的id来获取下载路径和下载状态。
public class FileDownloadManager { private DownloadManager downloadManager; private Context context; private static FileDownloadManager instance; private FileDownloadManager(Context context) { downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); this.context = context.getApplicationContext(); } public static FileDownloadManager getInstance(Context context) { if (instance == null) { instance = new FileDownloadManager(context); } return instance; } /** * @param uri * @param title * @param description * @return download id */ public long startDownload(String uri, String title, String description,String appName) { DownloadManager.Request req = new DownloadManager.Request(Uri.parse(uri)); req.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); //req.setAllowedOverRoaming(false); req.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); //设置文件的保存的位置[三种方式] //第一种 //file:///storage/emulated/0/Android/data/your-package/files/Download/update.apk req.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, appName+".apk"); //第二种 //file:///storage/emulated/0/Download/update.apk //req.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "update.apk"); //第三种 自定义文件路径 //req.setDestinationUri() // 设置一些基本显示信息 req.setTitle(title); req.setDescription(description); //req.setMimeType("application/vnd.android.package-archive"); return downloadManager.enqueue(req);//异步 //dm.openDownloadedFile() } /** * 获取文件保存的路径 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @return file path * @see FileDownloadManager#getDownloadUri(long) */ public String getDownloadPath(long downloadId) { DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = downloadManager.query(query); if (c != null) { try { if (c.moveToFirst()) { return c.getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI)); } } finally { c.close(); } } return null; } /** * 获取保存文件的地址 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @see FileDownloadManager#getDownloadPath(long) */ public Uri getDownloadUri(long downloadId) { return downloadManager.getUriForDownloadedFile(downloadId); } public DownloadManager getDownloadManager() { return downloadManager; } /** * 获取下载状态 * * @param downloadId an ID for the download, unique across the system. * This ID is used to make future calls related to this download. * @return int * @see DownloadManager#STATUS_PENDING * @see DownloadManager#STATUS_PAUSED * @see DownloadManager#STATUS_RUNNING * @see DownloadManager#STATUS_SUCCESSFUL * @see DownloadManager#STATUS_FAILED */ public int getDownloadStatus(long downloadId) { DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = downloadManager.query(query); if (c != null) { try { if (c.moveToFirst()) { return c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); } } finally { c.close(); } } return -1; } }
-
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
-
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
app的检测安装的实现:
public class DownLoadApk { public static final String TAG = DownLoadApk.class.getSimpleName(); public static void download(Context context, String url, String title,final String appName) { // 获取存储ID SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); long downloadId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L); if (downloadId != -1L) { FileDownloadManager fdm = FileDownloadManager.getInstance(context); int status = fdm.getDownloadStatus(downloadId); if (status == DownloadManager.STATUS_SUCCESSFUL) { //启动更新界面 Uri uri = fdm.getDownloadUri(downloadId); if (uri != null) { if (compare(getApkInfo(context, uri.getPath()), context)) { startInstall(context, uri); return; } else { fdm.getDownloadManager().remove(downloadId); } } start(context, url, title,appName); } else if (status == DownloadManager.STATUS_FAILED) { start(context, url, title,appName); } else { Log.d(TAG, "apk is already downloading"); } } else { start(context, url, title,appName); } } private static void start(Context context, String url, String title,String appName) { long id = FileDownloadManager.getInstance(context).startDownload(url, title, "下载完成后点击打开",appName); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); sp.edit().putLong(DownloadManager.EXTRA_DOWNLOAD_ID,id).commit(); Log.d(TAG, "apk start download " + id); } public static void startInstall(Context context, Uri uri) { Intent install = new Intent(Intent.ACTION_VIEW); install.setDataAndType(uri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); } /** * 获取apk程序信息[packageName,versionName...] * * @param context Context * @param path apk path */ private static PackageInfo getApkInfo(Context context, String path) { PackageManager pm = context.getPackageManager(); PackageInfo info = pm.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES); if (info != null) { return info; } return null; } /** * 下载的apk和当前程序版本比较 * * @param apkInfo apk file's packageInfo * @param context Context * @return 如果当前应用版本小于apk的版本则返回true */ private static boolean compare(PackageInfo apkInfo, Context context) { if (apkInfo == null) { return false; } String localPackage = context.getPackageName(); if (apkInfo.packageName.equals(localPackage)) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(localPackage, 0); if (apkInfo.versionCode > packageInfo.versionCode) { return true; } } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } return false; } }
-
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
-
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
上面的代码可知:我们通过获取当前app的信息来比较是否需要下载和是否立即安装。第一次下载把downloadId保存到本地,用户下次进来的时候,取出保存的downloadId,然后通过downloadId来获取下载的状态信息。如果下载失败,则重新下载并且把downloadId存起来。如果下载成功,则判断本地的apk的包名是否和当前程序是相同的,并且本地apk的版本号大于当前程序的版本
,如果都满足则直接启动安装程序。
监听app是否安装完成
public class ApkInstallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)){ long downloadApkId =intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); installApk(context, downloadApkId); } } /** * 安装apk */ private void installApk(Context context,long downloadApkId) { // 获取存储ID SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); long downId =sp.getLong(DownloadManager.EXTRA_DOWNLOAD_ID,-1L); if(downloadApkId == downId){ DownloadManager downManager= (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); Uri downloadFileUri = downManager.getUriForDownloadedFile(downloadApkId); if (downloadFileUri != null) { Intent install= new Intent(Intent.ACTION_VIEW); install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive"); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); }else{ Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show(); } } } }
-
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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/8e9d580f5d466e04ee53bbb2c1295083.jpeg)
文末
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
DownloadedFile(downloadApkId); if (downloadFileUri != null) { Intent install= new Intent(Intent.ACTION_VIEW); install.setDataAndType(downloadFileUri, “application/vnd.android.package-archive”); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(install); }else{ Toast.makeText(context, “下载失败”, Toast.LENGTH_SHORT).show(); } } } }`
-
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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-7uN75h54-1712553544738)]
[外链图片转存中…(img-NiyjfUxV-1712553544739)]
[外链图片转存中…(img-5WcVRt6F-1712553544739)]
[外链图片转存中…(img-bnlu5L63-1712553544739)]
[外链图片转存中…(img-2WBFkpZV-1712553544740)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/8e9d580f5d466e04ee53bbb2c1295083.jpeg)
文末
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
[外链图片转存中…(img-dlw88lwV-1712553544740)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-RGvQIs72-1712553544740)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!