App应用 一般更新

App应用 一般更新
  • 意义

    • 能及时告知用户有新的版本
  • 途径

    • 各大应用市场:google play 等

实践

  • 下载apk
  • 安装apk
所需的类图

在这里插入图片描述

所需的类
/**
 * @author zl
 * 版本 1.0
 * 创建时间 2021/6/6
 * 描述:
 * 事件的监听回调
 */
public interface UpdateDownLoadListener {

    //下载请求开始回调
     void onStarted();

    //下载进度回调
     void onProgressChanged(int progress,String downLoadUrl);


    //下载完成
    void onFinished(long completeSize,String downLoadUrl);


    //下载失败
    void onFailure();

}
/**
 * @author zl
 * 版本 1.0
 * 创建时间 2021/6/6
 * 描述:
 * 负责处理文件 和 线程切换
 */
public class UpdateDownLoadRequest implements Runnable {

    private static final String TAG = "UpdateDownLoadRequest";

    private String downLoadUrl;
    private String localFilePath;
    private UpdateDownLoadListener mUpdateDownLoadListener;
    private boolean isDownloading = false;//正在下载中
    private long currentLength;//当前文件的长度
    private DownLoadResponseHandler mDownLoadResponseHandler;

    //构造函数
    public UpdateDownLoadRequest(String downLoadUrl, String localFilePath, UpdateDownLoadListener updateDownLoadListener) {
        isDownloading = true;
        this.downLoadUrl = downLoadUrl;
        this.localFilePath = localFilePath;
        this.mUpdateDownLoadListener = updateDownLoadListener;
        this.mDownLoadResponseHandler = new DownLoadResponseHandler();
    }


    //建立连接
    private void makeRequest() throws IOException, InterruptedException {
        if (!Thread.currentThread().isInterrupted()) {
            URL url = new URL(downLoadUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.connect();//阻塞我们当前线程
            currentLength = connection.getContentLength();
            if (!Thread.currentThread().isInterrupted()) {
                //真正的完成文件的下载
                mDownLoadResponseHandler.sendResponseMessage(connection.getInputStream());
            }
        }
    }

    @Override
    public void run() {
        try {
            makeRequest();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public enum FailureCode {
        UnknownHost, Socket, SocketTimeOut, ConnectionTimeOut,
        IO, JSON, Interrupted, HttpResponse
    }


    //真正下载文件,发送消息 和 接口回调
    public class DownLoadResponseHandler {
        protected static final int FAILURE_MESSAGE = 1;
        protected static final int FINISH_MESSAGE = 3;
        protected static final int PROGRESS_CHANGED = 5;
        private int mCompleteSize;
        private int progress;
        private int oldProgress; //之前的进度

        private Handler mHandler;//真正完成线程间通信

        public DownLoadResponseHandler() {
            mHandler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    handleSlefMessage(msg);
                }
            };
        }

        //消息处理
        private void handleSlefMessage(Message msg) {
            Object[] response;
            switch (msg.what) {
                case FINISH_MESSAGE:
                    onFinish();
                    break;
                case PROGRESS_CHANGED:
                    response = (Object[]) msg.obj;
                    handleProgressChangedMessage(((Integer) response[0]).intValue());
                    break;
                case FAILURE_MESSAGE:
                    response = (Object[]) msg.obj;
                    handleFailureMessage((FailureCode) response[0]);
                    break;
                default:
            }
        }


        private void sendMessage(Message msg) {
            if (mHandler != null) {
                mHandler.sendMessage(msg);
            } else {
                handleSlefMessage(msg);
            }
        }


        //消息发送
        protected void sendFinishMessage() {
            sendMessage(obtainMessage(FINISH_MESSAGE, null));
        }

        protected void sendProgressChangedMessage(int progress) {
            sendMessage(obtainMessage(PROGRESS_CHANGED, new Object[]{progress}));
        }

        protected void sendFailureMessage(FailureCode failureCode) {
            sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{failureCode}));
        }


        //获取一个消息
        private Message obtainMessage(int responseMessage, Object response) {
            Message msg;
            if (mHandler != null) {
                msg = mHandler.obtainMessage(responseMessage, response);
            } else {
                msg = Message.obtain();
                msg.what = responseMessage;
                msg.obj = response;
            }
            return msg;
        }


        //消息接口回调处理
        private void onFinish() {
            if (mUpdateDownLoadListener != null) {
                mUpdateDownLoadListener.onFinished(mCompleteSize, "");
            }
        }

