Android入门笔记13

it2025-03-02  25

开发手机安全卫士

此篇笔记记录了android闪屏页面的开发和主页面(静态,功能还未实现)的开发。主要记录闪屏界面中的版本检测和更新。

代码组织结构

根据业务逻辑划分

办公软件

出差 com.itheima.travel工资 com.itheima.money会议 com.itheima.meeting

网盘

上传 com.vdisk.upload下载 com.vdisk.download分享 com.vdisk.share

根据功能模块划分(Android开发推荐此方法)

Activity com.itheima.mobilesafe.activty后台服务 com.itheima.mobilesafe.service广播接受者 com.itheima.mobilesafe.receiver数据库 com.itheima.mobilesafe.db.dao对象(java bean) com.itheima.mobilesafe.domain/bean自定义控件 com.itheima.mobilesafe.view工具类 com.itheima.mobilesafe.utils业务逻辑 com.itheima.mobilesafe.engine

项目创建

minimum SDK 要求最低的安装版本, 安装apk前,系统会判断当前版本是否高于(包含)此版本, 是的话才允许安装

maxSdkVersion 要求最高的安装版本(一般不用)

Target SDK 目标SDK, 一般设置为开发时使用的手机版本, 这样的话,系统在运行我的apk时,就认为我已经在该做了充分的测试, 系统就不会做过多的兼容性判断, 从而提高运行效率

Compile With 编译程序时使用的版本

闪屏页面(Splash)

展示logo,公司品牌项目初始化检测版本更新校验程序合法性(比如:判断是否有网络,有的话才运行)

签名冲突

如果两个应用程序, 包名相同, 但是签名不同, 就无法覆盖安装

正式签名

1. 有效期比较长,一般大于25年 2. 需要设置密码 3. 正式发布应用时,必须用正式签名来打包

测试签名(debug.keystore)

1. 有效期是1年,很短 2. 有默认的别名,密码, alias=android, 密码是androiddebugkey 3. 在eclipse中直接运行项目是,系统默认采用此签名文件

如果正式签名丢失了怎么办?

1. 修改包名, 发布, 会发现有两个手机卫士, 用户会比较纠结 2. 请用户先删掉原来的版本,再进行安装, 用户会流失 3. 作为一名有经验的开发人员,请不要犯这种低级错误

子类和父类

子类拥有父类的所有方法, 而且可以有更多自己的方法

Activity(token), Context(没有token) 平时,要获取context对象的话, 优先选择Activity, 避免bug出现, 尽量不用getApplicationContext()

项目结构

app构建文件

apply plugin: 'com.android.application' android { compileSdkVersion 29 defaultConfig { applicationId "com.example.administrator.myapplication" minSdkVersion 15 targetSdkVersion 16 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) // implementation 'com.android.support:appcompat-v7:29.+' implementation 'com.android.support:appcompat-v7:+' implementation 'com.android.support.constraint:constraint-layout:2.0.1' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' }

主要文件

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.administrator.myapplication" android:versionCode="1" android:versionName="1.0"> <uses-permission android:name="android.permission.CALL_PHONE"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <uses-library android:name="android.test.runner"/> <activity android:name=".activity.SplashActivity" android:label="主界面" android:icon="@mipmap/chrome"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:name=".activity.HomeActivity"/> </application> </manifest>

activity_splash.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/launcher_bg" > <TextView android:text="TextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv_version" android:layout_weight="1" android:layout_centerHorizontal="true" android:layout_alignParentBottom="true" android:textSize="16sp" android:textColor="#000" android:shadowColor="#f00" android:shadowDx="1" android:shadowDy="1" android:shadowRadius="1" android:layout_marginBottom="116dp" tools:text="版本号1.0"/> <ProgressBar style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/progressBar" android:layout_centerInParent="true"/> <TextView android:text="下载进度" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:textColor="#f00" android:layout_marginLeft="20dp" android:textSize="16sp" android:visibility="gone" android:id="@+id/tv_progress"/> </RelativeLayout>

home_list_item.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/iv_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/home_apps" /> <TextView android:id="@+id/tv_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:textColor="@color/black" android:textSize="18sp" android:text="手机防盗" /> </LinearLayout>

StreamUtils.java

package com.example.administrator.myapplication.util; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; /** * 读取流数据 */ public class StreamUtils { /** * 读取输入流返回String * @param in * @return */ public static String readFromStream(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); int len = 0; byte [] buffer = new byte[1024]; while ((len=in.read(buffer))!=-1){ out.write(buffer,0,len); } String result = out.toString(); in.close(); out.close(); return result; } }

