今天产品又有特大喜讯啦,App要添加新功能了普(ma)天(de)同(zhi)庆(zhang)~~~

登陆页面就强制用户更新。。。

脑壳疼+1

写吧

首先是三个工具类

apk



public class InstallApk {
    Activity context;

    public InstallApk(Activity context) {
        this.context = context;
    }

    public void installApk(File apkFile) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            boolean b = context.getPackageManager().canRequestPackageInstalls();
            intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(),
                    BuildConfig.APPLICATION_ID+".fileProvider", apkFile);
            intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
            context.startActivity(intent);

        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                Uri contentUri = FileProvider.getUriForFile(context.getApplicationContext(),
                        BuildConfig.APPLICATION_ID+".fileProvider", apkFile);
                intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
                context.startActivity(intent);
            } else {
                intent.setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);
            }
        }

    }
}



头布局



public class DownFileHelper {
    private static final String TAG = DownFileHelper.class.getSimpleName();
    Handler handler;
    Context mContext;
    NotificationManager mNotifyManager;
    Notification.Builder builder;
    private CommonProgressDialog mDialog;
    private RemoteViews contentView;

    public DownFileHelper(Context mContext, Handler handler) {
        this.handler = handler;
        this.mContext = mContext;
    }


    private void createNotification(final long total, final long current) {
        mNotifyManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
        builder.setSmallIcon(R.mipmap.ic_launcher);//必须要设置这个属性,否则不显示
        contentView = new RemoteViews(mContext.getPackageName(), R.layout.common_progress_dialog);
        contentView.setProgressBar(R.id.progress, (int) total, (int) current, true);
        builder.setOngoing(true);//设置左右滑动不能删除
        Notification notification = builder.build();
        notification.contentView = contentView;
        mNotifyManager.notify(R.layout.common_progress_dialog, notification);//发送通知

    }


