此篇笔记记录了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.engineminimum SDK 要求最低的安装版本, 安装apk前,系统会判断当前版本是否高于(包含)此版本, 是的话才允许安装
maxSdkVersion 要求最高的安装版本(一般不用)
Target SDK 目标SDK, 一般设置为开发时使用的手机版本, 这样的话,系统在运行我的apk时,就认为我已经在该做了充分的测试, 系统就不会做过多的兼容性判断, 从而提高运行效率
Compile With 编译程序时使用的版本
如果两个应用程序, 包名相同, 但是签名不同, 就无法覆盖安装
正式签名
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文件。