一.JobService介绍
1.JobService类的继承关系
JobService
extends Service
java.lang.Object | ||||
android.content.Context | ||||
android.content.ContextWrapper | ||||
android.app.Service | ||||
android.app.job.JobService |
JobService本质上是一个service,是Google在5.0以后推出的,用来执行一些并非即时执行的后台进程.
2.JobService方法
在JobService中有两个抽象方法onStartJob(JobParameters)和onStopJob(JobParameters)
1).onStartJob()
onStartJob在JobService被调度到的时候会执行,我们只要继承JobService然后重写onStartJob方法,并在里面执行我们的后台任务就可以了,需要注意的是,在onStartJob方法中,执行定时服务主要依靠的是JobScheduler后续项目代码中会用到.
2).onStopJob(): 在此可以实现停止service服务的代码
3).定时启动JobService方法,此处启动在application中进行,保证应用在后台运行时能执行该操作
private void scheduleSilentDownloadJob(Context context) {
JobInfo.Builder builder = new JobInfo.Builder(0, new ComponentName(context, DownloadService.class));//此处DownloadService为自定义的Jobservice
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);//设置可需的网络类型
builder.setPeriodic(24 * 60 * 60 * 1000);//应用在后台的情况下,设置每隔多久启动一次
builder.setPersisted(true);
builder.setRequiresCharging(false); // 未充电状态
JobInfo jobInfo = builder.build();
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
}
4).给JobService添加权限android:permission="android.permission.BIND_JOB_SERVICE"
<service
android:name="com.jon.download.DownloadService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE" />
二.静默安装实现方法
1.一般需要获取root权限才能进行静默安装,此处代码可在android设备自带应用中进行添加
public static boolean silentInstall(String apkAbsolutePath) {
boolean isSuccess = false ;
String[] args = {"pm", "install", "-r", "-d", "-i" , "应用包名","--user","0", apkAbsolutePath};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
InputStream inIs = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
process = processBuilder.start();
baos.write('/');
inIs = process.getInputStream();
byte[] b = new byte[1024];
while (inIs.read(b) != -1) {
baos.write(b);
}
String res = new String(baos.toByteArray(), "utf-8");
isSuccess = res.contains("Success");
baos.close();
} catch (Exception e) {
Log.i(TAG,"silentInstall end"+e.toString());
e.printStackTrace();
} finally {
try {
if (inIs != null) {
inIs.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
Log.i(TAG,"silentInstall end isSuccess"+isSuccess);
return isSuccess;
}
三.部分代码展示: 根据项目需要,实现每天静默安装相关apk,apk路径及相关描述以json的形式保存,需要通过解析,下载,安装完成该功能.
1.BaseJsonUtils.java
package com.jon.download;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseJsonUtils<T> {
private static final int COUNT_EMPTY = 0;
public List<T> parseJson(String json) throws JSONException {
JSONArray dataArray = getData(json);
return parse(dataArray);
}
public JSONArray getData(String json) throws JSONException {
if (TextUtils.isEmpty(json)) {
return null;
}
JSONObject jsonObject = new JSONObject(json);
JSONArray dataArray = jsonObject.getJSONArray("data");
if (dataArray.length() <= 0) {
return null;
}
return dataArray;
}
private List<T> parse(JSONArray dataArray) throws JSONException {
List<T> items = new ArrayList<>();
if (dataArray != null) {
int length = dataArray.length();
for (int i = 0; i < length; i++) {
String jsonString = dataArray.getString(i);
T item = creatBean(jsonString);
items.add(item);
}
}
return items;
}
public abstract T creatBean(String jsonString) throws JSONException;
public static String optString(JSONObject jsonObject, String key) {
if (jsonObject.isNull(key)) {
return null;
}
return jsonObject.optString(key, "");
}
}
2.CalendarDownloadBean.java
package com.jon.download;
import java.io.Serializable;
public class CalendarDownloadBean implements Serializable {
private int mID;
private String mName;
private String mStar;
private String mPackageName;
private String mIconUrl;
private String mApkUrl;
private int mSize;
private String mDeveloper;
private int mCount;
private String mVersionName;
private String mVersionCode;
private boolean mIsOff;
private String mIntro;
private String mDetailUrl;
private boolean mFee;
public CalendarDownloadBean() {
}
public int getID() {
return mID;
}
public void setID(int mID) {
this.mID = mID;
}
public String getName() {
return mName;
}
public void setName(String mName) {
this.mName = mName;
}
public String getStar() {
return mStar;
}
public void setStar(String mStar) {
this.mStar = mStar;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String mPackageName) {
this.mPackageName = mPackageName;
}
public String getIconUrl() {
return mIconUrl;
}
public void setIconUrl(String mIconUrl) {
this.mIconUrl = mIconUrl;
}
public String getApkUrl() {
return mApkUrl;
}
public void setApkUrl(String mApkUrl) {
this.mApkUrl = mApkUrl;
}
public int getSize() {
return mSize;
}
public void setSize(int mSize) {
this.mSize = mSize;
}
public String getDeveloper() {
return mDeveloper;
}
public void setDeveloper(String mDeveloper) {
this.mDeveloper = mDeveloper;
}
public int getCount() {
return mCount;
}
public void setCount(int mCount) {
this.mCount = mCount;
}
public String getVersionName() {
return mVersionName;
}
public void setVersionName(String mVersionName) {
this.mVersionName = mVersionName;
}
public String getVersionCode() {
return mVersionCode;
}
public void setVersionCode(String mVersionCode) {
this.mVersionCode = mVersionCode;
}
public boolean isIsOff() {
return mIsOff;
}
public void setIsOff(boolean mIsOff) {
this.mIsOff = mIsOff;
}
public String getIntro() {
return mIntro;
}
public void setIntro(String mIntro) {
this.mIntro = mIntro;
}
public String getDetailUrl() {
return mDetailUrl;
}
public void setDetailUrl(String mDetailUrl) {
this.mDetailUrl = mDetailUrl;
}
public boolean isFee() {
return mFee;
}
public void setFee(boolean mFee) {
this.mFee = mFee;
}
}
3.DownloadParserUtils.java
package com.jon.download;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import com.jon.download.CalendarDownloadBean;
import com.jon.download.BaseJsonUtils;
public class DownloadParserUtils extends BaseJsonUtils<CalendarDownloadBean> {
private static DownloadParserUtils sInstance;
private DownloadParserUtils(){}
public static DownloadParserUtils getInstance() {
if (sInstance == null) {
synchronized (DownloadParserUtils.class) {
if (sInstance == null) {
sInstance = new DownloadParserUtils();
}
}
}
return sInstance;
}
@Override
public CalendarDownloadBean creatBean(String jsonString) throws JSONException {
JSONObject jsonObject = new JSONObject(jsonString);
CalendarDownloadBean item = new CalendarDownloadBean();
item.setID(jsonObject.optInt("id"));
item.setName(jsonObject.getString("name"));
item.setStar(jsonObject.getString("star"));
item.setPackageName(jsonObject.getString("packageName"));
item.setIconUrl(jsonObject.getString("iconUrl"));
item.setApkUrl(jsonObject.getString("apkUrl"));
item.setSize(jsonObject.optInt("size"));
item.setDeveloper(jsonObject.getString("developer"));
item.setCount(jsonObject.optInt("count"));
item.setVersionName(jsonObject.getString("versionName"));
item.setVersionCode(jsonObject.getString("versionCode"));
item.setIsOff(jsonObject.optInt("isOff")==1);
item.setIntro(jsonObject.getString("intro"));
item.setDetailUrl(jsonObject.getString("detailUrl"));
item.setFee(jsonObject.optInt("fee")==0);
return item;
}
}
4.DownloadService.java
package com.jon.download;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.jon.download.CalendarDownloadBean;
import com.jon.download.DownloadUtils;
import com.jon.download.DownloadParserUtils;
import org.json.JSONArray;
import org.json.JSONException;
public class DownloadService extends JobService {
private static final String TAG = "DownloadService";
private static final String TEST_URL = "";
private static final String REAL_URL = "";
public DownloadService() {
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.i(TAG,"onStartJob-----------");
final int id = jobParameters.getJobId();
initDownloadData(this);
return true;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
public void initDownloadData(final Context context){
String jsonStr=getServiceUrl();
Log.i(TAG,jsonStr.toString());
try {
DownloadUtils.readParse(jsonStr, new DownloadUtils.CallBack() {
List<CalendarDownloadBean> mList=new ArrayList<CalendarDownloadBean>();
@Override
public void successful(String json) {
Log.i(TAG,json.toString());
try {
if(mList.size()>0){
mList.clear();
}
mList=filterList(context,json);
Log.i(TAG,"mList.size()="+mList.size());
if(mList.size()>0&&mList!=null){
new DownloadFileAsync(context,mList).execute();
}
}catch (JSONException e){
Log.i(TAG,"JSONException"+e.toString());
}
}
@Override
public void error(Exception exception) {
Log.i(TAG,exception.toString()+"");
}
});
}catch (Exception e){
Log.i(TAG ,"Exception"+e.toString()+"");
}
}
private List<CalendarDownloadBean> filterList(Context context,String json) throws JSONException{
List<CalendarDownloadBean> mList=new ArrayList<CalendarDownloadBean>();
mList= DownloadParserUtils.getInstance().parseJson(json);
if(mList.size()>0&&mList!=null){
Iterator<CalendarDownloadBean> iterator=mList.iterator();
while(iterator.hasNext()){
CalendarDownloadBean bean=(CalendarDownloadBean)iterator.next();
String packageName=bean.getPackageName();
String newVersionCode=bean.getVersionCode();
if(DownloadUtils.checkApkIsInstall(context,packageName)){
String oldVersionCode=DownloadUtils.getPackageVersionCode(context,packageName)+"";
Log.i(TAG,"newVersionCode=="+newVersionCode+"--oldVersionCode"+oldVersionCode);
if(DownloadUtils.compareVersion(newVersionCode,oldVersionCode)!=1){
iterator.remove();
}
}else{
String prefInstallDate=DownloadUtils.getPrefPackageInstallDate(context,packageName);
String nowDate=DownloadUtils.getCalendarDate();
Log.i(TAG,"nowDate=="+nowDate+"--prefInstallDate="+prefInstallDate);
if(null!=prefInstallDate&&!prefInstallDate.equals("")){
if(DownloadUtils.compareVersion(nowDate,prefInstallDate)==0){
iterator.remove();
}
}
}
}//while
}//if
return mList;
}
private static String getServiceUrl() {
StringBuffer buffer = new StringBuffer();
buffer.append(Utils.isTestEnvironment() ? TEST_URL : REAL_URL);
//buffer.append(TEST_URL);
buffer.append("/jon/app/recom.do?chan=");
buffer.append("&rows=10");
return buffer.toString();
}
}
5.DownloadFileAsync.java
package com.jon.download;
import android.content.Context;
import android.os.AsyncTask;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.jon.download.CalendarDownloadBean;
import com.jon.download.DownloadUtils;
public class DownloadFileAsync extends AsyncTask<String,String ,String> {
private static final String TAG="DownloadFileAsync";
private String apkUrl="";
private String[] fileStrings={};
private static final int BUFFER_SIZE = 10 * 1024; // 8k ~ 32K
private Context mContext;
private List<CalendarDownloadBean> mList;
public DownloadFileAsync(Context context,List<CalendarDownloadBean> mBeanList) {
Log.i(TAG,"mBeanList.size()="+mBeanList.size());
mList=new ArrayList<CalendarDownloadBean>();
this.mContext=context;
this.mList=mBeanList;
fileStrings=new String[mBeanList.size()];
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected String doInBackground(String... strings) {
if(mList!=null){
for (int i=0;i<mList.size();i++){
apkUrl=mList.get(i).getApkUrl();
Log.i(TAG,"doInBackground==strings[i]="+i+" "+apkUrl);
InputStream in = null;
FileOutputStream out = null;
File apkFile =null;
try {
URL url = new URL(apkUrl);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(false);
urlConnection.setConnectTimeout(10 * 1000);
urlConnection.setReadTimeout(10 * 1000);
urlConnection.setRequestProperty("Connection", "Keep-Alive");
urlConnection.setRequestProperty("Charset", "UTF-8");
urlConnection.setRequestProperty("Accept-Encoding", "gzip, deflate");
urlConnection.connect();
int byteread = 0;
in = urlConnection.getInputStream();
File dir = DownloadUtils.createApkDir(mContext);
String apkName = apkUrl.substring(apkUrl.lastIndexOf("/") + 1, apkUrl.length());
apkFile = new File(dir.getPath(),apkName);
if(!apkFile.exists()){
apkFile.createNewFile();
}
out = new FileOutputStream(apkFile);
byte[] buffer = new byte[BUFFER_SIZE];
while ((byteread = in.read(buffer)) != -1) {
out.write(buffer, 0, byteread);
}
// 下载完成
Log.i(TAG,"downloadApk------ok=-----");
} catch (Exception e) {
Log.e(TAG, "download apk file error:" + e.getMessage());
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
}
if(null!=apkFile){
fileStrings[i]=apkFile.exists()?apkFile.toString():null;
}
//return apkFile.exists()?apkFile.toString():null;
}//for
}
return null;
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
for (int i=0;i<fileStrings.length;i++){
Log.i(TAG,"onPostExecute---"+fileStrings[i].toString());
//if(!DownloadUtils.checkApkIsInstall(mContext,packageNames[i])){
if(null!=fileStrings[i]){
final File file=new File(fileStrings[i]);
final CalendarDownloadBean bean=(CalendarDownloadBean)mList.get(i);
if(file.exists()){
new Thread(new Runnable() {
@Override
public void run() {
if(DownloadUtils.silentInstall(file.toString())){
DownloadUtils.putPrefPackageInstallDate(mContext,bean.getPackageName(),DownloadUtils.getCalendarDate());
if(file.exists()){
file.delete();
}
}
}
}).start();
}
}//if
}//for
}
}
6.DownloadUtils.java
package com.jon.download;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.support.v4.content.FileProvider;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
import android.content.pm.PackageInfo;
import java.util.Calendar;
public class DownloadUtils {
private static final String TAG = "ApkUtils";
private static final String APK_PATH="/jon/download/";
private static final String SILENT_DOWNLOAD_PREFS="silent_download_prefs";
public static File createApkDir(Context context){
String apkDir = null;
if(isExistSdcard()){
apkDir = Environment.getExternalStorageDirectory() + APK_PATH;// 保存到SD卡路径下
}else{
apkDir = context.getFilesDir().getAbsolutePath() + APK_PATH;// 保存到app的包名路径下
}
File destDir = new File(apkDir);
if (!destDir.exists()) {// 判断文件夹是否存在
destDir.mkdirs();
}
return destDir;
}
private static boolean isExistSdcard(){
String status = Environment.getExternalStorageState();
if (status.equals(Environment.MEDIA_MOUNTED)) {
return true;
} else {
return false;
}
}
public static boolean checkApkIsInstall(Context context, String packageName) {
if (packageName == null || "".equals(packageName)) {
return false;
}
try {
ApplicationInfo info = context.getPackageManager()
.getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
public static boolean silentInstall(String apkAbsolutePath) {
boolean isSuccess = false ;
String[] args = {"pm", "install", "-r", "-d", "-i" , "应用包名","--user","0", apkAbsolutePath};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
InputStream inIs = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
process = processBuilder.start();
baos.write('/');
inIs = process.getInputStream();
byte[] b = new byte[1024];
while (inIs.read(b) != -1) {
baos.write(b);
}
String res = new String(baos.toByteArray(), "utf-8");
isSuccess = res.contains("Success");
baos.close();
} catch (Exception e) {
Log.i(TAG,"silentInstall end"+e.toString());
e.printStackTrace();
} finally {
try {
if (inIs != null) {
inIs.close();
}
} catch (Exception e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
Log.i(TAG,"silentInstall end isSuccess"+isSuccess);
return isSuccess;
}
public interface CallBack {
void successful(String e);
void error(Exception exception);
}
public static void readParse(final String urlPath, final CallBack callBack) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
URL url=null;
HttpURLConnection conn=null;
InputStream inStream=null;
ByteArrayOutputStream outStream=null;
try{
outStream = new ByteArrayOutputStream();
byte[] data = new byte[1024];
int len = 0;
url = new URL(urlPath);
conn= (HttpURLConnection) url.openConnection();
inStream = conn.getInputStream();
while ((len = inStream.read(data)) != -1) {
outStream.write(data, 0, len);
}
callBack.successful(new String(outStream.toByteArray()));
}catch (Exception e){
callBack.error(e);
}finally {
if (null!=outStream){
try {
outStream.close();
}catch (IOException e){
}
}
if (null!=inStream){
try {
inStream.close();
}catch (IOException e){
}
}
if (null!=conn){
conn.disconnect();
}
}
}
}).start();
}
public static int getPackageVersionCode(Context context, String packageName) {
//上下文,包名
PackageManager pm = context.getPackageManager();
PackageInfo pi = null;
try {
pi = pm.getPackageInfo(packageName,
PackageManager.GET_CONFIGURATIONS);
return pi.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
return 0;
}
}
/**
*
* @param version1
* @param version2
* 0代表相等,1代表version1大于version2,-1代表version1小于version2
* @return
*/
public static int compareVersion(String version1, String version2) {
if (version1.equals(version2)) {
return 0;
}
String[] version1Array = version1.split("\\.");
String[] version2Array = version2.split("\\.");
int index = 0; // 获取最小长度值
int minLen = Math.min(version1Array.length, version2Array.length);
int diff = 0; // 循环判断每位的大小
while (index < minLen && (diff = Integer.parseInt(version1Array[index]) - Integer.parseInt(version2Array[index])) == 0) {
index++;
}if (diff == 0){ // 如果位数不一致,比较多余位数
for (int i = index; i < version1Array.length; i++){
if (Integer.parseInt(version1Array[i]) > 0) {
return 1;
}
}
for (int i = index; i < version2Array.length; i++) {
if (Integer.parseInt(version2Array[i]) > 0) {
return -1;
}
}
return 0;
} else {
return diff > 0 ? 1 : -1;
}
}
public static SharedPreferences getSharedPreferences(Context context) {
SharedPreferences sharedPreferences = context.getSharedPreferences(SILENT_DOWNLOAD_PREFS,
Context.MODE_PRIVATE);
return sharedPreferences;
}
public static void putPrefPackageInstallDate(Context context,String packageName,String date){
SharedPreferences sharedPreferences = getSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(packageName,date);
editor.apply();
}
public static String getPrefPackageInstallDate(Context context,String packageName){
SharedPreferences sharedPreferences = getSharedPreferences(context);
String versionCode=sharedPreferences.getString(packageName,"");
return versionCode;
}
public static String getCalendarDate(){
Calendar calendar = Calendar.getInstance();
StringBuilder sb=new StringBuilder();
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH)+1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
sb.append(year).append(month).append(day);
Log.i(TAG,"getCalendarDay "+sb.toString());
return sb.toString();
}
}