    /**
     * 下载最新版本的apk
     *
     * @param path apk下载地址
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    public void downFile(final String path) {
        mDialog = new CommonProgressDialog(mContext);

        mDialog.setMessage("正在下载");
        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setMax(100);
        mDialog.setIndeterminate(true);
        mDialog.setCancelable(true);
        mDialog.show();

        mNotifyManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        Bitmap btm = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.ic_launcher);//可以换成你的app的logo
        if (Build.VERSION.SDK_INT >= 26) {



        

//创建 通知通道  channelid和channelname是必须的(自己命名就好)
            @SuppressLint("WrongConstant")
            NotificationChannel channel = new NotificationChannel("1",
                    "Channel1", NotificationManager.IMPORTANCE_LOW);
//            channel.enableLights(true);//是否在桌面icon右上角展示小红点
            channel.setLightColor(Color.GREEN);//小红点颜色
            channel.setShowBadge(true); //是否在久按桌面图标时显示此渠道的通知

            channel.enableLights(false);
            channel.enableVibration(false);
//            channel.setVibrationPattern(new long[]{0});
            channel.setSound(null, null);



mNotifyManager.createNotificationChannel(channel);

            builder = new Notification.Builder(mContext, "1");
            //设置通知显示图标、文字等
            builder.setSmallIcon(R.mipmap.ic_launcher)//可以换成你的app的logo
                    .setLargeIcon(btm)
                    .setTicker("正在下载")
                    .setContentTitle("我的app")
                    .setAutoCancel(true)
                    .build();
            mNotifyManager.notify(1, builder.build());

        } else {
            builder = new Notification.Builder(mContext);
            builder.setSmallIcon(R.drawable.ic_danmuku_off)//可以换成你的app的logo
                    .setLargeIcon(btm)
                    .setTicker("正在下载")
                    .setContentTitle("我的app")
                    .setAutoCancel(true)//可以滑动删除通知栏
                    .build();
            mNotifyManager.notify(1, builder.build());
        }
        new Thread() {
            public void run() {
                try {
                    URL url = new URL(path);
                    HttpURLConnection con = (HttpURLConnection) url.openConnection();
                    con.setReadTimeout(5000);
                    con.setConnectTimeout(5000);
                    con.setRequestProperty("Charset", "UTF-8");
                    con.setRequestMethod("GET");

                    if (con.getResponseCode() == 200) {
                        int length = con.getContentLength();// 获取文件大小
                        InputStream is = con.getInputStream();

                        FileOutputStream fileOutputStream = null;
                        if (is != null) {
                            //对apk进行保存
                            File file = new File(Environment.getExternalStorageDirectory()
                                    .getPath(), "your_app_name.apk");
                            fileOutputStream = new FileOutputStream(file);
                            byte[] buf = new byte[1024];
                            int ch;
                            int process = 0;
                            NumberFormat numberFormat = NumberFormat.getInstance();
                            // 设置精确到小数点后2位
                            numberFormat.setMaximumFractionDigits(2);
                            String result;
                            while ((ch = is.read(buf)) != -1) {
                                fileOutputStream.write(buf, 0, ch);
                                process += ch;
                                //更新进度条
                                result = numberFormat.format((float) process / (float) length * 100);
                                mDialog.setProgress((int) ((float) process / (float) length * 100));
                                builder.setContentText("下载进度:" + result + "%");

                                builder.setProgress(length, process, false);
                                mNotifyManager.notify(1, builder.build());

                            }
                        }
                        if (fileOutputStream != null) {
                            fileOutputStream.flush();
                            fileOutputStream.close();
                        }
                        //apk下载完成,使用Handler()通知安装apk
                        builder.setProgress(length, length, false);
                        builder.setContentText("已经下载完成");
                        mNotifyManager.notify(1, builder.build());
                        mNotifyManager.cancelAll();
                        handler.sendEmptyMessage(0);

                    } else {
                        Log.e(TAG, "run: ResponseCode"+ con.getResponseCode());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        }.start();

    }
}



Dialog



public class CommonProgressDialog extends AlertDialog {
    private ProgressBar mProgress;
    private TextView mProgressNumber;
    private TextView    mProgressPercent;
    private TextView    mProgressMessage;

    private Handler mViewUpdateHandler;
    private int          mMax;
    private CharSequence mMessage;
    private boolean      mHasStarted;
    private int          mProgressVal;

    private String TAG = "CommonProgressDialog";
    private String       mProgressNumberFormat;
    private NumberFormat mProgressPercentFormat;
    private TextView tvCancel;

    public CommonProgressDialog(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        initFormats();
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.common_progress_dialog);
        mProgress = (ProgressBar) findViewById(R.id.progress);
        mProgressNumber = (TextView) findViewById(R.id.progress_number);
        mProgressPercent = (TextView) findViewById(R.id.progress_percent);
        mProgressMessage = (TextView) findViewById(R.id.progress_message);
        //      LayoutInflater inflater = LayoutInflater.from(getContext());
        mViewUpdateHandler = new Handler() {


            @Override
            public void handleMessage(Message msg) {
                // TODO Auto-generated method stub
                super.handleMessage(msg);
                int progress = mProgress.getProgress();
                int max = mProgress.getMax();
                double dProgress = (double)progress;
                double dMax = (double)max;
                if (mProgressNumberFormat != null) {
                    String format = mProgressNumberFormat;
                    mProgressNumber.setText(String.format(format, dProgress, dMax));
                } else {
                    mProgressNumber.setText("");
                }
                if (mProgressPercentFormat != null) {
                    double percent = (double) progress / (double) max;
                    SpannableString tmp = new SpannableString(mProgressPercentFormat.format(percent));
                    tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
                            0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    mProgressPercent.setText(tmp);
                } else {
                    mProgressPercent.setText("");
                }
            }

        };
        onProgressChanged();
        if (mMessage != null) {
            setMessage(mMessage);
        }
        if (mMax > 0) {
            setMax(mMax);
        }
        if (mProgressVal > 0) {
            setProgress(mProgressVal);
        }


    }
    private void initFormats() {
        mProgressNumberFormat = "%s/%s";
        mProgressPercentFormat = NumberFormat.getPercentInstance();
        mProgressPercentFormat.setMaximumFractionDigits(0);
    }
    private void onProgressChanged() {
        mViewUpdateHandler.sendEmptyMessage(0);


    }
    public void setProgressStyle(int style) {
        //mProgressStyle = style;
    }
    public int getMax() {
        if (mProgress != null) {
            return mProgress.getMax();
        }
        return mMax;
    }
    public void setMax(int max) {
        if (mProgress != null) {
            mProgress.setMax(max);
            onProgressChanged();
        } else {
            mMax = max;
        }
    }
    public void setIndeterminate(boolean indeterminate) {
        if (mProgress != null) {
            mProgress.setIndeterminate(indeterminate);
        }
    }
    public void setProgress(int value) {
        if (mHasStarted) {
            mProgress.setProgress(value);
            onProgressChanged();
        } else {
            mProgressVal = value;
        }
    }


    @Override
    public void setMessage(CharSequence message) {
        // TODO Auto-generated method stub
        if(mProgressMessage!=null){
            mProgressMessage.setText(message);
        }
        else{
            mMessage = message;
        }
    }


    @Override
    protected void onStart() {
        // TODO Auto-generated method stub
        super.onStart();
        mHasStarted = true;
    }


    @Override
    protected void onStop() {
        // TODO Auto-generated method stub
        super.onStop();
        mHasStarted = false;
    }


}



然后是布局类

activity_update.xml


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <Button
        android:gravity="center"
        android:id="@+id/btn_install"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="安装"
         />
</android.support.constraint.ConstraintLayout>



common_progress_dialog.xml



<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <!--@drawable/common_progress_dialog_background"-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="#ffffff">
        <!--Title-->
        <TextView
            android:id="@+id/progress_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:layout_marginTop="30dp"
            android:layout_gravity="center_horizontal"
            android:textColor="#000"
            android:text="234"
            />

        <!--进度-->
        <ProgressBar
            android:id="@+id/progress"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="30dp"
            android:layout_marginRight="30dp"
            android:layout_centerHorizontal="true"
            android:progressDrawable="@drawable/seekbar_style"
            />

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="30dp"
            android:layout_marginRight="30dp"
            >
            <!--左边的-->
            <TextView
                android:id="@+id/progress_percent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentLeft="true"
                android:textSize="18sp"
                android:gravity="center_horizontal"
                android:textColor="#000"
                android:text="3243"
                />
            <!--右边的-->
            <TextView
                android:id="@+id/progress_number"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="30dp"
                android:textSize="16sp"
                android:gravity="center_horizontal"
                android:textColor="#000"
                android:text="23424sdfsf2"
                android:layout_alignParentRight="true"
                />
        </RelativeLayout>

    </LinearLayout>
</FrameLayout>



drawable文件夹资源文件seekbar_style.xml



<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">
        <shape >
            <corners android:radius="10dip" />
            <gradient
                android:angle="270"
                android:centerColor="#151515"
                android:centerY="0.45"
                android:endColor="#151515"
                android:startColor="#151515" />
        </shape>
    </item>

    <item android:id="@android:id/secondaryProgress">
        <clip >
            <shape >
                <corners android:radius="10dip" />
                <gradient
                    android:angle="270"
                    android:centerColor="#333333"
                    android:centerY="0.45"
                    android:endColor="#333333"
                    android:startColor="#333333" />
            </shape>
        </clip>
    </item>

    <item android:id="@android:id/progress" >
        <clip >
            <shape >
                <corners android:radius="10dip" />
                <gradient
                    android:angle="270"
                    android:centerColor="#a07e5d"
                    android:centerY="0.45"
                    android:endColor="#a07e5d"
                    android:startColor="#a07e5d"
                    />
            </shape>
        </clip>
    </item>
</layer-list>



res内新建一个xml文件夹内设置file_provider.xml



<?xml version="1.0" encoding="utf-8"?>
<paths>

    <!--1、对应内部内存卡根目录:Context.getFileDir()-->
    <!--<files-path-->
    <!--name="int_root"-->
    <!--path="/" />-->
    <!--2、对应应用默认缓存根目录:Context.getCacheDir()-->
    <!--<cache-path-->
    <!--name="app_cache"-->
    <!--path="/" />-->
    <!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()-->
    <!--<external-path-->
    <!--name="ext_root"-->
    <!--path="pictures/" />-->
    <!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)-->
    <!--<external-files-path-->
    <!--name="ext_pub"-->
    <!--path="/" />-->
    <!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()-->
    <!--<external-cache-path-->
    <!--name="ext_cache"-->
    <!--path="/" />-->

    <external-path path="Android/com.guorentong.learn.myapplication/" name="files_root" />
    <external-path path="." name="external_storage_root" />
</paths>



 

注意path="Android/com.guorentong.learn.myapplication/"要和自己的报名一致

然后清单文件里application标签内设置



<!-- FileProvider配置访问路径,适配7.0及其以上 -->
<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.guorentong.learn.myapplication.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider" />
</provider>



 

在Activity中设置



//版本更新
private String[] permissionStr = new String[]{
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 0:
                new InstallApk(HomeActivity.this)
                        .installApk(new File(Environment.getExternalStorageDirectory(), "your_app_name.apk"));
                break;
        }

    }
};

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public void permissionsCheckAndDownload() {
    if (Build.VERSION.SDK_INT >= 23) {
        applyPermission();
    } else {
        new DownFileHelper(HomeActivity.this, handler)
                .downFile(mUrl);
    }

}

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void applyPermission() {
    /**
     * 第 1 步: 检查是否有相应的权限
     */
    boolean isAllGranted = checkPermissionAllGranted(permissionStr);
    // 如果这3个权限全都拥有, 则直接执行备份代码
    if (!isAllGranted) {
        /**
         * 第 2 步: 请求权限
         */
        // 一次请求多个权限, 如果其他有权限是已经授予的将会自动忽略掉
        ActivityCompat.requestPermissions(this, permissionStr,1);
    } else {
        new DownFileHelper(HomeActivity.this, handler)
                .downFile(mUrl);
    }
}