        private void onFailure(FailureCode failureCode) {
            if (mUpdateDownLoadListener != null) {
                mUpdateDownLoadListener.onFailure();
            }
        }

        private void handleFailureMessage(FailureCode failureCode) {
            onFailure(failureCode);
        }

        private void handleProgressChangedMessage(int progress) {
            if (mUpdateDownLoadListener != null) {
                mUpdateDownLoadListener.onProgressChanged(progress, "");
            }
        }

        //真正下载方法,发送各种类型的事件
        void sendResponseMessage(InputStream is) {
            RandomAccessFile randomAccessFile = null;
            mCompleteSize = 0;
            try {
                byte[] buffer = new byte[1024];
                int length;
                //用指针指向这个文件,并标记为read + write(double)
                randomAccessFile = new RandomAccessFile(localFilePath, "rwd");
                while ((length = is.read(buffer)) != -1) {
                    if (isDownloading) {
                        randomAccessFile.write(buffer, 0, length);
                        mCompleteSize += length;
                        if (mCompleteSize <= currentLength) {
                            progress = (int) (mCompleteSize * 100L / currentLength);
                            Log.d(TAG, "sendResponseMessage: 当前的下载进度是 " + progress);
                            if (oldProgress != progress) {
                                sendProgressChangedMessage(progress);
                            }
                            oldProgress = progress;
                        }
                    }
                }

                sendFinishMessage();
            } catch (IOException e) {
                sendFailureMessage(FailureCode.IO);
            } finally {

                try {
                    if (is != null) {
                        is.close();
                    }

                    if (randomAccessFile != null) {
                        randomAccessFile.close();
                    }
                } catch (IOException e) {
                    sendFailureMessage(FailureCode.IO);
                }
            }
        }
    }

}
/**
 * @author zl
 * 版本 1.0
 * 创建时间 2021/6/6
 * 描述:
 *  调用UpdateDownloadRequest
 */
public class UpdateManager {

    private static UpdateManager manager;
    private ThreadPoolExecutor mThreadPoolExecutor;
    private UpdateDownLoadRequest mUpdateDownLoadRequest;

    private UpdateManager(){
        mThreadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    }

    static {
        Log.d("tag","对象创建 声明周期");
        manager = new UpdateManager();
    }


    public static UpdateManager getInstance(){
        return manager;
    }


    public void startDownLoad(String downloadUrl,String localPath,UpdateDownLoadListener loadListener){

        //如果任务已存在,不做任何操作
        if(mUpdateDownLoadRequest != null){
            return;
        }

        //文件名不能为空
        if(TextUtils.isEmpty(localPath)){
            return;
        }

        //检查路径是否存在 或 合法
        checkLocalFilePath(localPath);
        //开始真正的去下载任务
        mUpdateDownLoadRequest = new UpdateDownLoadRequest(downloadUrl,localPath,loadListener);
        Future<?> future = mThreadPoolExecutor.submit(mUpdateDownLoadRequest);
    }


