使用activity安装xapk
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
<activity
|
||||
android:name=".activity.upload.UploadActivity"
|
||||
android:launchMode="singleTask" />
|
||||
<activity android:name=".activity.InstallActivity"
|
||||
<activity android:name=".activity.install.InstallActivity"
|
||||
android:enabled="true"
|
||||
android:launchMode="singleTop"
|
||||
android:exported="true"/>
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.example.mir4updater.activity;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.utils.XapkUtils;
|
||||
|
||||
public class InstallActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "InstallActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_install);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
Log.e(TAG, "onNewIntent: ");
|
||||
Bundle extras = intent.getExtras();
|
||||
if (XapkUtils.PACKAGE_INSTALLED_ACTION.equals(intent.getAction())) {
|
||||
int status = -100;
|
||||
String message = "";
|
||||
if (extras != null) {
|
||||
status = extras.getInt(PackageInstaller.EXTRA_STATUS);
|
||||
message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
}
|
||||
switch (status) {
|
||||
case PackageInstaller.STATUS_PENDING_USER_ACTION:
|
||||
// This test app isn't privileged, so the user has to confirm the install.
|
||||
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
|
||||
startActivity(confirmIntent);
|
||||
break;
|
||||
case PackageInstaller.STATUS_SUCCESS:
|
||||
Toast.makeText(this, "安装成功!", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
break;
|
||||
case PackageInstaller.STATUS_FAILURE:
|
||||
case PackageInstaller.STATUS_FAILURE_ABORTED:
|
||||
case PackageInstaller.STATUS_FAILURE_BLOCKED:
|
||||
case PackageInstaller.STATUS_FAILURE_CONFLICT:
|
||||
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
|
||||
case PackageInstaller.STATUS_FAILURE_INVALID:
|
||||
case PackageInstaller.STATUS_FAILURE_STORAGE:
|
||||
Toast.makeText(this, "安装失败,请重试",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
Log.d(TAG, "Install failed! " + status + ", " + message);
|
||||
break;
|
||||
default:
|
||||
Toast.makeText(this, "安装失败,解压文件可能已丢失或损坏,请重试",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
Log.d(TAG, "Unrecognized status received from installer: " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
package com.example.mir4updater.activity.install;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.blankj.utilcode.util.RomUtils;
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.base.mvvm.BaseMvvmActivity;
|
||||
import com.example.mir4updater.databinding.ActivityInstallBinding;
|
||||
import com.example.mir4updater.utils.FileUtils;
|
||||
import com.hjq.toast.Toaster;
|
||||
|
||||
import org.zeroturnaround.zip.ZipUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.ObservableEmitter;
|
||||
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
|
||||
import io.reactivex.rxjava3.core.Observer;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class InstallActivity extends BaseMvvmActivity<InstallViewModel,ActivityInstallBinding> {
|
||||
|
||||
private static final String TAG = "InstallActivity";
|
||||
|
||||
private static final String PACKAGE_INSTALLED_ACTION = "TT_SESSION_API_PACKAGE_INSTALLED";
|
||||
|
||||
public static final String KEY_XAPK_PATH = "xapk_path";
|
||||
public static final String KEY_APK_PATHS = "apk_paths";
|
||||
|
||||
private String mXapkPath;
|
||||
|
||||
private ExecutorService installXapkExectuor;
|
||||
|
||||
private PackageInstaller.Session mSession;
|
||||
|
||||
|
||||
@Override
|
||||
protected int getLayoutId() {
|
||||
return R.layout.activity_install;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDataBinding() {
|
||||
mViewModel.setCtx(this);
|
||||
mViewModel.setLifecycle(getLifecycleSubject());
|
||||
mViewModel.setVDBinding(mViewDataBinding);
|
||||
mViewDataBinding.setClick(new BtnClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initView() {
|
||||
|
||||
}
|
||||
|
||||
public void initData() {
|
||||
// apkPaths = getIntent().getStringArrayListExtra(KEY_APK_PATHS);
|
||||
Intent intent = getIntent();
|
||||
mXapkPath = intent.getStringExtra(KEY_XAPK_PATH);
|
||||
Log.e(TAG, "initData: " + mXapkPath);
|
||||
if (TextUtils.isEmpty(mXapkPath) || !new File(mXapkPath).exists()) {
|
||||
Toaster.show("文件不存在");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
Observable.create(new ObservableOnSubscribe<List<String>>() {
|
||||
@Override
|
||||
public void subscribe(ObservableEmitter<List<String>> emitter) throws Exception {
|
||||
long startTime = System.currentTimeMillis();
|
||||
File mApkFile = new File(mXapkPath);
|
||||
String dirName = FileUtils.getFileNoExName(mApkFile.getAbsolutePath());
|
||||
String unpackPath = getExternalCacheDir() + File.separator + "xapk_unzip" + File.separator + dirName;
|
||||
File unpackDir = new File(unpackPath);
|
||||
ZipUtil.unpack(mApkFile, unpackDir);
|
||||
Log.e(TAG, "subscribe: time = " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
List<File> files = new ArrayList<>(Arrays.asList(unpackDir.listFiles()));
|
||||
List<String> paths = files.stream().map(new Function<File, String>() {
|
||||
@Override
|
||||
public String apply(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
List<String> apkPaths = paths.stream().filter(new Predicate<String>() {
|
||||
@Override
|
||||
public boolean test(String s) {
|
||||
return s.endsWith(".apk");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
emitter.onNext(apkPaths);
|
||||
emitter.onComplete();
|
||||
}
|
||||
}).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(new Observer<List<String>>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(List<String> fileList) {
|
||||
Log.e(TAG, "installXAPK: " + fileList);
|
||||
installXapk(fileList);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "onError: " + e.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
Log.e(TAG, "onComplete: ");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void installXapk(List<String> mApkPaths) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
Toast.makeText(this, "暂时不支持安装,请更新到Android 5.0及以上版本", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
if (mApkPaths == null || mApkPaths.isEmpty()) {
|
||||
Toast.makeText(this, "解析apk出错或已取消", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
if (RomUtils.isMeizu() || RomUtils.isVivo()) {
|
||||
Toast.makeText(this, "魅族或VIVO系统用户如遇安装被中止或者安装失败的情况,请尝试联系手机平台客服,或者更换系统内置包安装器再重试",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
installXapkExectuor = Executors.newSingleThreadExecutor();
|
||||
installXapkExectuor.execute(() -> {
|
||||
try {
|
||||
mSession = initSession();
|
||||
|
||||
for (String apkPath : mApkPaths) {
|
||||
addApkToInstallSession(apkPath, mSession);
|
||||
}
|
||||
|
||||
commitSession(mSession);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
abandonSession();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private PackageInstaller.Session initSession() throws IOException {
|
||||
PackageInstaller.Session session;
|
||||
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
|
||||
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
|
||||
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
|
||||
|
||||
int sessionId;
|
||||
sessionId = packageInstaller.createSession(params);
|
||||
session = packageInstaller.openSession(sessionId);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
private void addApkToInstallSession(String filePath, PackageInstaller.Session session)
|
||||
throws IOException {
|
||||
// It's recommended to pass the file size to openWrite(). Otherwise installation may fail
|
||||
// if the disk is almost full.
|
||||
try (OutputStream packageInSession = session.openWrite(FileUtils.getFileName(filePath), 0, new File(filePath).length());
|
||||
InputStream is = new BufferedInputStream(new FileInputStream(filePath))) {
|
||||
byte[] buffer = new byte[16384];
|
||||
int n;
|
||||
while ((n = is.read(buffer)) >= 0) {
|
||||
packageInSession.write(buffer, 0, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void commitSession(PackageInstaller.Session session) {
|
||||
// Create an install status receiver.
|
||||
Intent intent = new Intent(this, InstallActivity.class);
|
||||
intent.setAction(PACKAGE_INSTALLED_ACTION);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
|
||||
IntentSender intentSender = pendingIntent.getIntentSender();
|
||||
|
||||
// Commit the session (this will start the installation workflow).
|
||||
session.commit(intentSender);
|
||||
}
|
||||
|
||||
private void abandonSession() {
|
||||
if (mSession != null) {
|
||||
mSession.abandon();
|
||||
mSession.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
Bundle extras = intent.getExtras();
|
||||
if (PACKAGE_INSTALLED_ACTION.equals(intent.getAction())) {
|
||||
int status = -100;
|
||||
String message = "";
|
||||
if (extras != null) {
|
||||
status = extras.getInt(PackageInstaller.EXTRA_STATUS);
|
||||
message = extras.getString(PackageInstaller.EXTRA_STATUS_MESSAGE);
|
||||
Log.e(TAG, "onNewIntent: status = " + status);
|
||||
Log.e(TAG, "onNewIntent: message = " + message);
|
||||
}
|
||||
switch (status) {
|
||||
case PackageInstaller.STATUS_PENDING_USER_ACTION:
|
||||
// This test app isn't privileged, so the user has to confirm the install.
|
||||
Intent confirmIntent = (Intent) extras.get(Intent.EXTRA_INTENT);
|
||||
startActivity(confirmIntent);
|
||||
break;
|
||||
case PackageInstaller.STATUS_SUCCESS:
|
||||
Toast.makeText(this, "安装成功!", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
break;
|
||||
case PackageInstaller.STATUS_FAILURE:
|
||||
case PackageInstaller.STATUS_FAILURE_ABORTED:
|
||||
case PackageInstaller.STATUS_FAILURE_BLOCKED:
|
||||
case PackageInstaller.STATUS_FAILURE_CONFLICT:
|
||||
case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE:
|
||||
case PackageInstaller.STATUS_FAILURE_INVALID:
|
||||
case PackageInstaller.STATUS_FAILURE_STORAGE:
|
||||
Toast.makeText(this, "安装失败,请重试",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
Log.d(TAG, "Install failed! " + status + ", " + message);
|
||||
break;
|
||||
default:
|
||||
Toast.makeText(this, "安装失败,解压文件可能已丢失或损坏,请重试",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
Log.d(TAG, "Unrecognized status received from installer: " + status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (installXapkExectuor != null && !installXapkExectuor.isShutdown()) {
|
||||
installXapkExectuor.shutdown();
|
||||
}
|
||||
abandonSession();
|
||||
}
|
||||
|
||||
|
||||
public class BtnClick {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.mir4updater.activity.install;
|
||||
|
||||
import com.example.mir4updater.base.mvvm.BaseViewModel;
|
||||
import com.example.mir4updater.databinding.ActivityInstallBinding;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
|
||||
public class InstallViewModel extends BaseViewModel<ActivityInstallBinding, ActivityEvent> {
|
||||
|
||||
@Override
|
||||
public ActivityInstallBinding getVDBinding() {
|
||||
return binding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.mir4updater.activity.main;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@@ -16,6 +15,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import com.example.mir4updater.R;
|
||||
import com.example.mir4updater.activity.install.InstallActivity;
|
||||
import com.example.mir4updater.activity.update.UpdateActivity;
|
||||
import com.example.mir4updater.activity.upload.UploadActivity;
|
||||
import com.example.mir4updater.base.mvvm.BaseMvvmActivity;
|
||||
@@ -24,17 +24,12 @@ import com.example.mir4updater.config.CommonConfig;
|
||||
import com.example.mir4updater.databinding.ActivityMainBinding;
|
||||
import com.example.mir4updater.utils.HashUtils;
|
||||
import com.example.mir4updater.utils.PermissionUtils;
|
||||
import com.example.mir4updater.utils.XapkUtils;
|
||||
import com.hjq.permissions.OnPermissionCallback;
|
||||
import com.hjq.permissions.Permission;
|
||||
import com.hjq.permissions.XXPermissions;
|
||||
import com.hjq.toast.Toaster;
|
||||
import com.trello.rxlifecycle4.RxLifecycle;
|
||||
import com.trello.rxlifecycle4.android.ActivityEvent;
|
||||
import com.wuliang.lib.XapkInstaller;
|
||||
import com.wuliang.lib.XapkInstallerFactoryKt;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -567,9 +562,13 @@ public class MainActivity extends BaseMvvmActivity<MainViewModel, ActivityMainBi
|
||||
return;
|
||||
}
|
||||
|
||||
XapkUtils xapkUtils =new XapkUtils(MainActivity.this);
|
||||
xapkUtils.setApkFilePath(file.getAbsolutePath());
|
||||
xapkUtils.installXapk();
|
||||
Intent intent = new Intent(MainActivity.this, InstallActivity.class);
|
||||
intent.putExtra(InstallActivity.KEY_XAPK_PATH, file.getAbsolutePath());
|
||||
startActivity(intent);
|
||||
|
||||
// XapkUtils xapkUtils =new XapkUtils(MainActivity.this);
|
||||
// xapkUtils.setApkFilePath(file.getAbsolutePath());
|
||||
// xapkUtils.installXapk();
|
||||
|
||||
// TODO: 2025/4/21 google chrome 需要google框架
|
||||
//Install failed! 7, INSTALL_FAILED_MISSING_SHARED_LIBRARY: Reconciliation failed...: Reconcile failed:
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
package com.example.mir4updater.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class FileUtils {
|
||||
public static String getFileNoExName(String filePath) {
|
||||
|
||||
String fileNameEx = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
|
||||
String fileName = fileNameEx.substring(0,fileNameEx.lastIndexOf("."));
|
||||
String fileName = fileNameEx.substring(0, fileNameEx.lastIndexOf("."));
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public static String getFileName(String filePath) {
|
||||
if (TextUtils.isEmpty(filePath)){
|
||||
return "";
|
||||
}
|
||||
if (!filePath.contains(File.separator)){
|
||||
return filePath;
|
||||
}
|
||||
String fileNameEx = filePath.substring(filePath.lastIndexOf(File.separator) + 1);
|
||||
return fileNameEx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.example.mir4updater.activity.InstallActivity;
|
||||
import com.example.mir4updater.bean.Expansions;
|
||||
import com.example.mir4updater.bean.SplitApks;
|
||||
import com.example.mir4updater.receiver.InstallResultReceiver;
|
||||
@@ -312,14 +311,14 @@ public class XapkUtils {
|
||||
try {
|
||||
PackageInstaller.Session session = packageInstaller.openSession(sessionId);
|
||||
|
||||
// Intent intent = new Intent(context, InstallResultReceiver.class);
|
||||
// PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
// context, sessionId, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
||||
// );
|
||||
Intent intent = new Intent(context, InstallResultReceiver.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
||||
context, sessionId, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
Intent intent = new Intent(mContext, InstallActivity.class);
|
||||
intent.setAction(PACKAGE_INSTALLED_ACTION);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
|
||||
// Intent intent = new Intent(mContext, InstallActivity.class);
|
||||
// intent.setAction(PACKAGE_INSTALLED_ACTION);
|
||||
// PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
|
||||
|
||||
session.commit(pendingIntent.getIntentSender());
|
||||
} catch (IOException e) {
|
||||
@@ -346,7 +345,8 @@ public class XapkUtils {
|
||||
PackageInstaller.Session session = null;
|
||||
try {
|
||||
session = pi.openSession(sessionId);
|
||||
OutputStream out = session.openWrite(FileUtils.getFileNoExName(apkFilePath), 0, apkFile.length());
|
||||
Log.e(TAG, "copyApkFile: " + FileUtils.getFileName(apkFilePath));
|
||||
OutputStream out = session.openWrite(FileUtils.getFileName(apkFilePath), 0, apkFile.length());
|
||||
FileInputStream input = new FileInputStream(apkFile);
|
||||
int read = 0;
|
||||
byte[] buffer = new byte[65536];
|
||||
|
||||
@@ -1,9 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<layout 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"
|
||||
tools:context=".activity.InstallActivity">
|
||||
tools:context=".activity.nstall.InstallActivity">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="click"
|
||||
type="com.example.mir4updater.activity.install.InstallActivity.BtnClick" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.372" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:text="正在安装"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="13sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/iv_icon" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
Reference in New Issue
Block a user