/**
 * 检查是否拥有指定的所有权限
 */
private boolean checkPermissionAllGranted(String[] permissions) {
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            // 只要有一个权限没有被授予, 则直接返回 false
            return false;
        }
    }
    return true;
}


@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    boolean isAllGranted = true;

    // 判断是否所有的权限都已经授予了
    for (int grant : grantResults) {
        if (grant != PackageManager.PERMISSION_GRANTED) {
            isAllGranted = false;
            break;
        }
    }

    if (isAllGranted) {
        // 如果所有的权限都授予了, 则执行备份代码
        new DownFileHelper(HomeActivity.this, handler)
                .downFile(mUrl);
    } else {
        // 弹出对话框告诉用户需要权限的原因, 并引导用户去应用权限管理中手动打开权限按钮
        openAppDetails();
    }
}

private void openAppDetails() {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);
    builder.setMessage("备份通讯录需要访问 “通讯录” 和 “外部存储器”,请到 “应用信息 -> 权限” 中授予!");
    builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            Intent intent = new Intent();
            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
            intent.addCategory(Intent.CATEGORY_DEFAULT);
            intent.setData(Uri.parse("package:" + getPackageName()));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            startActivity(intent);
        }
    });
    builder.setNegativeButton("取消", null);
    builder.show();
}



 

使用的时候判断版本,或者返回字段调用方法



permissionsCheckAndDownload();



就可以了

别忘了设置权限允许未知程序安装 



//安装权限 <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />



如果需要改变样式可以自己修改

最好 其实 是不用activity开启更新,而采用非绑定式服务。这样后台运行也可以继续下载避免被杀死,防止下载停止。