最近一年在工作上除了后端开发之余还因为大学专业是Android开发,还被安排了开发了两个APP,算是重操旧业了。更新了一波Android的一些技术栈也将一些开发遇到的零零碎碎小问题记录了下来。
获取主题的颜色
1
| android:background="?attr/colorPrimary"
|
重写了主页面
原本布局
修改后布局
修改后RecycleView与ViewPager冲突
解决办法
方法一 官方推荐
用下面的NestedScrollableHost作为RecyclerView的容器可以解决滑动冲突,具体代码及注释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| //RecyclerView.java
... //0 private int mScrollState = SCROLL_STATE_IDLE; ... @Override public boolean onInterceptTouchEvent(MotionEvent e) { ... //1 final boolean canScrollHorizontally = mLayout.canScrollHorizontally(); final boolean canScrollVertically = mLayout.canScrollVertically(); ... switch (action) { ... case MotionEvent.ACTION_MOVE: { ... final int x = (int) (e.getX(index) + 0.5f); final int y = (int) (e.getY(index) + 0.5f); //2 if (mScrollState != SCROLL_STATE_DRAGGING) { final int dx = x - mInitialTouchX; final int dy = y - mInitialTouchY; boolean startScroll = false; //2 if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { mLastTouchX = x; startScroll = true; } //3 if (canScrollVertically && Math.abs(dy) > mTouchSlop) { mLastTouchY = y; startScroll = true; } //4 if (startScroll) { setScrollState(SCROLL_STATE_DRAGGING); } } } break; ... } //5 return mScrollState == SCROLL_STATE_DRAGGING; }
|
但似乎由于我ViewPager跟RecycleView中间套了个Fragemnt 使用这个方法闪退
方法二 自定义View继承RecyclerView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class RecyclerViewAtViewPager2 extends RecyclerView {
public RecyclerViewAtViewPager2(@NonNull Context context) { super(context); }
public RecyclerViewAtViewPager2(@NonNull Context context, @Nullable AttributeSet attrs) { super(context, attrs); }
public RecyclerViewAtViewPager2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); }
private int startX, startY;
@Override public boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: startX = (int) ev.getX(); startY = (int) ev.getY(); getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int endX = (int) ev.getX(); int endY = (int) ev.getY(); int disX = Math.abs(endX - startX); int disY = Math.abs(endY - startY); LogUtils.debugInfo("DispatchTouchEvent disX="+ disX + "; disY" + disY + "; canScrollHorizontally(startX - endX) = " + canScrollHorizontally(startX - endX) + "; canScrollVertically(startY - endY)" + canScrollVertically(startY - endY)); if (disX > disY) { //如果是纵向滑动,告知父布局不进行时间拦截,交由子布局消费, requestDisallowInterceptTouchEvent(true) getParent().requestDisallowInterceptTouchEvent(canScrollHorizontally(startX - endX)); } else { getParent().requestDisallowInterceptTouchEvent(canScrollVertically(startX - endX)); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); break; } return super.dispatchTouchEvent(ev); } }
|
获取所有的已安装App列表
添加权限
1
| <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
|
获取已经安装的所有应用,PackageInfo系统类,包含应用信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private void getPackages() { List<PackageInfo> packages = getPackageManager().getInstalledPackages(0); for (int i = 0; i < packages.size(); i++) { PackageInfo packageInfo = packages.get(i); if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { AppInfo appInfo = new AppInfo(); appInfo.setAppName(packageInfo.applicationInfo.loadLabel(getPackageManager()).toString()); appInfo.setPackageName(packageInfo.packageName); appInfo.setVersionName(packageInfo.versionName); appInfo.setVersionCode(packageInfo.versionCode); appInfo.setAppIcon(packageInfo.applicationInfo.loadIcon(getPackageManager())); } else { } } }
|
获取所有用户已安装APP并且读取ClassName
1 2 3 4 5 6 7 8 9 10
| private List<ResolveInfo> getAppInfos() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); return getPackageManager().queryIntentActivities(intent, 0); }
List<ResolveInfo> packages = getAppInfos();
String packName = packages.get(position).activityInfo.packageName; String className = packages.get(position).activityInfo.name;
|
默认横屏
在AndroidManifest.xml
文件的activity
内添加android:screenOrientation="landscape"
1 2 3 4 5 6 7 8
| <activity android:name=".MainActivity" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
|
界面被输入法挤压
在AndroidManifest.xml
文件中增加android:windowSoftInputMode="adjustNothing"
,生效后界面不会被挤压。常用的值是adjustPan
。
1 2 3 4 5 6 7 8
| <activity android:name=".MainActivity" android:windowSoftInputMode="adjustNothing"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
|
隐藏虚拟按键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
protected void hideBottomUI() { int uiFlags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
if (android.os.Build.VERSION.SDK_INT >= 19) { uiFlags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; } else { uiFlags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; } getWindow().getDecorView().setSystemUiVisibility(uiFlags); getWindow().getDecorView().setOnSystemUiVisibilityChangeListener((i) -> hideBottomUI()); }
protected void hideBottomUIMenu() { if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) { View v = this.getWindow().getDecorView(); v.setSystemUiVisibility(View.GONE); } else if (Build.VERSION.SDK_INT >= 19) { Window _window = getWindow(); WindowManager.LayoutParams params = _window.getAttributes(); params.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_IMMERSIVE; _window.setAttributes(params); } }
|
卸载系统签名应用
安装了系统证书签名的APP并且使用了android:sharedUserId="android.uid.system"
时会出现报错INSTALL_FAILED_SHARED_USER_INCOMPATIBLE
使用adb命令卸载
adb uninstall com.XXX.XXX
json 转实体多重嵌套会转成Link
子类也需要添加注释
获取唯一设备ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
| package com.lnt.lnt_skillappraisal_android.utils;
import android.annotation.SuppressLint; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log;
import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.UUID;
public final class DeviceIdUtils { private static final String TAG = DeviceIdUtils.class.getSimpleName();
private static final String TEMP_DIR = "system_config"; private static final String TEMP_FILE_NAME = "system_file"; private static final String TEMP_FILE_NAME_MIME_TYPE = "application/octet-stream"; private static final String SP_NAME = "device_info"; private static final String SP_KEY_DEVICE_ID = "device_id";
public static String getDeviceId(Context context) { SharedPreferences sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); String deviceId = sharedPreferences.getString(SP_KEY_DEVICE_ID, null); if (!TextUtils.isEmpty(deviceId)) { return deviceId; } deviceId = getIMEI(context); if (TextUtils.isEmpty(deviceId)) { deviceId = createUUID(context); } sharedPreferences.edit() .putString(SP_KEY_DEVICE_ID, deviceId) .apply(); return deviceId; }
private static String createUUID(Context context) { String uuid = UUID.randomUUID().toString().replace("-", "");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { Uri externalContentUri = MediaStore.Downloads.EXTERNAL_CONTENT_URI; ContentResolver contentResolver = context.getContentResolver(); String[] projection = new String[]{ MediaStore.Downloads._ID }; String selection = MediaStore.Downloads.TITLE + "=?"; String[] args = new String[]{ TEMP_FILE_NAME }; Cursor query = contentResolver.query(externalContentUri, projection, selection, args, null); if (query != null && query.moveToFirst()) { Uri uri = ContentUris.withAppendedId(externalContentUri, query.getLong(0)); query.close();
InputStream inputStream = null; BufferedReader bufferedReader = null; try { inputStream = contentResolver.openInputStream(uri); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); uuid = bufferedReader.readLine(); } } catch (IOException e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } else { ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Downloads.TITLE, TEMP_FILE_NAME); contentValues.put(MediaStore.Downloads.MIME_TYPE, TEMP_FILE_NAME_MIME_TYPE); contentValues.put(MediaStore.Downloads.DISPLAY_NAME, TEMP_FILE_NAME); contentValues.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + File.separator + TEMP_DIR);
Uri insert = contentResolver.insert(externalContentUri, contentValues); if (insert != null) { OutputStream outputStream = null; try { outputStream = contentResolver.openOutputStream(insert); if (outputStream == null) { return uuid; } outputStream.write(uuid.getBytes()); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } } } else { File externalDownloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); File applicationFileDir = new File(externalDownloadsDir, TEMP_DIR); if (!applicationFileDir.exists()) { if (!applicationFileDir.mkdirs()) { Log.e(TAG, "文件夹创建失败: " + applicationFileDir.getPath()); } } File file = new File(applicationFileDir, TEMP_FILE_NAME); if (!file.exists()) { FileWriter fileWriter = null; try { if (file.createNewFile()) { fileWriter = new FileWriter(file, false); fileWriter.write(uuid); } else { Log.e(TAG, "文件创建失败:" + file.getPath()); } } catch (IOException e) { Log.e(TAG, "文件创建失败:" + file.getPath()); e.printStackTrace(); } finally { if (fileWriter != null) { try { fileWriter.close(); } catch (IOException e) { e.printStackTrace(); } } } } else { FileReader fileReader = null; BufferedReader bufferedReader = null; try { fileReader = new FileReader(file); bufferedReader = new BufferedReader(fileReader); uuid = bufferedReader.readLine();
bufferedReader.close(); fileReader.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } }
if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
return uuid; }
private static String getIMEI(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return null; } try { TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); if (telephonyManager == null) { return null; } @SuppressLint({"MissingPermission", "HardwareIds"}) String imei = telephonyManager.getDeviceId(); return imei; } catch (Exception e) { return null; } } }
|
灰白模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class StudyApp extends Application { @Override public void onCreate() { super.onCreate(); Paint mPaint = new Paint(); ColorMatrix mColorMatrix = new ColorMatrix(); mColorMatrix.setSaturation(0); mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix)); registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { // 当Activity创建,我们拿到DecorView,使用Paint进行重绘 View decorView = activity.getWindow().getDecorView(); decorView.setLayerType(View.LAYER_TYPE_HARDWARE, mPaint); } .... }); } }
|
Android检测NFC卡被拦截
AndroidManifest.xml
新增intent-filter
1 2 3 4 5
| <intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED"/> <category android:name="android.intent.category.DEFAULT"/> <data android:mimeType="text/plain" /> </intent-filter>
|
实现自定义消息声音
需要权限
1 2 3 4 5
| <!-- 读取存储空间的权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- 设置铃声的权限 --> <uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
可以使用以下代码来发送通知
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| private void sendNotification(Context context) { Intent intent = new Intent(context, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); if(pushContentModel.getUni()!=null){ intent.putExtra("uni",pushContentModel.getUni()); } PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* Request code */, intent, PendingIntent.FLAG_ONE_SHOT);
String channelId = "my_channel_01"; String channelName = "my_channel"; // 设置自定义铃声 String soundUri = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + R.raw.custom_notification_sound; NotificationManager notificationManager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE); // 创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_NOTIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build();
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH); notificationChannel.setDescription("channelDescription"); notificationChannel.enableLights(true); notificationChannel.enableVibration(true); notificationChannel.setSound(Uri.parse(soundUri), audioAttributes); notificationManager.createNotificationChannel(notificationChannel); } // 构建通知 NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) .setSmallIcon(R.mipmap.logo) .setContentTitle(pushContentModel.getTitle()) .setContentText(pushContentModel.getContent()) .setPriority(NotificationCompat.PRIORITY_HIGH) .setContentIntent(pendingIntent) .setAutoCancel(true);
notificationManager.notify(notificationId, builder.build());
}
|
EditText居右显示文本
1
| android:textAlignment="textEnd"
|
安装更新APP
在您的 AndroidManifest.xml 文件中,检查 FileProvider
的声明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <manifest> <!-- ... --> <application> <!-- ... --> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.your.package.name.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> </manifest>
|
在 res/xml 文件夹下的 file_paths.xml
文件中,确保正确配置根目录和文件路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <paths xmlns:android="http://schemas.android.com/apk/res/android"> <files-path name="files-path" path="." /> <cache-path name="cache-path" path="." /> <external-files-path name="external_files" path="." /> <external-cache-path name="external_cache" path="." /> <external-path name="external_storage_root" path="." /> <root-path name="my_image" path="."/> </paths>
|
在Activity中调用
1 2 3 4 5
| Intent intent = new Intent(Intent.ACTION_VIEW); Uri apkUri = FileProvider.getUriForFile(getContext(), "com.sc.workstation.fileprovider", file); intent.setData(apkUri); intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent);
|