文末
那么对于想坚持程序员这行的真的就一点希望都没有吗?
其实不然,在互联网的大浪淘沙之下,留下的永远是最优秀的,我们考虑的不是哪个行业差哪个行业难,就逃避掉这些,无论哪个行业,都会有他的问题,但是无论哪个行业都会有站在最顶端的那群人。我们要做的就是努力提升自己,让自己站在最顶端,学历不够那就去读,知识不够那就去学。人之所以为人,不就是有解决问题的能力吗?挡住自己的由于只有自己。
Android希望=技能+面试
- 技能
- 面试技巧+面试题
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
{"data":{ "appname": "hoolay.apk", "serverVersion": "1.0.2", "serverFlag": "1", "lastForce" : "1", "updateurl": "http://releases.b0.upaiyun.com/hoolay.apk", "upgradeinfo": "V1.0.2版本更新,你想不想要试一下哈!!!" }, "error_code":"200","error_msg" :"蛋疼的认识"}
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
然后我电脑上是装了webstrom的,没有装也没有关系但是必须有nodejs,现在都自带了express,表示并没有学过,所以简单的写个express_demo.js:
var express = require('express'); var app = express(); var fs = require("fs"); //此处设置为get请求,app里面直接写 (本机ip:8081/update) app.get('/update', function (req, res) {//http://127.0.0.1:8081/update fs.readFile( __dirname + "/" + "version.json", 'utf8', function (err, data) {//读取相同目录下的version.json文件 console.log( data );//打印json数据 res.end( data );//把json数据response回去 }); }) var server = app.listen(8081, function () {//端口我这里写的是8081 var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) })
-
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
有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
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Info == 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
学习分享
①「Android面试真题解析大全」PDF完整高清版+②「Android面试知识体系」学习思维导图压缩包
[外链图片转存中…(img-2pHYA0v6-1715592017962)]
[外链图片转存中…(img-hTIbkpkb-1715592017962)]
[外链图片转存中…(img-VmQimHnk-1715592017963)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!