    private void checkLocalFilePath(String localPath) {
        //判断文件夹是否存在
        File dir = new File(localPath.substring(0,localPath.lastIndexOf("/") + 1));
        if(!dir.exists()){
            dir.mkdirs();
        }
        //判断文件是否存在
        File file = new File(localPath);
        if(!file.exists()){
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * @author zl
 * 版本 1.0
 * 创建时间 2021/6/6
 * 描述:
 * app更新下载后台任务
 */
public class UpdateService extends Service {
    //默认测试的apk
    private String apkUrl = "http://xyapk.xy599.com/xiaoyu_351_77_gw.apk";
    private String filePath;
    private NotificationManager mNotificationManagerCompat;
    private Notification mNotificationCompat;


    @SuppressLint("ServiceCast")
    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("tag", "onCreate ");

        mNotificationManagerCompat = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        filePath = Environment.getExternalStorageDirectory() + "/xyzq.apk";
        Log.d("tag", "filePath =  " + filePath);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("tag", "onStartCommand");

        if (intent == null) {
            notityUser("下载失败", "参数为空", 0);
            stopSelf();
        }
        notityUser("下载开始", "下载开始", 0);
        startDownLoad();

        return super.onStartCommand(intent, flags, startId);
    }

    private void startDownLoad() {
        UpdateManager.getInstance().startDownLoad(apkUrl, filePath, new UpdateDownLoadListener() {
            @Override
            public void onStarted() {
                Toast.makeText(UpdateService.this, "开始下载", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onProgressChanged(int progress, String downLoadUrl) {
                notityUser("正在下载", "正在下载", progress);
            }

            @Override
            public void onFinished(long completeSize, String downLoadUrl) {
                notityUser("下载完成", "下载完成", 100);
                stopSelf();
            }

            @Override
            public void onFailure() {
                notityUser("下载失败", "下载失败", 0);
                stopSelf();
            }
        });
    }

    //更新我们的Notification ,告知用户进度
    private void notityUser(String result, String reason, int progress) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
        builder.setSmallIcon(R.mipmap.ic_launcher);
        builder.setContentTitle(getString(R.string.app_name));

        if (progress > 0 && progress < 100) {
            builder.setProgress(100, progress, false);
        } else {
            //设置0 可以隐藏progress
            builder.setProgress(100, 100, false);
        }
        builder.setAutoCancel(true);
        builder.setWhen(System.currentTimeMillis());
        //显示的文本
        builder.setTicker(result);
        builder.setContentIntent(progress >= 100 ? getContentIntent()
                : PendingIntent.getActivity(this, 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
        builder.setChannelId(getApplication().getPackageName()); //必须添加(Android 8.0) 【唯一标识】


        builder.setPriority(Notification.PRIORITY_MIN).setSmallIcon(R.mipmap.ic_launcher)
                // .setColor(0xFF000000)
                //.setContentIntent(PendingIntent.getService(this, 0, actionIntent, 0))
                .setOngoing(false)
                .setShowWhen(false);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            setupNotificationChannel();
            builder.setChannelId("test");
        }

        mNotificationCompat = builder.build();
        mNotificationManagerCompat.notify(0, mNotificationCompat);

    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void setupNotificationChannel() {
        String channelName = "Termux_Backer";
        String channelDescription = "Notifications from Termux_Backer";
        int importance = NotificationManager.IMPORTANCE_LOW;

        NotificationChannel channel = new NotificationChannel("test", channelName, importance);
        channel.setDescription(channelDescription);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.createNotificationChannel(channel);
    }

    //点击notification的操作
    private PendingIntent getContentIntent() {
        //安装APk
        Toast.makeText(this, "安装APk", Toast.LENGTH_SHORT).show();

//        install();

        return null;
    }

    public void install() {

        try {
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            Uri uri;
            File file = new File(filePath);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                String packageName = getApplication().getPackageName();
                String authority = new StringBuilder(packageName).append(".fileprovider").toString();
                uri = FileProvider.getUriForFile(getApplicationContext(), authority, file);
                intent.setDataAndType(uri, "application/vnd.android.package-archive");
            } else {
                uri = Uri.fromFile(file);
                intent.setDataAndType(uri, "application/vnd.android.package-archive");
            }
            getApplicationContext().startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}
/**
 * @author zl
 * 版本 1.0
 * 创建时间 2021/6/6
 * 描述:
 */
public class UpdateActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
        }

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 101);
        } else {
            Intent intent = new Intent(this, UpdateService.class);
            startService(intent);

            boolean en = NotificationManagerCompat.from(this).areNotificationsEnabled();
            Log.e("tag", en + "");
        }
    }
}
其他配置
  • AndroidManifest.xml
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>

    <!-- 安装apk 权限 -->
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    
    
     <provider
            android:exported="false"
            android:grantUriPermissions="true"
            android:authorities="com.liangzai.fulisehapplication.fileprovider"
            android:name="androidx.core.content.FileProvider">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                />
        </provider>
  • file_paths配置

    <?xml version="1.0" encoding="utf-8"?>
    <paths>
        <!-- 注意 name 是不能一样的,不然会覆盖  -->
        <!-- files-path /data/data/包名/files   目录 -->
        <!-- external-path /storage/emulate/0  目录 -->
        <!-- external-files-path /storage/emulate/0/Android/data/包名/files  目录 -->
        <!-- Environment.getExternalStorageDirectory() 和 external-path 对应 -->
    
        <external-path
            name="external_files"
            path="."/>
    
        <files-path
            name="apk"
            path="apk/"/>
    
    </paths>
    
关于遗留的部分
  • 不能重复下载,应该在网络下载之前判断本地是否已存在或本地版本是不是最新的,后序版本优化
快捷键配置
  • option + command + t try catch 快捷键
  • logt 快速打印 TAG
  • logd 快速打印 Log
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值