SplashActivity.java

package com.example.administrator.myapplication.activity; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.view.View; import android.widget.TextView; import android.widget.Toast; import com.example.administrator.myapplication.R; import com.example.administrator.myapplication.util.StreamUtils; import com.lidroid.xutils.HttpUtils; import com.lidroid.xutils.exception.HttpException; import com.lidroid.xutils.http.ResponseInfo; import com.lidroid.xutils.http.callback.RequestCallBack; import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class SplashActivity extends Activity { private static final int CODE_UPDATE_DIALOG = 0; private static final int CODE_URL_ERROR = 1; private static final int CODE_NET_ERROR = 2; private static final int CODE_JSON_ERROR = 3; private static final int CODE_ENTER_HOME = 4; private TextView tvVserion; private TextView tvProgress; //下载进度展示 private String mVersionName; //版本名 private int mVersionCode; //版本号 private String mDesc;//版本描述 private String mDownloadUrl; //下载地址 private Handler mHandler = new Handler(){ @Override public void handleMessage( Message msg) { switch (msg.what){ case CODE_UPDATE_DIALOG: showUpdateDialog(); break; case CODE_URL_ERROR: Toast.makeText(SplashActivity.this,"下载地址错误",Toast.LENGTH_SHORT).show(); enterHome(); break; case CODE_NET_ERROR: Toast.makeText(SplashActivity.this,"网络错误",Toast.LENGTH_SHORT).show(); enterHome(); break; case CODE_JSON_ERROR: Toast.makeText(SplashActivity.this,"数据解析错误",Toast.LENGTH_SHORT).show(); enterHome(); break; case CODE_ENTER_HOME: enterHome(); } }; }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { setContentView(R.layout.activity_splash); super.onCreate(savedInstanceState); tvVserion = findViewById(R.id.tv_version); tvVserion.setText("版本号:"+getVersionName()); tvProgress = findViewById(R.id.tv_progress); //默认隐藏 checkVersion(); } private String getVersionName(){ PackageManager packageManager = getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0); return packageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return ""; } /** * 获取本地app 的versioncode * @return */ private int getVersionCode(){ PackageManager packageManager = getPackageManager(); try { PackageInfo packageInfo = packageManager.getPackageInfo(getPackageName(), 0); return packageInfo.versionCode; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return -1; } /** * 从服务器获取版本信息进行校验 */ public void checkVersion(){ final long startTime = System.currentTimeMillis(); //启动子线程加载数据 new Thread(){ @Override public void run(){ Message msg = Message.obtain(); HttpURLConnection conn = null; try { URL url = new URL("http://192.168.1.210:8080/update.json"); conn = (HttpURLConnection)url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000);//设置连接超时 conn.setReadTimeout(5000);//设置响应超时 conn.connect(); int responseCode = conn.getResponseCode();//获取响应码 if(responseCode==200){ InputStream inputStream = conn.getInputStream(); String result = StreamUtils.readFromStream(inputStream); System.out.println("网络返回:"+result); //解析json JSONObject json = new JSONObject(result); mVersionName = json.getString("versionName"); mVersionCode = json.getInt("versionCode"); mDesc = json.getString("description"); mDownloadUrl = json.getString("downloadUrl"); if(mVersionCode>getVersionCode()){ //服务器versionCode大于本地versionCode //弹出升级对话框 msg.what = CODE_UPDATE_DIALOG; }else { msg.what = CODE_ENTER_HOME; } } } catch (MalformedURLException e) { msg.what = CODE_URL_ERROR; //url 错误异常 e.printStackTrace(); }catch (IOException e){ msg.what = CODE_NET_ERROR; //网络错误异常 e.printStackTrace(); } catch (JSONException e) { msg.what = CODE_JSON_ERROR; e.printStackTrace(); }finally { long endTime =System.currentTimeMillis(); long timeUsed = startTime - endTime; //这里就不是很人性化了,本来秒进系统的,你要给人家阻塞,就为了让闪屏页面至少展示2秒 if(timeUsed<2000){ try { Thread.sleep(2000-timeUsed); } catch (InterruptedException e) { e.printStackTrace(); } } mHandler.sendMessage(msg); if(conn!=null){ conn.disconnect(); } } } }.start(); } private void showUpdateDialog(){ AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("检测到最新版本:"+mVersionName); builder.setMessage(mDesc); builder.setPositiveButton("立即更新", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { System.out.println("立即更新"); download(); } }); builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { enterHome(); } }); //设置取消监听,点击返回调到主界面 builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { enterHome(); } }); builder.show(); } /** * 进入主页面 */ private void enterHome(){ Intent intent = new Intent(this,HomeActivity.class); startActivity(intent); finish(); } /** * 下载apk文件 */ private void download(){ //判断SD卡有没有挂在,至少要有这个概念,会不会不要紧可以去网上搜 if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ tvProgress.setVisibility(View.VISIBLE);//显示下载进度 String target = Environment.getExternalStorageDirectory()+"/update.apk"; HttpUtils utils = new HttpUtils(); utils.download("http://192.168.1.210:8080/app-release.apk", target, new RequestCallBack<File>() { //文件的下载情况 @Override public void onLoading(long total, long current, boolean isUploading) { super.onLoading(total, current, isUploading); System.out.println("下载进度:"+current+"/"+total); tvProgress.setText("下载进度:"+current*100/total+"%"); } //下载成功 @Override public void onSuccess(ResponseInfo<File> responseInfo) { System.out.println("下载成功"); //跳转到安装页面 Intent intent = new Intent(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setDataAndType(Uri.fromFile(responseInfo.result),"application/vnd.android.package-archive"); //startActivity(intent); startActivityForResult(intent,0);//如果用户取消安装会返回结果,回调方法onActivityResult } //下载失败 @Override public void onFailure(HttpException e, String s) { Toast.makeText(SplashActivity.this,"下载失败!",Toast.LENGTH_SHORT).show(); } }); }else { Toast.makeText(SplashActivity.this,"没有找到SD卡",Toast.LENGTH_SHORT); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { enterHome(); super.onActivityResult(requestCode, resultCode, data); } }

HomeActivity.java

package com.example.administrator.myapplication.activity; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; import com.example.administrator.myapplication.R; /** * 主页面 * * @author Kevin * */ public class HomeActivity extends Activity { private GridView gvHome; private String[] mItems = new String[] { "手机防盗", "通讯卫士", "软件管理", "进程管理", "流量统计", "手机杀毒", "缓存清理", "高级工具", "设置中心" }; private int[] mPics = new int[] { R.drawable.home_safe, R.drawable.home_callmsgsafe, R.drawable.home_apps, R.drawable.home_taskmanager, R.drawable.home_netmanager, R.drawable.home_trojan, R.drawable.home_sysoptimize, R.drawable.home_tools, R.drawable.home_settings }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); gvHome = (GridView) findViewById(R.id.gv_home); gvHome.setAdapter(new HomeAdapter()); } class HomeAdapter extends BaseAdapter { @Override public int getCount() { return mItems.length; } @Override public Object getItem(int position) { return mItems[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = View.inflate(HomeActivity.this, R.layout.home_list_item, null); ImageView ivItem = (ImageView) view.findViewById(R.id.iv_item); TextView tvItem = (TextView) view.findViewById(R.id.tv_item); tvItem.setText(mItems[position]); ivItem.setImageResource(mPics[position]); return view; } } }

此外需要在tomcat的webapp/ROOT/下放一个正式的apk文件,用于版本更新时下载。 和一个app.json,用于得到服务器的版本信息 app.json

{"versionName": "2.0", "versionCode": 2, "description": "新增NB功能,赶紧体验!!!", "downloadUrl": "http://www.baidu.com"}

功能演示

这里超过5s请求不到网络,会有一个网络错误 启动tomcat,再试试 安装的时候产生了一个签名错误,原因是,本机开发用的是测试的签名,需要用正式的签名在app上安装一个低版本的,再用一个相同签名的高版本来覆盖才行。 说道这里演示一下怎么生成一个正式签名的apk吧

用的开发工具是idea,

没有签名文件则需要先生成一个签名文件 这里要选V1,V2不能安装,安装的时候会报一个Certificate Error 点击,finish就会在release下生成一个正式的apk文件。

最新回复(0)